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 // Trigger the event, it is assumed that "handle" is a function
217 var handle = jQuery.data(elem, "handle");
219 handle.apply( elem, data );
221 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
222 if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
223 event.result = false;
225 // Trigger the native events (except for clicks on links)
226 if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
227 this.triggered = true;
230 // prevent IE from throwing an error for some hidden elements
234 if ( !event.isPropagationStopped() ) {
235 var parent = elem.parentNode || elem.ownerDocument;
237 jQuery.event.trigger(event, data, parent, true);
240 this.triggered = false;
243 handle: function(event) {
244 // returned undefined or false
247 event = arguments[0] = jQuery.event.fix( event || window.event );
249 // Namespaced event handlers
250 var namespaces = event.type.split(".");
251 event.type = namespaces.shift();
253 // Cache this now, all = true means, any handler
254 all = !namespaces.length && !event.exclusive;
256 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
258 handlers = ( jQuery.data(this, "events") || {} )[event.type];
260 for ( var j in handlers ) {
261 var handler = handlers[j];
263 // Filter the functions by class
264 if ( all || namespace.test(handler.type) ) {
265 // Pass in a reference to the handler function itself
266 // So that we can later remove it
267 event.handler = handler;
268 event.data = handler.data;
270 var ret = handler.apply(this, arguments);
272 if( ret !== undefined ){
274 if ( ret === false ) {
275 event.preventDefault();
276 event.stopPropagation();
280 if( event.isImmediatePropagationStopped() )
287 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(" "),
289 fix: function(event) {
290 if ( event[expando] )
293 // store a copy of the original event object
294 // and "clone" to set read-only properties
295 var originalEvent = event;
296 event = jQuery.Event( originalEvent );
298 for ( var i = this.props.length, prop; i; ){
299 prop = this.props[ --i ];
300 event[ prop ] = originalEvent[ prop ];
303 // Fix target property, if necessary
305 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
307 // check if target is a textnode (safari)
308 if ( event.target.nodeType == 3 )
309 event.target = event.target.parentNode;
311 // Add relatedTarget, if necessary
312 if ( !event.relatedTarget && event.fromElement )
313 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
315 // Calculate pageX/Y if missing and clientX/Y available
316 if ( event.pageX == null && event.clientX != null ) {
317 var doc = document.documentElement, body = document.body;
318 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
319 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
322 // Add which for key events
323 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
324 event.which = event.charCode || event.keyCode;
326 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
327 if ( !event.metaKey && event.ctrlKey )
328 event.metaKey = event.ctrlKey;
330 // Add which for click: 1 == left; 2 == middle; 3 == right
331 // Note: button is not normalized, so don't use it
332 if ( !event.which && event.button )
333 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
338 proxy: function( fn, proxy ){
339 // Set the guid of unique handler to the same of original handler, so it can be removed
340 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
341 // So proxy can be declared as an argument
347 // Make sure the ready event is setup
349 teardown: function() {}
355 setup: function( selector, namespaces ){
356 jQuery.event.add( this, namespaces[0], liveHandler );
358 teardown: function( namespaces ){
359 if ( namespaces.length ) {
360 var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
362 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
363 if ( name.test(this.type) )
368 jQuery.event.remove( this, namespaces[0], liveHandler );
375 jQuery.Event = function( src ){
376 // Allow instantiation without the 'new' keyword
377 if( !this.preventDefault )
378 return new jQuery.Event(src);
381 if( src && src.type ){
382 this.originalEvent = src;
383 this.type = src.type;
384 this.timeStamp = src.timeStamp;
389 if( !this.timeStamp )
390 this.timeStamp = now();
393 this[expando] = true;
396 function returnFalse(){
399 function returnTrue(){
403 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
404 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
405 jQuery.Event.prototype = {
406 preventDefault: function() {
407 this.isDefaultPrevented = returnTrue;
409 var e = this.originalEvent;
412 // if preventDefault exists run it on the original event
413 if (e.preventDefault)
415 // otherwise set the returnValue property of the original event to false (IE)
416 e.returnValue = false;
418 stopPropagation: function() {
419 this.isPropagationStopped = returnTrue;
421 var e = this.originalEvent;
424 // if stopPropagation exists run it on the original event
425 if (e.stopPropagation)
427 // otherwise set the cancelBubble property of the original event to true (IE)
428 e.cancelBubble = true;
430 stopImmediatePropagation:function(){
431 this.isImmediatePropagationStopped = returnTrue;
432 this.stopPropagation();
434 isDefaultPrevented: returnFalse,
435 isPropagationStopped: returnFalse,
436 isImmediatePropagationStopped: returnFalse
438 // Checks if an event happened on an element within another element
439 // Used in jQuery.event.special.mouseenter and mouseleave handlers
440 var withinElement = function(event) {
441 // Check if mouse(over|out) are still within the same parent element
442 var parent = event.relatedTarget;
443 // Traverse up the tree
444 while ( parent && parent != this )
445 try { parent = parent.parentNode; }
446 catch(e) { parent = this; }
448 if( parent != this ){
449 // set the correct event type
450 event.type = event.data;
451 // handle event if we actually just moused on to a non sub-element
452 jQuery.event.handle.apply( this, arguments );
457 mouseover: 'mouseenter',
458 mouseout: 'mouseleave'
459 }, function( orig, fix ){
460 jQuery.event.special[ fix ] = {
462 jQuery.event.add( this, orig, withinElement, fix );
464 teardown: function(){
465 jQuery.event.remove( this, orig, withinElement );
471 bind: function( type, data, fn ) {
472 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
473 jQuery.event.add( this, type, fn || data, fn && data );
477 one: function( type, data, fn ) {
478 var one = jQuery.event.proxy( fn || data, function(event) {
479 jQuery(this).unbind(event, one);
480 return (fn || data).apply( this, arguments );
482 return this.each(function(){
483 jQuery.event.add( this, type, one, fn && data);
487 unbind: function( type, fn ) {
488 return this.each(function(){
489 jQuery.event.remove( this, type, fn );
493 trigger: function( type, data ) {
494 return this.each(function(){
495 jQuery.event.trigger( type, data, this );
499 triggerHandler: function( type, data ) {
501 var event = jQuery.Event(type);
502 event.preventDefault();
503 event.stopPropagation();
504 jQuery.event.trigger( event, data, this[0] );
509 toggle: function( fn ) {
510 // Save reference to arguments for access in closure
511 var args = arguments, i = 1;
513 // link all the functions, so any of them can unbind this click handler
514 while( i < args.length )
515 jQuery.event.proxy( fn, args[i++] );
517 return this.click( jQuery.event.proxy( fn, function(event) {
518 // Figure out which function to execute
519 this.lastToggle = ( this.lastToggle || 0 ) % i;
521 // Make sure that clicks stop
522 event.preventDefault();
524 // and execute the function
525 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
529 hover: function(fnOver, fnOut) {
530 return this.mouseenter(fnOver).mouseleave(fnOut);
533 ready: function(fn) {
534 // Attach the listeners
537 // If the DOM is already ready
538 if ( jQuery.isReady )
539 // Execute the function immediately
540 fn.call( document, jQuery );
542 // Otherwise, remember the function for later
544 // Add the function to the wait list
545 jQuery.readyList.push( fn );
550 live: function( type, fn ){
551 jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
555 die: function( type, fn ){
556 jQuery(document).unbind( liveConvert(type, this.selector), fn );
561 function liveHandler( event ){
562 var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
565 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
566 if ( check.test(fn.type) ) {
567 var elem = jQuery(event.target).closest(fn.data)[0];
568 if ( elem && fn.call(elem, event, fn.data) === false )
575 function liveConvert(type, selector){
576 return ["live", type, selector.replace(/\./g, "_")].join(".");
582 // Handle when the DOM is ready
584 // Make sure that the DOM is not already loaded
585 if ( !jQuery.isReady ) {
586 // Remember that the DOM is ready
587 jQuery.isReady = true;
589 // If there are functions bound, to execute
590 if ( jQuery.readyList ) {
591 // Execute all of them
592 jQuery.each( jQuery.readyList, function(){
593 this.call( document, jQuery );
596 // Reset the list of functions
597 jQuery.readyList = null;
600 // Trigger any bound ready events
601 jQuery(document).triggerHandler("ready");
606 var readyBound = false;
608 function bindReady(){
609 if ( readyBound ) return;
612 // Mozilla, Opera and webkit nightlies currently support this event
613 if ( document.addEventListener ) {
614 // Use the handy event callback
615 document.addEventListener( "DOMContentLoaded", function(){
616 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
620 // If IE event model is used
621 } else if ( document.attachEvent ) {
622 // ensure firing before onload,
623 // maybe late but safe also for iframes
624 document.attachEvent("onreadystatechange", function(){
625 if ( document.readyState === "complete" ) {
626 document.detachEvent( "onreadystatechange", arguments.callee );
631 // If IE and not an iframe
632 // continually check to see if the document is ready
633 if ( document.documentElement.doScroll && !window.frameElement ) (function(){
634 if ( jQuery.isReady ) return;
637 // If IE is used, use the trick by Diego Perini
638 // http://javascript.nwbox.com/IEContentLoaded/
639 document.documentElement.doScroll("left");
641 setTimeout( arguments.callee, 0 );
645 // and execute any waiting functions
650 // A fallback to window.onload, that will always work
651 jQuery.event.add( window, "load", jQuery.ready );
654 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
655 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
656 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
658 // Handle event binding
659 jQuery.fn[name] = function(fn){
660 return fn ? this.bind(name, fn) : this.trigger(name);
664 // Prevent memory leaks in IE
665 // And prevent errors on refresh with events like mouseover in other browsers
666 // Window isn't included so as not to unbind existing unload events
667 jQuery( window ).bind( 'unload', function(){
668 for ( var id in jQuery.cache )
670 if ( id != 1 && jQuery.cache[ id ].handle )
671 jQuery.event.remove( jQuery.cache[ id ].handle.elem );