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 ( handler in events[type] )
132 // Handle the removal of namespaced events
133 if ( namespace.test(events[type][handler].type) )
134 delete events[type][handler];
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 trigger: function( event, data, elem, bubbling /* internal */ ) {
167 // Event object or event type
168 var type = event.type || event;
171 event = typeof event === "object" ?
172 // jQuery.Event object
173 event[expando] ? event :
175 jQuery.extend( jQuery.Event(type), event ) :
176 // Just the event type (string)
179 if ( type.indexOf("!") >= 0 ) {
180 event.type = type = type.slice(0, -1);
181 event.exclusive = true;
184 // Handle a global trigger
186 // Don't bubble custom events when global (to avoid too much overhead)
187 event.stopPropagation();
188 // Only trigger if we've ever bound an event for it
189 if ( this.global[type] )
190 jQuery.each( jQuery.cache, function(){
191 if ( this.events && this.events[type] )
192 jQuery.event.trigger( event, data, this.handle.elem );
196 // Handle triggering a single element
198 // don't do events on text and comment nodes
199 if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
202 // AT_TARGET phase (not bubbling)
204 // Clean up in case it is reused
205 event.result = undefined;
208 // Clone the incoming data, if any
209 data = jQuery.makeArray(data);
210 data.unshift( event );
214 event.currentTarget = elem;
216 var fn = jQuery.isFunction( elem[ type ] );
218 // Trigger the event, it is assumed that "handle" is a function
219 var handle = jQuery.data(elem, "handle");
221 handle.apply( elem, data );
223 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
224 if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
225 event.result = false;
229 // Trigger the native events (except for clicks on links)
230 if ( !bubbling && fn && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
231 this.triggered = true;
234 // prevent IE from throwing an error for some hidden elements
238 if ( !event.isPropagationStopped() ) {
239 var parent = elem.parentNode || elem.ownerDocument;
241 jQuery.event.trigger(event, data, parent, true);
244 this.triggered = false;
247 handle: function(event) {
248 // returned undefined or false
251 event = arguments[0] = jQuery.event.fix( event || window.event );
253 // Namespaced event handlers
254 var namespaces = event.type.split(".");
255 event.type = namespaces.shift();
257 // Cache this now, all = true means, any handler
258 all = !namespaces.length && !event.exclusive;
260 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
262 handlers = ( jQuery.data(this, "events") || {} )[event.type];
264 for ( var j in handlers ) {
265 var handler = handlers[j];
267 // Filter the functions by class
268 if ( all || namespace.test(handler.type) ) {
269 // Pass in a reference to the handler function itself
270 // So that we can later remove it
271 event.handler = handler;
272 event.data = handler.data;
274 var ret = handler.apply(this, arguments);
276 if( ret !== undefined ){
278 if ( ret === false ) {
279 event.preventDefault();
280 event.stopPropagation();
284 if( event.isImmediatePropagationStopped() )
291 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(" "),
293 fix: function(event) {
294 if ( event[expando] )
297 // store a copy of the original event object
298 // and "clone" to set read-only properties
299 var originalEvent = event;
300 event = jQuery.Event( originalEvent );
302 for ( var i = this.props.length, prop; i; ){
303 prop = this.props[ --i ];
304 event[ prop ] = originalEvent[ prop ];
307 // Fix target property, if necessary
309 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
311 // check if target is a textnode (safari)
312 if ( event.target.nodeType == 3 )
313 event.target = event.target.parentNode;
315 // Add relatedTarget, if necessary
316 if ( !event.relatedTarget && event.fromElement )
317 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
319 // Calculate pageX/Y if missing and clientX/Y available
320 if ( event.pageX == null && event.clientX != null ) {
321 var doc = document.documentElement, body = document.body;
322 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
323 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
326 // Add which for key events
327 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
328 event.which = event.charCode || event.keyCode;
330 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
331 if ( !event.metaKey && event.ctrlKey )
332 event.metaKey = event.ctrlKey;
334 // Add which for click: 1 == left; 2 == middle; 3 == right
335 // Note: button is not normalized, so don't use it
336 if ( !event.which && event.button )
337 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
342 proxy: function( fn, proxy ){
343 // Set the guid of unique handler to the same of original handler, so it can be removed
344 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
345 // So proxy can be declared as an argument
351 // Make sure the ready event is setup
353 teardown: function() {}
359 setup: function( selector, namespaces ){
360 jQuery.event.add( this, namespaces[0], liveHandler );
362 teardown: function( namespaces ){
363 if ( namespaces.length ) {
364 var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
366 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
367 if ( name.test(this.type) )
372 jQuery.event.remove( this, namespaces[0], liveHandler );
379 jQuery.Event = function( src ){
380 // Allow instantiation without the 'new' keyword
381 if( !this.preventDefault )
382 return new jQuery.Event(src);
385 if( src && src.type ){
386 this.originalEvent = src;
387 this.type = src.type;
388 this.timeStamp = src.timeStamp;
393 if( !this.timeStamp )
394 this.timeStamp = now();
397 this[expando] = true;
400 function returnFalse(){
403 function returnTrue(){
407 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
408 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
409 jQuery.Event.prototype = {
410 preventDefault: function() {
411 this.isDefaultPrevented = returnTrue;
413 var e = this.originalEvent;
416 // if preventDefault exists run it on the original event
417 if (e.preventDefault)
419 // otherwise set the returnValue property of the original event to false (IE)
420 e.returnValue = false;
422 stopPropagation: function() {
423 this.isPropagationStopped = returnTrue;
425 var e = this.originalEvent;
428 // if stopPropagation exists run it on the original event
429 if (e.stopPropagation)
431 // otherwise set the cancelBubble property of the original event to true (IE)
432 e.cancelBubble = true;
434 stopImmediatePropagation:function(){
435 this.isImmediatePropagationStopped = returnTrue;
436 this.stopPropagation();
438 isDefaultPrevented: returnFalse,
439 isPropagationStopped: returnFalse,
440 isImmediatePropagationStopped: returnFalse
442 // Checks if an event happened on an element within another element
443 // Used in jQuery.event.special.mouseenter and mouseleave handlers
444 var withinElement = function(event) {
445 // Check if mouse(over|out) are still within the same parent element
446 var parent = event.relatedTarget;
447 // Traverse up the tree
448 while ( parent && parent != this )
449 try { parent = parent.parentNode; }
450 catch(e) { parent = this; }
452 if( parent != this ){
453 // set the correct event type
454 event.type = event.data;
455 // handle event if we actually just moused on to a non sub-element
456 jQuery.event.handle.apply( this, arguments );
461 mouseover: 'mouseenter',
462 mouseout: 'mouseleave'
463 }, function( orig, fix ){
464 jQuery.event.special[ fix ] = {
466 jQuery.event.add( this, orig, withinElement, fix );
468 teardown: function(){
469 jQuery.event.remove( this, orig, withinElement );
475 bind: function( type, data, fn ) {
476 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
477 jQuery.event.add( this, type, fn || data, fn && data );
481 one: function( type, data, fn ) {
482 var one = jQuery.event.proxy( fn || data, function(event) {
483 jQuery(this).unbind(event, one);
484 return (fn || data).apply( this, arguments );
486 return this.each(function(){
487 jQuery.event.add( this, type, one, fn && data);
491 unbind: function( type, fn ) {
492 return this.each(function(){
493 jQuery.event.remove( this, type, fn );
497 trigger: function( type, data ) {
498 return this.each(function(){
499 jQuery.event.trigger( type, data, this );
503 triggerHandler: function( type, data ) {
505 var event = jQuery.Event(type);
506 event.preventDefault();
507 event.stopPropagation();
508 jQuery.event.trigger( event, data, this[0] );
513 toggle: function( fn ) {
514 // Save reference to arguments for access in closure
515 var args = arguments, i = 1;
517 // link all the functions, so any of them can unbind this click handler
518 while( i < args.length )
519 jQuery.event.proxy( fn, args[i++] );
521 return this.click( jQuery.event.proxy( fn, function(event) {
522 // Figure out which function to execute
523 this.lastToggle = ( this.lastToggle || 0 ) % i;
525 // Make sure that clicks stop
526 event.preventDefault();
528 // and execute the function
529 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
533 hover: function(fnOver, fnOut) {
534 return this.mouseenter(fnOver).mouseleave(fnOut);
537 ready: function(fn) {
538 // Attach the listeners
541 // If the DOM is already ready
542 if ( jQuery.isReady )
543 // Execute the function immediately
544 fn.call( document, jQuery );
546 // Otherwise, remember the function for later
548 // Add the function to the wait list
549 jQuery.readyList.push( fn );
554 live: function( type, fn ){
555 jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
559 die: function( type, fn ){
560 jQuery(document).unbind( liveConvert(type, this.selector), fn );
565 function liveHandler( event ){
566 var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
569 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
570 if ( check.test(fn.type) ) {
571 var elem = jQuery(event.target).closest(fn.data)[0];
572 if ( elem && fn.call(elem, event, fn.data) === false )
579 function liveConvert(type, selector){
580 return ["live", type, selector.replace(/\./g, "_")].join(".");
586 // Handle when the DOM is ready
588 // Make sure that the DOM is not already loaded
589 if ( !jQuery.isReady ) {
590 // Remember that the DOM is ready
591 jQuery.isReady = true;
593 // If there are functions bound, to execute
594 if ( jQuery.readyList ) {
595 // Execute all of them
596 jQuery.each( jQuery.readyList, function(){
597 this.call( document, jQuery );
600 // Reset the list of functions
601 jQuery.readyList = null;
604 // Trigger any bound ready events
605 jQuery(document).triggerHandler("ready");
610 var readyBound = false;
612 function bindReady(){
613 if ( readyBound ) return;
616 // Mozilla, Opera and webkit nightlies currently support this event
617 if ( document.addEventListener ) {
618 // Use the handy event callback
619 document.addEventListener( "DOMContentLoaded", function(){
620 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
624 // If IE event model is used
625 } else if ( document.attachEvent ) {
626 // ensure firing before onload,
627 // maybe late but safe also for iframes
628 document.attachEvent("onreadystatechange", function(){
629 if ( document.readyState === "complete" ) {
630 document.detachEvent( "onreadystatechange", arguments.callee );
635 // If IE and not an iframe
636 // continually check to see if the document is ready
637 if ( document.documentElement.doScroll && !window.frameElement ) (function(){
638 if ( jQuery.isReady ) return;
641 // If IE is used, use the trick by Diego Perini
642 // http://javascript.nwbox.com/IEContentLoaded/
643 document.documentElement.doScroll("left");
645 setTimeout( arguments.callee, 0 );
649 // and execute any waiting functions
654 // A fallback to window.onload, that will always work
655 jQuery.event.add( window, "load", jQuery.ready );
658 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
659 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
660 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
662 // Handle event binding
663 jQuery.fn[name] = function(fn){
664 return fn ? this.bind(name, fn) : this.trigger(name);
668 // Prevent memory leaks in IE
669 // And prevent errors on refresh with events like mouseover in other browsers
670 // Window isn't included so as not to unbind existing unload events
671 jQuery( window ).bind( 'unload', function(){
672 for ( var id in jQuery.cache )
674 if ( id != 1 && jQuery.cache[ id ].handle )
675 jQuery.event.remove( jQuery.cache[ id ].handle.elem );