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 )
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 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 jQuery.each(types.split(/\s+/), function(index, type) {
55 // Namespaced event handlers
56 var namespaces = type.split(".");
57 type = namespaces.shift();
58 handler.type = namespaces.slice().sort().join(".");
60 // Get the current list of functions bound to this event
61 var handlers = events[type];
63 if ( jQuery.event.specialAll[type] )
64 jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
66 // Init the event handler queue
68 handlers = events[type] = {};
70 // Check for a special event handler
71 // Only use addEventListener/attachEvent if the special
72 // events handler returns false
73 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
74 // Bind the global event handler to the element
75 if (elem.addEventListener)
76 elem.addEventListener(type, handle, false);
77 else if (elem.attachEvent)
78 elem.attachEvent("on" + type, handle);
82 // Add the function to the element's handler list
83 handlers[handler.guid] = handler;
85 // Keep track of which events have been used, for global triggering
86 jQuery.event.global[type] = true;
89 // Nullify elem to prevent memory leaks in IE
96 // Detach an event or set of events from an element
97 remove: function(elem, types, handler) {
98 // don't do events on text and comment nodes
99 if ( elem.nodeType == 3 || elem.nodeType == 8 )
102 var events = jQuery.data(elem, "events"), ret, index;
105 // Unbind all events for the element
106 if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
107 for ( var type in events )
108 this.remove( elem, type + (types || "") );
110 // types is actually an event object here
112 handler = types.handler;
116 // Handle multiple events seperated by a space
117 // jQuery(...).unbind("mouseover mouseout", fn);
118 jQuery.each(types.split(/\s+/), function(index, type){
119 // Namespaced event handlers
120 var namespaces = type.split(".");
121 type = namespaces.shift();
122 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
124 if ( events[type] ) {
125 // remove the given handler for the given type
127 delete events[type][handler.guid];
129 // remove all handlers for the given type
131 for ( var handle in events[type] )
132 // Handle the removal of namespaced events
133 if ( namespace.test(events[type][handle].type) )
134 delete events[type][handle];
136 if ( jQuery.event.specialAll[type] )
137 jQuery.event.specialAll[type].teardown.call(elem, namespaces);
139 // remove generic event handler if no more handlers exist
140 for ( ret in events[type] ) break;
142 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
143 if (elem.removeEventListener)
144 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
145 else if (elem.detachEvent)
146 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
155 // Remove the expando if it's no longer used
156 for ( ret in events ) break;
158 var handle = jQuery.data( elem, "handle" );
159 if ( handle ) handle.elem = null;
160 jQuery.removeData( elem, "events" );
161 jQuery.removeData( elem, "handle" );
166 // bubbling is internal
167 trigger: function( event, data, elem, bubbling ) {
168 // Event object or event type
169 var type = event.type || event;
172 event = typeof event === "object" ?
173 // jQuery.Event object
174 event[expando] ? event :
176 jQuery.extend( jQuery.Event(type), event ) :
177 // Just the event type (string)
180 if ( type.indexOf("!") >= 0 ) {
181 event.type = type = type.slice(0, -1);
182 event.exclusive = true;
185 // Handle a global trigger
187 // Don't bubble custom events when global (to avoid too much overhead)
188 event.stopPropagation();
189 // Only trigger if we've ever bound an event for it
190 if ( this.global[type] )
191 jQuery.each( jQuery.cache, function(){
192 if ( this.events && this.events[type] )
193 jQuery.event.trigger( event, data, this.handle.elem );
197 // Handle triggering a single element
199 // don't do events on text and comment nodes
200 if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
203 // Clean up in case it is reused
204 event.result = undefined;
207 // Clone the incoming data, if any
208 data = jQuery.makeArray(data);
209 data.unshift( event );
212 event.currentTarget = elem;
214 // Trigger the event, it is assumed that "handle" is a function
215 var handle = jQuery.data(elem, "handle");
217 handle.apply( elem, data );
219 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
220 if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
221 event.result = false;
223 // Trigger the native events (except for clicks on links)
224 if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
225 this.triggered = true;
228 // prevent IE from throwing an error for some hidden elements
232 this.triggered = false;
234 if ( !event.isPropagationStopped() ) {
235 var parent = elem.parentNode || elem.ownerDocument;
237 jQuery.event.trigger(event, data, parent, true);
241 handle: function(event) {
242 // returned undefined or false
245 event = arguments[0] = jQuery.event.fix( event || window.event );
247 // Namespaced event handlers
248 var namespaces = event.type.split(".");
249 event.type = namespaces.shift();
251 // Cache this now, all = true means, any handler
252 all = !namespaces.length && !event.exclusive;
254 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
256 handlers = ( jQuery.data(this, "events") || {} )[event.type];
258 for ( var j in handlers ) {
259 var handler = handlers[j];
261 // Filter the functions by class
262 if ( all || namespace.test(handler.type) ) {
263 // Pass in a reference to the handler function itself
264 // So that we can later remove it
265 event.handler = handler;
266 event.data = handler.data;
268 var ret = handler.apply(this, arguments);
270 if( ret !== undefined ){
272 if ( ret === false ) {
273 event.preventDefault();
274 event.stopPropagation();
278 if( event.isImmediatePropagationStopped() )
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 toElement 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 = jQuery.Event( originalEvent );
296 for ( var i = this.props.length, prop; i; ){
297 prop = this.props[ --i ];
298 event[ prop ] = originalEvent[ prop ];
301 // Fix target property, if necessary
303 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
305 // check if target is a textnode (safari)
306 if ( event.target.nodeType == 3 )
307 event.target = event.target.parentNode;
309 // Add relatedTarget, if necessary
310 if ( !event.relatedTarget && event.fromElement )
311 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
313 // Calculate pageX/Y if missing and clientX/Y available
314 if ( event.pageX == null && event.clientX != null ) {
315 var doc = document.documentElement, body = document.body;
316 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
317 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
320 // Add which for key events
321 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
322 event.which = event.charCode || event.keyCode;
324 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
325 if ( !event.metaKey && event.ctrlKey )
326 event.metaKey = event.ctrlKey;
328 // Add which for click: 1 == left; 2 == middle; 3 == right
329 // Note: button is not normalized, so don't use it
330 if ( !event.which && event.button )
331 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
336 proxy: function( fn, proxy ){
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 = 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;
382 this.timeStamp = src.timeStamp;
387 if( !this.timeStamp )
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 jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
553 die: function( type, fn ){
554 jQuery(document).unbind( liveConvert(type, this.selector), fn );
559 function liveHandler( event ){
560 var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
563 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
564 if ( !event.isImmediatePropagationStopped() && check.test(fn.type) ) {
565 var elem = jQuery(event.target).closest(fn.data)[0];
566 if ( elem && fn.call(elem, event, fn.data) === false )
573 function liveConvert(type, selector){
574 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
580 // Handle when the DOM is ready
582 // Make sure that the DOM is not already loaded
583 if ( !jQuery.isReady ) {
584 // Remember that the DOM is ready
585 jQuery.isReady = true;
587 // If there are functions bound, to execute
588 if ( jQuery.readyList ) {
589 // Execute all of them
590 jQuery.each( jQuery.readyList, function(){
591 this.call( document, jQuery );
594 // Reset the list of functions
595 jQuery.readyList = null;
598 // Trigger any bound ready events
599 jQuery(document).triggerHandler("ready");
604 var readyBound = false;
606 function bindReady(){
607 if ( readyBound ) return;
610 // Mozilla, Opera and webkit nightlies currently support this event
611 if ( document.addEventListener ) {
612 // Use the handy event callback
613 document.addEventListener( "DOMContentLoaded", function(){
614 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
618 // If IE event model is used
619 } else if ( document.attachEvent ) {
620 // ensure firing before onload,
621 // maybe late but safe also for iframes
622 document.attachEvent("onreadystatechange", function(){
623 if ( document.readyState === "complete" ) {
624 document.detachEvent( "onreadystatechange", arguments.callee );
629 // If IE and not an iframe
630 // continually check to see if the document is ready
631 if ( document.documentElement.doScroll && !window.frameElement ) (function(){
632 if ( jQuery.isReady ) return;
635 // If IE is used, use the trick by Diego Perini
636 // http://javascript.nwbox.com/IEContentLoaded/
637 document.documentElement.doScroll("left");
639 setTimeout( arguments.callee, 0 );
643 // and execute any waiting functions
648 // A fallback to window.onload, that will always work
649 jQuery.event.add( window, "load", jQuery.ready );
652 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
653 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
654 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
656 // Handle event binding
657 jQuery.fn[name] = function(fn){
658 return fn ? this.bind(name, fn) : this.trigger(name);
662 // Prevent memory leaks in IE
663 // And prevent errors on refresh with events like mouseover in other browsers
664 // Window isn't included so as not to unbind existing unload events
665 jQuery( window ).bind( 'unload', function(){
666 for ( var id in jQuery.cache )
668 if ( id != 1 && jQuery.cache[ id ].handle )
669 jQuery.event.remove( jQuery.cache[ id ].handle.elem );