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( e, data, elem, donative, extra, dohandlers) {
167 // Event object or event type
168 var type = e.type || e;
170 // Handle a global trigger
172 // Only trigger if we've ever bound an event for it
173 if ( this.global[type] )
174 jQuery.each( jQuery.cache, function(){
175 if ( this.events && this.events[type] )
176 jQuery.event.trigger( e, data, this.handle.elem, false );
179 // Handle triggering a single element
182 // don't do events on text and comment nodes
183 if ( elem.nodeType == 3 || elem.nodeType == 8 )
186 // Clone the incoming data, if any
187 data = jQuery.makeArray(data);
189 if ( type.indexOf("!") >= 0 ) {
190 type = type.slice(0, -1);
191 var exclusive = true;
194 e = typeof e === "object" ?
195 // jQuery.Event object
198 jQuery.extend( jQuery.Event(type), e ) :
199 // Just the event type (string)
202 e.target = e.target || elem;
203 e.currentTarget = elem;
204 e.exclusive = exclusive;
208 var val, ret, fn = jQuery.isFunction( elem[ type ] );
210 if ( dohandlers !== false ) {
211 // Trigger the event, it is assumed that "handle" is a function
212 var handle = jQuery.data(elem, "handle");
214 val = handle.apply( elem, data );
216 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
217 if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
221 if ( donative !== false && val !== false ) {
222 var parent = elem.parentNode || elem.ownerDocument;
224 jQuery.event.trigger(e, data, parent, donative);
227 // Extra functions don't get the custom event object
230 // Handle triggering of extra function
231 if ( extra && jQuery.isFunction( extra ) ) {
232 // call the extra function and tack the current return value on the end for possible inspection
233 ret = extra.apply( elem, val == null ? data : data.concat( val ) );
234 // if anything is returned, give it precedence and have it overwrite the previous value
235 if ( ret !== undefined )
239 // Trigger the native events (except for clicks on links)
240 if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
241 this.triggered = true;
244 // prevent IE from throwing an error for some hidden elements
248 this.triggered = false;
254 handle: function(event) {
255 // returned undefined or false
256 var val, ret, all, handlers;
258 event = arguments[0] = jQuery.event.fix( event || window.event );
260 // Namespaced event handlers
261 var namespaces = event.type.split(".");
262 event.type = namespaces.shift();
264 // Cache this now, all = true means, any handler
265 all = !namespaces.length && !event.exclusive;
267 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
269 handlers = ( jQuery.data(this, "events") || {} )[event.type];
271 for ( var j in handlers ) {
272 var handler = handlers[j];
274 // Filter the functions by class
275 if ( all || namespace.test(handler.type) ) {
276 // Pass in a reference to the handler function itself
277 // So that we can later remove it
278 event.handler = handler;
279 event.data = handler.data;
281 ret = handler.apply( this, arguments );
286 if ( ret === false ) {
287 event.preventDefault();
288 event.stopPropagation();
300 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(" "),
302 fix: function(event) {
303 if ( event[expando] )
306 // store a copy of the original event object
307 // and "clone" to set read-only properties
308 var originalEvent = event;
309 event = jQuery.Event( originalEvent );
311 for ( var i = this.props.length, prop; i; ){
312 prop = this.props[ --i ];
313 event[ prop ] = originalEvent[ prop ];
316 // Fix target property, if necessary
318 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
320 // check if target is a textnode (safari)
321 if ( event.target.nodeType == 3 )
322 event.target = event.target.parentNode;
324 // Add relatedTarget, if necessary
325 if ( !event.relatedTarget && event.fromElement )
326 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
328 // Calculate pageX/Y if missing and clientX/Y available
329 if ( event.pageX == null && event.clientX != null ) {
330 var doc = document.documentElement, body = document.body;
331 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
332 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
335 // Add which for key events
336 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
337 event.which = event.charCode || event.keyCode;
339 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
340 if ( !event.metaKey && event.ctrlKey )
341 event.metaKey = event.ctrlKey;
343 // Add which for click: 1 == left; 2 == middle; 3 == right
344 // Note: button is not normalized, so don't use it
345 if ( !event.which && event.button )
346 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
351 proxy: function( fn, proxy ){
352 // Set the guid of unique handler to the same of original handler, so it can be removed
353 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
354 // So proxy can be declared as an argument
360 // Make sure the ready event is setup
362 teardown: function() {}
368 setup: function( selector, namespaces ){
369 jQuery.event.add( this, namespaces[0], liveHandler );
371 teardown: function( namespaces ){
372 if ( namespaces.length ) {
373 var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
375 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
376 if ( name.test(this.type) )
381 jQuery.event.remove( this, namespaces[0], liveHandler );
388 jQuery.Event = function( src ){
389 // Allow instantiation without the 'new' keyword
390 if( !this.preventDefault )
391 return new jQuery.Event(src);
394 if( src && src.type ){
395 this.originalEvent = src;
396 this.type = src.type;
399 this.timeStamp = src.timeStamp || now();
405 this[expando] = true;
408 jQuery.Event.prototype = {
409 // add preventDefault and stopPropagation since
410 // they will not work on the clone
411 preventDefault: function() {
412 var e = this.originalEvent;
415 // if preventDefault exists run it on the original event
416 if (e.preventDefault)
418 // otherwise set the returnValue property of the original event to false (IE)
419 e.returnValue = false;
421 stopPropagation: function() {
422 var e = this.originalEvent;
425 // if stopPropagation exists run it on the original event
426 if (e.stopPropagation)
428 // otherwise set the cancelBubble property of the original event to true (IE)
429 e.cancelBubble = true;
431 stopImmediatePropagation:function(){
433 this.stopPropagation();
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, fn ) {
492 return this.each(function(){
493 jQuery.event.trigger( type, data, this, true, fn );
497 triggerHandler: function( type, data, fn ) {
498 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
501 toggle: function( fn ) {
502 // Save reference to arguments for access in closure
503 var args = arguments, i = 1;
505 // link all the functions, so any of them can unbind this click handler
506 while( i < args.length )
507 jQuery.event.proxy( fn, args[i++] );
509 return this.click( jQuery.event.proxy( fn, function(event) {
510 // Figure out which function to execute
511 this.lastToggle = ( this.lastToggle || 0 ) % i;
513 // Make sure that clicks stop
514 event.preventDefault();
516 // and execute the function
517 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
521 hover: function(fnOver, fnOut) {
522 return this.mouseenter(fnOver).mouseleave(fnOut);
525 ready: function(fn) {
526 // Attach the listeners
529 // If the DOM is already ready
530 if ( jQuery.isReady )
531 // Execute the function immediately
532 fn.call( document, jQuery );
534 // Otherwise, remember the function for later
536 // Add the function to the wait list
537 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
542 live: function( type, fn ){
543 jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
547 die: function( type, fn ){
548 jQuery(document).unbind( liveConvert(type, this.selector), fn );
553 function liveHandler( event ){
554 var check = RegExp("(^|\\.)" + event.type + "(\\.|$)");
555 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
556 if ( check.test(fn.type) ) {
557 var elem = jQuery(event.target).closest(fn.data)[0];
559 jQuery.event.trigger( event.type, fn.data, elem, false, fn, false );
564 function liveConvert(type, selector){
565 return ["live", type, selector.replace(/\./g, "_")].join(".");
571 // Handle when the DOM is ready
573 // Make sure that the DOM is not already loaded
574 if ( !jQuery.isReady ) {
575 // Remember that the DOM is ready
576 jQuery.isReady = true;
578 // If there are functions bound, to execute
579 if ( jQuery.readyList ) {
580 // Execute all of them
581 jQuery.each( jQuery.readyList, function(){
582 this.call( document );
585 // Reset the list of functions
586 jQuery.readyList = null;
589 // Trigger any bound ready events
590 jQuery(document).triggerHandler("ready");
595 var readyBound = false;
597 function bindReady(){
598 if ( readyBound ) return;
601 // Mozilla, Opera and webkit nightlies currently support this event
602 if ( document.addEventListener ) {
603 // Use the handy event callback
604 document.addEventListener( "DOMContentLoaded", function(){
605 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
609 // If IE event model is used
610 } else if ( document.attachEvent ) {
611 // ensure firing before onload,
612 // maybe late but safe also for iframes
613 document.attachEvent("onreadystatechange", function(){
614 if ( document.readyState === "complete" ) {
615 document.detachEvent( "onreadystatechange", arguments.callee );
620 // If IE and not an iframe
621 // continually check to see if the document is ready
622 if ( document.documentElement.doScroll && !window.frameElement ) (function(){
623 if ( jQuery.isReady ) return;
626 // If IE is used, use the trick by Diego Perini
627 // http://javascript.nwbox.com/IEContentLoaded/
628 document.documentElement.doScroll("left");
630 setTimeout( arguments.callee, 0 );
634 // and execute any waiting functions
639 // A fallback to window.onload, that will always work
640 jQuery.event.add( window, "load", jQuery.ready );
643 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
644 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
645 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
647 // Handle event binding
648 jQuery.fn[name] = function(fn){
649 return fn ? this.bind(name, fn) : this.trigger(name);
653 // Prevent memory leaks in IE
654 // And prevent errors on refresh with events like mouseover in other browsers
655 // Window isn't included so as not to unbind existing unload events
656 jQuery( window ).bind( 'unload', function(){
657 for ( var id in jQuery.cache )
659 if ( id != 1 && jQuery.cache[ id ].handle )
660 jQuery.event.remove( jQuery.cache[ id ].handle.elem );