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 ( elem.setInterval && ( elem != window && !elem.frameElement ) )
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 );
31 // Store data in unique handler
35 // Init the element's event structure
36 var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
37 handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
38 // Handle the second event of a trigger and when
39 // an event is called after a page has unloaded
40 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
41 jQuery.event.handle.apply(arguments.callee.elem, arguments) :
44 // Add elem as a property of the handle function
45 // This is to prevent a memory leak with non-native
49 // Handle multiple events separated by a space
50 // jQuery(...).bind("mouseover mouseout", fn);
51 jQuery.each(types.split(/\s+/), function(index, type) {
52 // Namespaced event handlers
53 var namespaces = type.split(".");
54 type = namespaces.shift();
55 handler.type = namespaces.slice().sort().join(".");
57 // Get the current list of functions bound to this event
58 var handlers = events[type];
60 if ( jQuery.event.specialAll[type] )
61 jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
63 // Init the event handler queue
65 handlers = events[type] = {};
67 // Check for a special event handler
68 // Only use addEventListener/attachEvent if the special
69 // events handler returns false
70 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
71 // Bind the global event handler to the element
72 if (elem.addEventListener)
73 elem.addEventListener(type, handle, false);
74 else if (elem.attachEvent)
75 elem.attachEvent("on" + type, handle);
79 // Add the function to the element's handler list
80 handlers[handler.guid] = handler;
82 // Keep track of which events have been used, for global triggering
83 jQuery.event.global[type] = true;
86 // Nullify elem to prevent memory leaks in IE
93 // Detach an event or set of events from an element
94 remove: function(elem, types, handler) {
95 // don't do events on text and comment nodes
96 if ( elem.nodeType == 3 || elem.nodeType == 8 )
99 var events = jQuery.data(elem, "events"), ret, index;
102 // Unbind all events for the element
103 if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
104 for ( var type in events )
105 this.remove( elem, type + (types || "") );
107 // types is actually an event object here
109 handler = types.handler;
113 // Handle multiple events seperated by a space
114 // jQuery(...).unbind("mouseover mouseout", fn);
115 jQuery.each(types.split(/\s+/), function(index, type){
116 // Namespaced event handlers
117 var namespaces = type.split(".");
118 type = namespaces.shift();
119 var all = !namespaces.length,
120 namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
122 if ( events[type] ) {
123 // remove the given handler for the given type
125 delete events[type][handler.guid];
127 // remove all handlers for the given type
129 for ( var handle in events[type] )
130 // Handle the removal of namespaced events
131 if ( all || namespace.test(events[type][handle].type) )
132 delete events[type][handle];
134 if ( jQuery.event.specialAll[type] )
135 jQuery.event.specialAll[type].teardown.call(elem, namespaces);
137 // remove generic event handler if no more handlers exist
138 for ( ret in events[type] ) break;
140 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
141 if (elem.removeEventListener)
142 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
143 else if (elem.detachEvent)
144 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
153 // Remove the expando if it's no longer used
154 for ( ret in events ) break;
156 var handle = jQuery.data( elem, "handle" );
157 if ( handle ) handle.elem = null;
158 jQuery.removeData( elem, "events" );
159 jQuery.removeData( elem, "handle" );
164 // bubbling is internal
165 trigger: function( event, data, elem, bubbling ) {
166 // Event object or event type
167 var type = event.type || event;
170 event = typeof event === "object" ?
171 // jQuery.Event object
172 event[expando] ? event :
174 jQuery.extend( jQuery.Event(type), event ) :
175 // Just the event type (string)
178 if ( type.indexOf("!") >= 0 ) {
179 event.type = type = type.slice(0, -1);
180 event.exclusive = true;
183 // Handle a global trigger
185 // Don't bubble custom events when global (to avoid too much overhead)
186 event.stopPropagation();
187 // Only trigger if we've ever bound an event for it
188 if ( this.global[type] )
189 jQuery.each( jQuery.cache, function(){
190 if ( this.events && this.events[type] )
191 jQuery.event.trigger( event, data, this.handle.elem );
195 // Handle triggering a single element
197 // don't do events on text and comment nodes
198 if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
201 // Clean up in case it is reused
202 event.result = undefined;
205 // Clone the incoming data, if any
206 data = jQuery.makeArray(data);
207 data.unshift( event );
210 event.currentTarget = elem;
212 // Trigger the event, it is assumed that "handle" is a function
213 var handle = jQuery.data(elem, "handle");
215 handle.apply( elem, data );
217 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
218 if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
219 event.result = false;
221 // Trigger the native events (except for clicks on links)
222 if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
223 this.triggered = true;
226 // prevent IE from throwing an error for some hidden elements
230 this.triggered = false;
232 if ( !event.isPropagationStopped() ) {
233 var parent = elem.parentNode || elem.ownerDocument;
235 jQuery.event.trigger(event, data, parent, true);
239 handle: function(event) {
240 // returned undefined or false
243 event = arguments[0] = jQuery.event.fix( event || window.event );
244 event.currentTarget = this;
246 // Namespaced event handlers
247 var namespaces = event.type.split(".");
248 event.type = namespaces.shift();
250 // Cache this now, all = true means, any handler
251 all = !namespaces.length && !event.exclusive;
253 var namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
255 handlers = ( jQuery.data(this, "events") || {} )[event.type];
257 for ( var j in handlers ) {
258 var handler = handlers[j];
260 // Filter the functions by class
261 if ( all || namespace.test(handler.type) ) {
262 // Pass in a reference to the handler function itself
263 // So that we can later remove it
264 event.handler = handler;
265 event.data = handler.data;
267 var ret = handler.apply(this, arguments);
269 if( ret !== undefined ){
271 if ( ret === false ) {
272 event.preventDefault();
273 event.stopPropagation();
277 if( event.isImmediatePropagationStopped() )
284 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(" "),
286 fix: function(event) {
287 if ( event[expando] )
290 // store a copy of the original event object
291 // and "clone" to set read-only properties
292 var originalEvent = event;
293 event = jQuery.Event( originalEvent );
295 for ( var i = this.props.length, prop; i; ){
296 prop = this.props[ --i ];
297 event[ prop ] = originalEvent[ prop ];
300 // Fix target property, if necessary
302 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
304 // check if target is a textnode (safari)
305 if ( event.target.nodeType == 3 )
306 event.target = event.target.parentNode;
308 // Add relatedTarget, if necessary
309 if ( !event.relatedTarget && event.fromElement )
310 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
312 // Calculate pageX/Y if missing and clientX/Y available
313 if ( event.pageX == null && event.clientX != null ) {
314 var doc = document.documentElement, body = document.body;
315 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
316 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
319 // Add which for key events
320 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
321 event.which = event.charCode || event.keyCode;
323 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
324 if ( !event.metaKey && event.ctrlKey )
325 event.metaKey = event.ctrlKey;
327 // Add which for click: 1 == left; 2 == middle; 3 == right
328 // Note: button is not normalized, so don't use it
329 if ( !event.which && event.button )
330 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
335 proxy: function( fn, proxy ){
336 proxy = proxy || function(){ return fn.apply(this, arguments); };
337 // Set the guid of unique handler to the same of original handler, so it can be removed
338 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
339 // So proxy can be declared as an argument
345 // Make sure the ready event is setup
347 teardown: function() {}
353 setup: function( selector, namespaces ){
354 jQuery.event.add( this, namespaces[0], liveHandler );
356 teardown: function( namespaces ){
357 if ( namespaces.length ) {
358 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
360 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
361 if ( name.test(this.type) )
366 jQuery.event.remove( this, namespaces[0], liveHandler );
373 jQuery.Event = function( src ){
374 // Allow instantiation without the 'new' keyword
375 if( !this.preventDefault )
376 return new jQuery.Event(src);
379 if( src && src.type ){
380 this.originalEvent = src;
381 this.type = src.type;
386 // timeStamp is buggy for some events on Firefox(#3843)
387 // So we won't rely on the native value
388 this.timeStamp = now();
391 this[expando] = true;
394 function returnFalse(){
397 function returnTrue(){
401 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
402 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
403 jQuery.Event.prototype = {
404 preventDefault: function() {
405 this.isDefaultPrevented = returnTrue;
407 var e = this.originalEvent;
410 // if preventDefault exists run it on the original event
411 if (e.preventDefault)
413 // otherwise set the returnValue property of the original event to false (IE)
414 e.returnValue = false;
416 stopPropagation: function() {
417 this.isPropagationStopped = returnTrue;
419 var e = this.originalEvent;
422 // if stopPropagation exists run it on the original event
423 if (e.stopPropagation)
425 // otherwise set the cancelBubble property of the original event to true (IE)
426 e.cancelBubble = true;
428 stopImmediatePropagation:function(){
429 this.isImmediatePropagationStopped = returnTrue;
430 this.stopPropagation();
432 isDefaultPrevented: returnFalse,
433 isPropagationStopped: returnFalse,
434 isImmediatePropagationStopped: returnFalse
436 // Checks if an event happened on an element within another element
437 // Used in jQuery.event.special.mouseenter and mouseleave handlers
438 var withinElement = function(event) {
439 // Check if mouse(over|out) are still within the same parent element
440 var parent = event.relatedTarget;
441 // Traverse up the tree
442 while ( parent && parent != this )
443 try { parent = parent.parentNode; }
444 catch(e) { parent = this; }
446 if( parent != this ){
447 // set the correct event type
448 event.type = event.data;
449 // handle event if we actually just moused on to a non sub-element
450 jQuery.event.handle.apply( this, arguments );
455 mouseover: 'mouseenter',
456 mouseout: 'mouseleave'
457 }, function( orig, fix ){
458 jQuery.event.special[ fix ] = {
460 jQuery.event.add( this, orig, withinElement, fix );
462 teardown: function(){
463 jQuery.event.remove( this, orig, withinElement );
469 bind: function( type, data, fn ) {
470 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
471 jQuery.event.add( this, type, fn || data, fn && data );
475 one: function( type, data, fn ) {
476 var one = jQuery.event.proxy( fn || data, function(event) {
477 jQuery(this).unbind(event, one);
478 return (fn || data).apply( this, arguments );
480 return this.each(function(){
481 jQuery.event.add( this, type, one, fn && data);
485 unbind: function( type, fn ) {
486 return this.each(function(){
487 jQuery.event.remove( this, type, fn );
491 trigger: function( type, data ) {
492 return this.each(function(){
493 jQuery.event.trigger( type, data, this );
497 triggerHandler: function( type, data ) {
499 var event = jQuery.Event(type);
500 event.preventDefault();
501 event.stopPropagation();
502 jQuery.event.trigger( event, data, this[0] );
507 toggle: function( fn ) {
508 // Save reference to arguments for access in closure
509 var args = arguments, i = 1;
511 // link all the functions, so any of them can unbind this click handler
512 while( i < args.length )
513 jQuery.event.proxy( fn, args[i++] );
515 return this.click( jQuery.event.proxy( fn, function(event) {
516 // Figure out which function to execute
517 this.lastToggle = ( this.lastToggle || 0 ) % i;
519 // Make sure that clicks stop
520 event.preventDefault();
522 // and execute the function
523 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
527 hover: function(fnOver, fnOut) {
528 return this.mouseenter(fnOver).mouseleave(fnOut);
531 ready: function(fn) {
532 // Attach the listeners
535 // If the DOM is already ready
536 if ( jQuery.isReady )
537 // Execute the function immediately
538 fn.call( document, jQuery );
540 // Otherwise, remember the function for later
542 // Add the function to the wait list
543 jQuery.readyList.push( fn );
548 live: function( type, fn ){
549 var proxy = jQuery.event.proxy( fn );
550 proxy.guid += this.selector + type;
552 jQuery( this.context ).bind( liveConvert(type, this.selector), this.selector, proxy );
557 die: function( type, fn ){
558 jQuery( this.context ).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
563 function liveHandler( event ){
564 var check = new RegExp("(^|\\.)" + event.type + "(\\.|$)"),
568 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
569 if ( check.test(fn.type) ) {
570 var elem = jQuery(event.target).closest(fn.data)[0];
572 elems.push({ elem: elem, fn: fn });
576 elems.sort(function(a,b) {
577 return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
580 jQuery.each(elems, function(){
581 event.currentTarget = this.elem;
582 if ( this.fn.call(this.elem, event, this.fn.data) === false )
583 return (stop = false);
589 function liveConvert(type, selector){
590 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
596 // Handle when the DOM is ready
598 // Make sure that the DOM is not already loaded
599 if ( !jQuery.isReady ) {
600 // Remember that the DOM is ready
601 jQuery.isReady = true;
603 // If there are functions bound, to execute
604 if ( jQuery.readyList ) {
605 // Execute all of them
606 jQuery.each( jQuery.readyList, function(){
607 this.call( document, jQuery );
610 // Reset the list of functions
611 jQuery.readyList = null;
614 // Trigger any bound ready events
615 jQuery(document).triggerHandler("ready");
620 var readyBound = false;
622 function bindReady(){
623 if ( readyBound ) return;
626 // Mozilla, Opera and webkit nightlies currently support this event
627 if ( document.addEventListener ) {
628 // Use the handy event callback
629 document.addEventListener( "DOMContentLoaded", function(){
630 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
634 // If IE event model is used
635 } else if ( document.attachEvent ) {
636 // ensure firing before onload,
637 // maybe late but safe also for iframes
638 document.attachEvent("onreadystatechange", function(){
639 if ( document.readyState === "complete" ) {
640 document.detachEvent( "onreadystatechange", arguments.callee );
645 // If IE and not an iframe
646 // continually check to see if the document is ready
647 if ( document.documentElement.doScroll && window == window.top ) (function(){
648 if ( jQuery.isReady ) return;
651 // If IE is used, use the trick by Diego Perini
652 // http://javascript.nwbox.com/IEContentLoaded/
653 document.documentElement.doScroll("left");
655 setTimeout( arguments.callee, 0 );
659 // and execute any waiting functions
664 // A fallback to window.onload, that will always work
665 jQuery.event.add( window, "load", jQuery.ready );
668 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
669 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
670 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
672 // Handle event binding
673 jQuery.fn[name] = function(fn){
674 return fn ? this.bind(name, fn) : this.trigger(name);
678 // Prevent memory leaks in IE
679 // And prevent errors on refresh with events like mouseover in other browsers
680 // Window isn't included so as not to unbind existing unload events
682 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
683 // - https://bugzilla.mozilla.org/show_bug.cgi?id=252542
684 jQuery( window ).bind( 'unload', function(){
685 for ( var id in jQuery.cache )
687 if ( id != 1 && jQuery.cache[ id ].handle )
688 jQuery.event.remove( jQuery.cache[ id ].handle.elem );