2 * A number of helper functions used for managing events.
3 * Many of the ideas behind this code originated from
4 * Dean Edwards' addEvent library.
8 // Bind an event to an element
9 // Original by Dean Edwards
10 add: function(elem, types, handler, data) {
11 if ( elem.nodeType == 3 || elem.nodeType == 8 )
14 // For whatever reason, IE has trouble passing the window object
15 // around, causing it to be cloned in the process
16 if ( jQuery.browser.msie && elem.setInterval )
19 // Make sure that the function being executed has a unique ID
21 handler.guid = this.guid++;
23 // if data is passed, bind to handler
24 if ( data !== undefined ) {
25 // Create temporary function pointer to original handler
28 // Create unique handler function, wrapped around original handler
29 handler = this.proxy( fn, function() {
30 // Pass arguments and context to original handler
31 return fn.apply(this, arguments);
34 // Store data in unique handler
38 // Init the element's event structure
39 var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
40 handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
41 // Handle the second event of a trigger and when
42 // an event is called after a page has unloaded
43 if ( typeof jQuery !== "undefined" && !jQuery.event.triggered )
44 return jQuery.event.handle.apply(arguments.callee.elem, arguments);
46 // Add elem as a property of the handle function
47 // This is to prevent a memory leak with non-native
51 // Handle multiple events separated by a space
52 // jQuery(...).bind("mouseover mouseout", fn);
53 jQuery.each(types.split(/\s+/), function(index, type) {
54 // Namespaced event handlers
55 var parts = type.split(".");
57 handler.type = parts[1];
59 // Get the current list of functions bound to this event
60 var handlers = events[type];
62 // Init the event handler queue
64 handlers = events[type] = {};
66 // Check for a special event handler
67 // Only use addEventListener/attachEvent if the special
68 // events handler returns false
69 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem,data) === false ) {
70 // Bind the global event handler to the element
71 if (elem.addEventListener)
72 elem.addEventListener(type, handle, false);
73 else if (elem.attachEvent)
74 elem.attachEvent("on" + type, handle);
78 // Add the function to the element's handler list
79 handlers[handler.guid] = handler;
81 // Keep track of which events have been used, for global triggering
82 jQuery.event.global[type] = true;
85 // Nullify elem to prevent memory leaks in IE
92 // Detach an event or set of events from an element
93 remove: function(elem, types, handler) {
94 // don't do events on text and comment nodes
95 if ( elem.nodeType == 3 || elem.nodeType == 8 )
98 var events = jQuery.data(elem, "events"), ret, index;
101 // Unbind all events for the element
102 if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
103 for ( var type in events )
104 this.remove( elem, type + (types || "") );
106 // types is actually an event object here
108 handler = types.handler;
112 // Handle multiple events seperated by a space
113 // jQuery(...).unbind("mouseover mouseout", fn);
114 jQuery.each(types.split(/\s+/), function(index, type){
115 // Namespaced event handlers
116 var parts = type.split(".");
119 if ( events[type] ) {
120 // remove the given handler for the given type
122 delete events[type][handler.guid];
124 // remove all handlers for the given type
126 for ( handler in events[type] )
127 // Handle the removal of namespaced events
128 if ( !parts[1] || events[type][handler].type == parts[1] )
129 delete events[type][handler];
131 // remove generic event handler if no more handlers exist
132 for ( ret in events[type] ) break;
134 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
135 if (elem.removeEventListener)
136 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
137 else if (elem.detachEvent)
138 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
147 // Remove the expando if it's no longer used
148 for ( ret in events ) break;
150 var handle = jQuery.data( elem, "handle" );
151 if ( handle ) handle.elem = null;
152 jQuery.removeData( elem, "events" );
153 jQuery.removeData( elem, "handle" );
158 trigger: function(type, data, elem, donative, extra) {
159 // Clone the incoming data, if any
160 data = jQuery.makeArray(data);
162 if ( type.indexOf("!") >= 0 ) {
163 type = type.slice(0, -1);
164 var exclusive = true;
167 // Handle a global trigger
169 // Only trigger if we've ever bound an event for it
170 if ( this.global[type] )
171 jQuery.each( jQuery.cache, function(){
172 if ( this.events && this.events[type] )
173 jQuery.event.trigger( type, data, this.handle.elem );
176 // Handle triggering a single element
178 // don't do events on text and comment nodes
179 if ( elem.nodeType == 3 || elem.nodeType == 8 )
182 var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
183 // Check to see if we need to provide a fake event, or not
184 event = !data[0] || !data[0].preventDefault;
186 // Pass along a fake event
191 preventDefault: function(){},
192 stopPropagation: function(){},
193 stopImmediatePropagation:stopImmediatePropagation,
196 data[0][expando] = true; // no need to fix fake event
199 // Enforce the right trigger type
202 data[0].exclusive = true;
204 // Trigger the event, it is assumed that "handle" is a function
205 var handle = jQuery.data(elem, "handle");
207 val = handle.apply( elem, data );
209 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
210 if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
213 // Extra functions don't get the custom event object
217 // Handle triggering of extra function
218 if ( extra && jQuery.isFunction( extra ) ) {
219 // call the extra function and tack the current return value on the end for possible inspection
220 ret = extra.apply( elem, val == null ? data : data.concat( val ) );
221 // if anything is returned, give it precedence and have it overwrite the previous value
222 if ( ret !== undefined )
226 // Trigger the native events (except for clicks on links)
227 if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
228 this.triggered = true;
231 // prevent IE from throwing an error for some hidden elements
235 this.triggered = false;
241 handle: function(event) {
242 // returned undefined or false
243 var val, ret, namespace, all, handlers;
245 event = arguments[0] = jQuery.event.fix( event || window.event );
247 // Namespaced event handlers
248 namespace = event.type.split(".");
249 event.type = namespace[0];
250 namespace = namespace[1];
251 // Cache this now, all = true means, any handler
252 all = !namespace && !event.exclusive;
254 handlers = ( jQuery.data(this, "events") || {} )[event.type];
256 for ( var j in handlers ) {
257 var handler = handlers[j];
259 // Filter the functions by class
260 if ( all || handler.type == namespace ) {
261 // Pass in a reference to the handler function itself
262 // So that we can later remove it
263 event.handler = handler;
264 event.data = handler.data;
266 ret = handler.apply( this, arguments );
271 if ( ret === false ) {
272 event.preventDefault();
273 event.stopPropagation();
285 props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" "),
287 fix: function(event) {
288 if ( event[expando] )
291 // store a copy of the original event object
292 // and "clone" to set read-only properties
293 var originalEvent = event;
294 event = { originalEvent: originalEvent };
296 for ( var i = this.props.length, prop; i; ){
297 prop = this.props[ --i ];
298 event[ prop ] = originalEvent[ prop ];
302 event[expando] = true;
304 // add preventDefault and stopPropagation since
305 // they will not work on the clone
306 event.preventDefault = function() {
307 // if preventDefault exists run it on the original event
308 if (originalEvent.preventDefault)
309 originalEvent.preventDefault();
310 // otherwise set the returnValue property of the original event to false (IE)
311 originalEvent.returnValue = false;
313 event.stopPropagation = function() {
314 // if stopPropagation exists run it on the original event
315 if (originalEvent.stopPropagation)
316 originalEvent.stopPropagation();
317 // otherwise set the cancelBubble property of the original event to true (IE)
318 originalEvent.cancelBubble = true;
321 event.stopImmediatePropagation = stopImmediatePropagation;
324 event.timeStamp = event.timeStamp || now();
326 // Fix target property, if necessary
328 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
330 // check if target is a textnode (safari)
331 if ( event.target.nodeType == 3 )
332 event.target = event.target.parentNode;
334 // Add relatedTarget, if necessary
335 if ( !event.relatedTarget && event.fromElement )
336 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
338 // Calculate pageX/Y if missing and clientX/Y available
339 if ( event.pageX == null && event.clientX != null ) {
340 var doc = document.documentElement, body = document.body;
341 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
342 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
345 // Add which for key events
346 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
347 event.which = event.charCode || event.keyCode;
349 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
350 if ( !event.metaKey && event.ctrlKey )
351 event.metaKey = event.ctrlKey;
353 // Add which for click: 1 == left; 2 == middle; 3 == right
354 // Note: button is not normalized, so don't use it
355 if ( !event.which && event.button )
356 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
361 proxy: function( fn, proxy ){
362 // Set the guid of unique handler to the same of original handler, so it can be removed
363 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
364 // So proxy can be declared as an argument
370 // Make sure the ready event is setup
372 teardown: function() {}
377 function stopImmediatePropagation(){
379 this.stopPropagation();
382 if ( !jQuery.browser.msie ){
383 // Checks if an event happened on an element within another element
384 // Used in jQuery.event.special.mouseenter and mouseleave handlers
385 var withinElement = function(event) {
386 // Check if mouse(over|out) are still within the same parent element
387 var parent = event.relatedTarget;
388 // Traverse up the tree
389 while ( parent && parent != this )
390 try { parent = parent.parentNode; }
391 catch(e) { parent = this; }
393 if( parent != this ){
394 // set the correct event type
395 event.type = event.data;
396 // handle event if we actually just moused on to a non sub-element
397 jQuery.event.handle.apply( this, arguments );
402 mouseover: 'mouseenter',
403 mouseout: 'mouseleave'
404 }, function( orig, fix ){
405 jQuery.event.special[ fix ] = {
407 jQuery.event.add( this, orig, withinElement, fix );
409 teardown: function(){
410 jQuery.event.remove( this, orig, withinElement );
417 bind: function( type, data, fn ) {
418 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
419 jQuery.event.add( this, type, fn || data, fn && data );
423 one: function( type, data, fn ) {
424 var one = jQuery.event.proxy( fn || data, function(event) {
425 jQuery(this).unbind(event, one);
426 return (fn || data).apply( this, arguments );
428 return this.each(function(){
429 jQuery.event.add( this, type, one, fn && data);
433 unbind: function( type, fn ) {
434 return this.each(function(){
435 jQuery.event.remove( this, type, fn );
439 trigger: function( type, data, fn ) {
440 return this.each(function(){
441 jQuery.event.trigger( type, data, this, true, fn );
445 triggerHandler: function( type, data, fn ) {
446 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
449 toggle: function( fn ) {
450 // Save reference to arguments for access in closure
451 var args = arguments, i = 1;
453 // link all the functions, so any of them can unbind this click handler
454 while( i < args.length )
455 jQuery.event.proxy( fn, args[i++] );
457 return this.click( jQuery.event.proxy( fn, function(event) {
458 // Figure out which function to execute
459 this.lastToggle = ( this.lastToggle || 0 ) % i;
461 // Make sure that clicks stop
462 event.preventDefault();
464 // and execute the function
465 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
469 hover: function(fnOver, fnOut) {
470 return this.mouseenter(fnOver).mouseleave(fnOut);
473 ready: function(fn) {
474 // Attach the listeners
477 // If the DOM is already ready
478 if ( jQuery.isReady )
479 // Execute the function immediately
480 fn.call( document, jQuery );
482 // Otherwise, remember the function for later
484 // Add the function to the wait list
485 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
494 // Handle when the DOM is ready
496 // Make sure that the DOM is not already loaded
497 if ( !jQuery.isReady ) {
498 // Remember that the DOM is ready
499 jQuery.isReady = true;
501 // If there are functions bound, to execute
502 if ( jQuery.readyList ) {
503 // Execute all of them
504 jQuery.each( jQuery.readyList, function(){
505 this.call( document );
508 // Reset the list of functions
509 jQuery.readyList = null;
512 // Trigger any bound ready events
513 jQuery(document).triggerHandler("ready");
518 var readyBound = false;
520 function bindReady(){
521 if ( readyBound ) return;
524 // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
525 if ( document.addEventListener && !jQuery.browser.opera)
526 // Use the handy event callback
527 document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
529 // If IE event model is used
530 if ( document.attachEvent )
531 // ensure firing before onload,
532 // maybe late but safe also for iframes
533 document.attachEvent("onreadystatechange", function(e) {
534 if ( document.readyState == "complete" ) {
535 document.detachEvent("onreadystatechange", arguments.callee );
540 // If IE and not an iframe
541 if ( document.documentElement.doScroll && !window.frameElement )
542 // continually check to see if the document is ready
544 if (jQuery.isReady) return;
546 // If IE is used, use the trick by Diego Perini
547 // http://javascript.nwbox.com/IEContentLoaded/
548 document.documentElement.doScroll("left");
550 setTimeout( arguments.callee, 0 );
553 // and execute any waiting functions
557 if ( jQuery.browser.opera )
558 document.addEventListener( "DOMContentLoaded", function () {
559 if (jQuery.isReady) return;
560 for (var i = 0; i < document.styleSheets.length; i++)
561 if (document.styleSheets[i].disabled) {
562 setTimeout( arguments.callee, 0 );
565 // and execute any waiting functions
569 if ( jQuery.browser.safari ) {
572 if (jQuery.isReady) return;
573 if ( document.readyState != "loaded" && document.readyState != "complete" ) {
574 setTimeout( arguments.callee, 0 );
577 if ( numStyles === undefined )
578 numStyles = jQuery("style, link[rel=stylesheet]").length;
579 if ( document.styleSheets.length != numStyles ) {
580 setTimeout( arguments.callee, 0 );
583 // and execute any waiting functions
588 // A fallback to window.onload, that will always work
589 jQuery.event.add( window, "load", jQuery.ready );
592 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
593 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
594 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
596 // Handle event binding
597 jQuery.fn[name] = function(fn){
598 return fn ? this.bind(name, fn) : this.trigger(name);
602 // Prevent memory leaks in IE
603 // And prevent errors on refresh with events like mouseover in other browsers
604 // Window isn't included so as not to unbind existing unload events
605 jQuery( window ).bind( 'unload', function(){
606 for ( var id in jQuery.cache )
608 if ( id != 1 && jQuery.cache[ id ].handle )
609 jQuery.event.remove( jQuery.cache[ id ].handle.elem );