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 namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
121 if ( events[type] ) {
122 // remove the given handler for the given type
124 delete events[type][handler.guid];
126 // remove all handlers for the given type
128 for ( var handle in events[type] )
129 // Handle the removal of namespaced events
130 if ( namespace.test(events[type][handle].type) )
131 delete events[type][handle];
133 if ( jQuery.event.specialAll[type] )
134 jQuery.event.specialAll[type].teardown.call(elem, namespaces);
136 // remove generic event handler if no more handlers exist
137 for ( ret in events[type] ) break;
139 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
140 if (elem.removeEventListener)
141 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
142 else if (elem.detachEvent)
143 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
152 // Remove the expando if it's no longer used
153 for ( ret in events ) break;
155 var handle = jQuery.data( elem, "handle" );
156 if ( handle ) handle.elem = null;
157 jQuery.removeData( elem, "events" );
158 jQuery.removeData( elem, "handle" );
163 // bubbling is internal
164 trigger: function( event, data, elem, bubbling ) {
165 // Event object or event type
166 var type = event.type || event;
169 event = typeof event === "object" ?
170 // jQuery.Event object
171 event[expando] ? event :
173 jQuery.extend( jQuery.Event(type), event ) :
174 // Just the event type (string)
177 if ( type.indexOf("!") >= 0 ) {
178 event.type = type = type.slice(0, -1);
179 event.exclusive = true;
182 // Handle a global trigger
184 // Don't bubble custom events when global (to avoid too much overhead)
185 event.stopPropagation();
186 // Only trigger if we've ever bound an event for it
187 if ( this.global[type] )
188 jQuery.each( jQuery.cache, function(){
189 if ( this.events && this.events[type] )
190 jQuery.event.trigger( event, data, this.handle.elem );
194 // Handle triggering a single element
196 // don't do events on text and comment nodes
197 if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
200 // Clean up in case it is reused
201 event.result = undefined;
204 // Clone the incoming data, if any
205 data = jQuery.makeArray(data);
206 data.unshift( event );
209 event.currentTarget = elem;
211 // Trigger the event, it is assumed that "handle" is a function
212 var handle = jQuery.data(elem, "handle");
214 handle.apply( elem, data );
216 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
217 if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
218 event.result = false;
220 // Trigger the native events (except for clicks on links)
221 if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
222 this.triggered = true;
225 // prevent IE from throwing an error for some hidden elements
229 this.triggered = false;
231 if ( !event.isPropagationStopped() ) {
232 var parent = elem.parentNode || elem.ownerDocument;
234 jQuery.event.trigger(event, data, parent, true);
238 handle: function(event) {
239 // returned undefined or false
242 event = arguments[0] = jQuery.event.fix( event || window.event );
243 event.currentTarget = this;
245 // Namespaced event handlers
246 var namespaces = event.type.split(".");
247 event.type = namespaces.shift();
249 // Cache this now, all = true means, any handler
250 all = !namespaces.length && !event.exclusive;
252 var namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
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 || namespace.test(handler.type) ) {
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 var ret = handler.apply(this, arguments);
268 if( ret !== undefined ){
270 if ( ret === false ) {
271 event.preventDefault();
272 event.stopPropagation();
276 if( event.isImmediatePropagationStopped() )
283 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(" "),
285 fix: function(event) {
286 if ( event[expando] )
289 // store a copy of the original event object
290 // and "clone" to set read-only properties
291 var originalEvent = event;
292 event = jQuery.Event( originalEvent );
294 for ( var i = this.props.length, prop; i; ){
295 prop = this.props[ --i ];
296 event[ prop ] = originalEvent[ prop ];
299 // Fix target property, if necessary
301 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
303 // check if target is a textnode (safari)
304 if ( event.target.nodeType == 3 )
305 event.target = event.target.parentNode;
307 // Add relatedTarget, if necessary
308 if ( !event.relatedTarget && event.fromElement )
309 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
311 // Calculate pageX/Y if missing and clientX/Y available
312 if ( event.pageX == null && event.clientX != null ) {
313 var doc = document.documentElement, body = document.body;
314 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
315 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
318 // Add which for key events
319 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
320 event.which = event.charCode || event.keyCode;
322 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
323 if ( !event.metaKey && event.ctrlKey )
324 event.metaKey = event.ctrlKey;
326 // Add which for click: 1 == left; 2 == middle; 3 == right
327 // Note: button is not normalized, so don't use it
328 if ( !event.which && event.button )
329 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
334 proxy: function( fn, proxy ){
335 proxy = proxy || function(){ return fn.apply(this, arguments); };
336 // Set the guid of unique handler to the same of original handler, so it can be removed
337 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
338 // So proxy can be declared as an argument
344 // Make sure the ready event is setup
346 teardown: function() {}
352 setup: function( selector, namespaces ){
353 jQuery.event.add( this, namespaces[0], liveHandler );
355 teardown: function( namespaces ){
356 if ( namespaces.length ) {
357 var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
359 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
360 if ( name.test(this.type) )
365 jQuery.event.remove( this, namespaces[0], liveHandler );
372 jQuery.Event = function( src ){
373 // Allow instantiation without the 'new' keyword
374 if( !this.preventDefault )
375 return new jQuery.Event(src);
378 if( src && src.type ){
379 this.originalEvent = src;
380 this.type = src.type;
385 // timeStamp is buggy for some events on Firefox(#3843)
386 // So we won't rely on the native value
387 this.timeStamp = now();
390 this[expando] = true;
393 function returnFalse(){
396 function returnTrue(){
400 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
401 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
402 jQuery.Event.prototype = {
403 preventDefault: function() {
404 this.isDefaultPrevented = returnTrue;
406 var e = this.originalEvent;
409 // if preventDefault exists run it on the original event
410 if (e.preventDefault)
412 // otherwise set the returnValue property of the original event to false (IE)
413 e.returnValue = false;
415 stopPropagation: function() {
416 this.isPropagationStopped = returnTrue;
418 var e = this.originalEvent;
421 // if stopPropagation exists run it on the original event
422 if (e.stopPropagation)
424 // otherwise set the cancelBubble property of the original event to true (IE)
425 e.cancelBubble = true;
427 stopImmediatePropagation:function(){
428 this.isImmediatePropagationStopped = returnTrue;
429 this.stopPropagation();
431 isDefaultPrevented: returnFalse,
432 isPropagationStopped: returnFalse,
433 isImmediatePropagationStopped: returnFalse
435 // Checks if an event happened on an element within another element
436 // Used in jQuery.event.special.mouseenter and mouseleave handlers
437 var withinElement = function(event) {
438 // Check if mouse(over|out) are still within the same parent element
439 var parent = event.relatedTarget;
440 // Traverse up the tree
441 while ( parent && parent != this )
442 try { parent = parent.parentNode; }
443 catch(e) { parent = this; }
445 if( parent != this ){
446 // set the correct event type
447 event.type = event.data;
448 // handle event if we actually just moused on to a non sub-element
449 jQuery.event.handle.apply( this, arguments );
454 mouseover: 'mouseenter',
455 mouseout: 'mouseleave'
456 }, function( orig, fix ){
457 jQuery.event.special[ fix ] = {
459 jQuery.event.add( this, orig, withinElement, fix );
461 teardown: function(){
462 jQuery.event.remove( this, orig, withinElement );
468 bind: function( type, data, fn ) {
469 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
470 jQuery.event.add( this, type, fn || data, fn && data );
474 one: function( type, data, fn ) {
475 var one = jQuery.event.proxy( fn || data, function(event) {
476 jQuery(this).unbind(event, one);
477 return (fn || data).apply( this, arguments );
479 return this.each(function(){
480 jQuery.event.add( this, type, one, fn && data);
484 unbind: function( type, fn ) {
485 return this.each(function(){
486 jQuery.event.remove( this, type, fn );
490 trigger: function( type, data ) {
491 return this.each(function(){
492 jQuery.event.trigger( type, data, this );
496 triggerHandler: function( type, data ) {
498 var event = jQuery.Event(type);
499 event.preventDefault();
500 event.stopPropagation();
501 jQuery.event.trigger( event, data, this[0] );
506 toggle: function( fn ) {
507 // Save reference to arguments for access in closure
508 var args = arguments, i = 1;
510 // link all the functions, so any of them can unbind this click handler
511 while( i < args.length )
512 jQuery.event.proxy( fn, args[i++] );
514 return this.click( jQuery.event.proxy( fn, function(event) {
515 // Figure out which function to execute
516 this.lastToggle = ( this.lastToggle || 0 ) % i;
518 // Make sure that clicks stop
519 event.preventDefault();
521 // and execute the function
522 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
526 hover: function(fnOver, fnOut) {
527 return this.mouseenter(fnOver).mouseleave(fnOut);
530 ready: function(fn) {
531 // Attach the listeners
534 // If the DOM is already ready
535 if ( jQuery.isReady )
536 // Execute the function immediately
537 fn.call( document, jQuery );
539 // Otherwise, remember the function for later
541 // Add the function to the wait list
542 jQuery.readyList.push( fn );
547 live: function( type, fn ){
548 var proxy = jQuery.event.proxy( fn );
549 proxy.guid += this.selector + type;
551 jQuery( this.context ).bind( liveConvert(type, this.selector), this.selector, proxy );
556 die: function( type, fn ){
557 jQuery( this.context ).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
562 function liveHandler( event ){
563 var check = new RegExp("(^|\\.)" + event.type + "(\\.|$)"),
567 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
568 if ( check.test(fn.type) ) {
569 var elem = jQuery(event.target).closest(fn.data)[0];
571 elems.push({ elem: elem, fn: fn });
575 elems.sort(function(a,b) {
576 return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
579 jQuery.each(elems, function(){
580 event.currentTarget = this.elem;
581 if ( this.fn.call(this.elem, event, this.fn.data) === false )
582 return (stop = false);
588 function liveConvert(type, selector){
589 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
595 // Handle when the DOM is ready
597 // Make sure that the DOM is not already loaded
598 if ( !jQuery.isReady ) {
599 // Remember that the DOM is ready
600 jQuery.isReady = true;
602 // If there are functions bound, to execute
603 if ( jQuery.readyList ) {
604 // Execute all of them
605 jQuery.each( jQuery.readyList, function(){
606 this.call( document, jQuery );
609 // Reset the list of functions
610 jQuery.readyList = null;
613 // Trigger any bound ready events
614 jQuery(document).triggerHandler("ready");
619 var readyBound = false;
621 function bindReady(){
622 if ( readyBound ) return;
625 // Mozilla, Opera and webkit nightlies currently support this event
626 if ( document.addEventListener ) {
627 // Use the handy event callback
628 document.addEventListener( "DOMContentLoaded", function(){
629 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
633 // If IE event model is used
634 } else if ( document.attachEvent ) {
635 // ensure firing before onload,
636 // maybe late but safe also for iframes
637 document.attachEvent("onreadystatechange", function(){
638 if ( document.readyState === "complete" ) {
639 document.detachEvent( "onreadystatechange", arguments.callee );
644 // If IE and not an iframe
645 // continually check to see if the document is ready
646 if ( document.documentElement.doScroll && window == window.top ) (function(){
647 if ( jQuery.isReady ) return;
650 // If IE is used, use the trick by Diego Perini
651 // http://javascript.nwbox.com/IEContentLoaded/
652 document.documentElement.doScroll("left");
654 setTimeout( arguments.callee, 0 );
658 // and execute any waiting functions
663 // A fallback to window.onload, that will always work
664 jQuery.event.add( window, "load", jQuery.ready );
667 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
668 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
669 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
671 // Handle event binding
672 jQuery.fn[name] = function(fn){
673 return fn ? this.bind(name, fn) : this.trigger(name);
677 // Prevent memory leaks in IE
678 // And prevent errors on refresh with events like mouseover in other browsers
679 // Window isn't included so as not to unbind existing unload events
681 // - http://isaacschlueter.com/2006/10/msie-memory-leaks/
682 // - https://bugzilla.mozilla.org/show_bug.cgi?id=252542
683 jQuery( window ).bind( 'unload', function(){
684 for ( var id in jQuery.cache )
686 if ( id != 1 && jQuery.cache[ id ].handle )
687 jQuery.event.remove( jQuery.cache[ id ].handle.elem );