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 ) {
15 // For whatever reason, IE has trouble passing the window object
16 // around, causing it to be cloned in the process
17 if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
21 // Make sure that the function being executed has a unique ID
22 if ( !handler.guid ) {
23 handler.guid = this.guid++;
26 // if data is passed, bind to handler
27 if ( data !== undefined ) {
28 // Create temporary function pointer to original handler
31 // Create unique handler function, wrapped around original handler
32 handler = this.proxy( fn );
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 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
44 jQuery.event.handle.apply( arguments.callee.elem, arguments ) :
47 // Add elem as a property of the handle function
48 // This is to prevent a memory leak with non-native
52 // Handle multiple events separated by a space
53 // jQuery(...).bind("mouseover mouseout", fn);
54 types = types.split( /\s+/ );
56 while ( (type = types[ i++ ]) ) {
57 // Namespaced event handlers
58 var namespaces = type.split(".");
59 type = namespaces.shift();
60 handler.type = namespaces.slice().sort().join(".");
62 // Get the current list of functions bound to this event
63 var handlers = events[ type ];
65 if ( this.specialAll[ type ] ) {
66 this.specialAll[ type ].setup.call( elem, data, namespaces );
69 // Init the event handler queue
71 handlers = events[ type ] = {};
73 // Check for a special event handler
74 // Only use addEventListener/attachEvent if the special
75 // events handler returns false
76 if ( !this.special[ type ] || this.special[ type ].setup.call( elem, data, namespaces ) === false ) {
77 // Bind the global event handler to the element
78 if ( elem.addEventListener ) {
79 elem.addEventListener( type, handle, false );
80 } else if ( elem.attachEvent ) {
81 elem.attachEvent( "on" + type, handle );
86 // Add the function to the element's handler list
87 handlers[ handler.guid ] = handler;
89 // Keep track of which events have been used, for global triggering
90 this.global[ type ] = true;
93 // Nullify elem to prevent memory leaks in IE
100 // Detach an event or set of events from an element
101 remove: function( elem, types, handler ) {
102 // don't do events on text and comment nodes
103 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
107 var events = jQuery.data( elem, "events" ), ret, type;
110 // Unbind all events for the element
111 if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) {
112 for ( type in events ) {
113 this.remove( elem, type + (types || "") );
116 // types is actually an event object here
118 handler = types.handler;
122 // Handle multiple events seperated by a space
123 // jQuery(...).unbind("mouseover mouseout", fn);
124 types = types.split(/\s+/);
126 while ( (type = types[ i++ ]) ) {
127 // Namespaced event handlers
128 var namespaces = type.split(".");
129 type = namespaces.shift();
130 var all = !namespaces.length,
131 namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
133 if ( events[type] ) {
134 // remove the given handler for the given type
136 delete events[ type ][ handler.guid ];
138 // remove all handlers for the given type
140 for ( var handle in events[ type ] ) {
141 // Handle the removal of namespaced events
142 if ( all || namespace.test( events[ type ][ handle ].type ) ) {
143 delete events[ type ][ handle ];
148 if ( this.specialAll[ type ] ) {
149 this.specialAll[ type ].teardown.call( elem, namespaces );
152 // remove generic event handler if no more handlers exist
153 for ( ret in events[ type ] ) {
157 if ( !this.special[ type ] || this.special[ type ].teardown.call( elem, namespaces ) === false ) {
158 if ( elem.removeEventListener ) {
159 elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
160 } else if ( elem.detachEvent ) {
161 elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) );
165 delete events[ type ];
171 // Remove the expando if it's no longer used
172 for ( ret in events ) {
176 var handle = jQuery.data( elem, "handle" );
180 jQuery.removeData( elem, "events" );
181 jQuery.removeData( elem, "handle" );
186 // bubbling is internal
187 trigger: function( event, data, elem, bubbling ) {
188 // Event object or event type
189 var type = event.type || event;
192 event = typeof event === "object" ?
193 // jQuery.Event object
194 event[expando] ? event :
196 jQuery.extend( jQuery.Event(type), event ) :
197 // Just the event type (string)
200 if ( type.indexOf("!") >= 0 ) {
201 event.type = type = type.slice(0, -1);
202 event.exclusive = true;
205 // Handle a global trigger
207 // Don't bubble custom events when global (to avoid too much overhead)
208 event.stopPropagation();
209 // Only trigger if we've ever bound an event for it
210 if ( this.global[ type ] ) {
211 for ( var cached in jQuery.cache ) {
212 if ( cached.events && cached.events[ type ] ) {
213 this.trigger( event, data, cached.handle.elem );
219 // Handle triggering a single element
221 // don't do events on text and comment nodes
222 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
226 // Clean up in case it is reused
227 event.result = undefined;
230 // Clone the incoming data, if any
231 data = jQuery.makeArray( data );
232 data.unshift( event );
235 event.currentTarget = elem;
237 // Trigger the event, it is assumed that "handle" is a function
238 var handle = jQuery.data( elem, "handle" );
240 handle.apply( elem, data );
243 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
244 if ( (!elem[ type ] || (jQuery.nodeName(elem, 'a') && type === "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) {
245 event.result = false;
248 // Trigger the native events (except for clicks on links)
249 if ( !bubbling && elem[ type ] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type === "click") ) {
250 this.triggered = true;
253 // prevent IE from throwing an error for some hidden elements
257 this.triggered = false;
259 if ( !event.isPropagationStopped() ) {
260 var parent = elem.parentNode || elem.ownerDocument;
262 jQuery.event.trigger( event, data, parent, true );
267 handle: function( event ) {
268 // returned undefined or false
271 event = arguments[0] = jQuery.event.fix( event || window.event );
272 event.currentTarget = this;
274 // Namespaced event handlers
275 var namespaces = event.type.split(".");
276 event.type = namespaces.shift();
278 // Cache this now, all = true means, any handler
279 all = !namespaces.length && !event.exclusive;
281 var namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
283 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
285 for ( var j in handlers ) {
286 var handler = handlers[ j ];
288 // Filter the functions by class
289 if ( all || namespace.test(handler.type) ) {
290 // Pass in a reference to the handler function itself
291 // So that we can later remove it
292 event.handler = handler;
293 event.data = handler.data;
295 var ret = handler.apply( this, arguments );
297 if ( ret !== undefined ) {
299 if ( ret === false ) {
300 event.preventDefault();
301 event.stopPropagation();
305 if ( event.isImmediatePropagationStopped() ) {
313 props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
315 fix: function( event ) {
316 if ( event[ expando ] ) {
320 // store a copy of the original event object
321 // and "clone" to set read-only properties
322 var originalEvent = event;
323 event = jQuery.Event( originalEvent );
325 for ( var i = this.props.length, prop; i; ) {
326 prop = this.props[ --i ];
327 event[ prop ] = originalEvent[ prop ];
330 // Fix target property, if necessary
331 if ( !event.target ) {
332 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
335 // check if target is a textnode (safari)
336 if ( event.target.nodeType === 3 ) {
337 event.target = event.target.parentNode;
340 // Add relatedTarget, if necessary
341 if ( !event.relatedTarget && event.fromElement ) {
342 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
345 // Calculate pageX/Y if missing and clientX/Y available
346 if ( event.pageX == null && event.clientX != null ) {
347 var doc = document.documentElement, body = document.body;
348 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
349 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
352 // Add which for key events
353 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
354 event.which = event.charCode || event.keyCode;
357 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
358 if ( !event.metaKey && event.ctrlKey ) {
359 event.metaKey = event.ctrlKey;
362 // Add which for click: 1 == left; 2 == middle; 3 == right
363 // Note: button is not normalized, so don't use it
364 if ( !event.which && event.button ) {
365 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
371 proxy: function( fn, proxy ) {
372 proxy = proxy || function() { return fn.apply( this, arguments ); };
373 // Set the guid of unique handler to the same of original handler, so it can be removed
374 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
375 // So proxy can be declared as an argument
381 // Make sure the ready event is setup
383 teardown: function() {}
389 setup: function( selector, namespaces ) {
390 jQuery.event.add( this, namespaces[0], liveHandler );
392 teardown: function( namespaces ) {
393 if ( namespaces.length ) {
394 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
396 jQuery.each( (jQuery.data(this, "events").live || {}), function() {
397 if ( name.test(this.type) ) {
403 jQuery.event.remove( this, namespaces[0], liveHandler );
411 jQuery.Event = function( src ){
412 // Allow instantiation without the 'new' keyword
413 if ( !this.preventDefault ) {
414 return new jQuery.Event( src );
418 if ( src && src.type ) {
419 this.originalEvent = src;
420 this.type = src.type;
426 // timeStamp is buggy for some events on Firefox(#3843)
427 // So we won't rely on the native value
428 this.timeStamp = now();
431 this[ expando ] = true;
434 function returnFalse() {
437 function returnTrue() {
441 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
442 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
443 jQuery.Event.prototype = {
444 preventDefault: function() {
445 this.isDefaultPrevented = returnTrue;
447 var e = this.originalEvent;
451 // if preventDefault exists run it on the original event
452 if ( e.preventDefault ) {
455 // otherwise set the returnValue property of the original event to false (IE)
456 e.returnValue = false;
458 stopPropagation: function() {
459 this.isPropagationStopped = returnTrue;
461 var e = this.originalEvent;
465 // if stopPropagation exists run it on the original event
466 if ( e.stopPropagation ) {
469 // otherwise set the cancelBubble property of the original event to true (IE)
470 e.cancelBubble = true;
472 stopImmediatePropagation: function(){
473 this.isImmediatePropagationStopped = returnTrue;
474 this.stopPropagation();
476 isDefaultPrevented: returnFalse,
477 isPropagationStopped: returnFalse,
478 isImmediatePropagationStopped: returnFalse
480 // Checks if an event happened on an element within another element
481 // Used in jQuery.event.special.mouseenter and mouseleave handlers
482 var withinElement = function( event ) {
483 // Check if mouse(over|out) are still within the same parent element
484 var parent = event.relatedTarget;
485 // Traverse up the tree
486 while ( parent && parent != this ) {
487 try { parent = parent.parentNode; }
488 catch(e) { parent = this; }
491 if ( parent != this ) {
492 // set the correct event type
493 event.type = event.data;
494 // handle event if we actually just moused on to a non sub-element
495 jQuery.event.handle.apply( this, arguments );
500 mouseover: 'mouseenter',
501 mouseout: 'mouseleave'
502 }, function( orig, fix ) {
503 jQuery.event.special[ fix ] = {
505 jQuery.event.add( this, orig, withinElement, fix );
507 teardown: function(){
508 jQuery.event.remove( this, orig, withinElement );
514 bind: function( type, data, fn ) {
515 return type === "unload" ? this.one(type, data, fn) : this.each(function() {
516 jQuery.event.add( this, type, fn || data, fn && data );
520 one: function( type, data, fn ) {
521 var one = jQuery.event.proxy( fn || data, function( event ) {
522 jQuery( this ).unbind( event, one );
523 return (fn || data).apply( this, arguments );
525 return this.each(function() {
526 jQuery.event.add( this, type, one, fn && data );
530 unbind: function( type, fn ) {
531 return this.each(function() {
532 jQuery.event.remove( this, type, fn );
536 trigger: function( type, data ) {
537 return this.each(function() {
538 jQuery.event.trigger( type, data, this );
542 triggerHandler: function( type, data ) {
544 var event = jQuery.Event( type );
545 event.preventDefault();
546 event.stopPropagation();
547 jQuery.event.trigger( event, data, this[0] );
552 toggle: function( fn ) {
553 // Save reference to arguments for access in closure
554 var args = arguments, i = 1;
556 // link all the functions, so any of them can unbind this click handler
557 while( i < args.length ) {
558 jQuery.event.proxy( fn, args[ i++ ] );
561 return this.click( jQuery.event.proxy( fn, function( event ) {
562 // Figure out which function to execute
563 this.lastToggle = ( this.lastToggle || 0 ) % i;
565 // Make sure that clicks stop
566 event.preventDefault();
568 // and execute the function
569 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
573 hover: function( fnOver, fnOut ) {
574 return this.mouseenter( fnOver ).mouseleave( fnOut );
577 ready: function( fn ) {
578 // Attach the listeners
581 // If the DOM is already ready
582 if ( jQuery.isReady ) {
583 // Execute the function immediately
584 fn.call( document, jQuery );
586 // Otherwise, remember the function for later
588 // Add the function to the wait list
589 jQuery.readyList.push( fn );
595 live: function( type, fn ) {
596 var proxy = jQuery.event.proxy( fn );
597 proxy.guid += this.selector + type;
599 jQuery( this.context ).bind( liveConvert( type, this.selector ), this.selector, proxy );
604 die: function( type, fn ) {
605 jQuery( this.context ).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
610 function liveHandler( event ) {
611 var check = new RegExp("(^|\\.)" + event.type + "(\\.|$)"),
612 stop = true, elems = [];
614 jQuery.each( jQuery.data( this, "events" ).live || [], function( i, fn ) {
615 if ( check.test( fn.type ) ) {
616 var elem = jQuery( event.target ).closest( fn.data )[0];
618 elems.push({ elem: elem, fn: fn });
623 elems.sort(function( a, b ) {
624 return jQuery.data( a.elem, "closest" ) - jQuery.data( b.elem, "closest" );
627 jQuery.each(elems, function() {
628 event.currentTarget = this.elem;
629 if ( this.fn.call( this.elem, event, this.fn.data ) === false ) {
630 return (stop = false);
637 function liveConvert( type, selector ) {
638 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
644 // Handle when the DOM is ready
646 // Make sure that the DOM is not already loaded
647 if ( !jQuery.isReady ) {
648 // Remember that the DOM is ready
649 jQuery.isReady = true;
651 // If there are functions bound, to execute
652 if ( jQuery.readyList ) {
653 // Execute all of them
655 while ( (fn = jQuery.readyList[ i++ ]) ) {
656 fn.call( document, jQuery );
659 // Reset the list of functions
660 jQuery.readyList = null;
663 // Trigger any bound ready events
664 jQuery( document ).triggerHandler( "ready" );
669 var readyBound = false;
671 function bindReady() {
672 if ( readyBound ) return;
675 // Mozilla, Opera and webkit nightlies currently support this event
676 if ( document.addEventListener ) {
677 // Use the handy event callback
678 document.addEventListener( "DOMContentLoaded", function() {
679 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
683 // If IE event model is used
684 } else if ( document.attachEvent ) {
685 // ensure firing before onload,
686 // maybe late but safe also for iframes
687 document.attachEvent("onreadystatechange", function() {
688 if ( document.readyState === "complete" ) {
689 document.detachEvent( "onreadystatechange", arguments.callee );
694 // If IE and not an iframe
695 // continually check to see if the document is ready
696 if ( document.documentElement.doScroll && window === window.top ) (function() {
697 if ( jQuery.isReady ) {
702 // If IE is used, use the trick by Diego Perini
703 // http://javascript.nwbox.com/IEContentLoaded/
704 document.documentElement.doScroll("left");
706 setTimeout( arguments.callee, 0 );
710 // and execute any waiting functions
715 // A fallback to window.onload, that will always work
716 jQuery.event.add( window, "load", jQuery.ready );
719 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
720 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
721 "change,select,submit,keydown,keypress,keyup,error").split(","), function( i, name ) {
723 // Handle event binding
724 jQuery.fn[ name ] = function( fn ) {
725 return fn ? this.bind (name, fn ) : this.trigger( name );
729 // Prevent memory leaks in IE
730 // And prevent errors on refresh with events like mouseover in other browsers
731 // Window isn't included so as not to unbind existing unload events
733 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
734 // - https://bugzilla.mozilla.org/show_bug.cgi?id=252542
735 jQuery( window ).bind( 'unload', function() {
736 for ( var id in jQuery.cache ) {
738 if ( id != 1 && jQuery.cache[ id ].handle ) {
739 jQuery.event.remove( jQuery.cache[ id ].handle.elem );