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 ( jQuery.browser.msie && elem.setInterval )
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 if ( typeof jQuery != "undefined" && !jQuery.event.triggered )
44 return jQuery.event.handle.apply(arguments.callee.elem, arguments);
46 // Add elem as a property of the handle function
47 // This is to prevent a memory leak with non-native
51 // Handle multiple events separated by a space
52 // jQuery(...).bind("mouseover mouseout", fn);
53 jQuery.each(types.split(/\s+/), function(index, type) {
54 // Namespaced event handlers
55 var parts = type.split(".");
57 handler.type = parts[1];
59 // Get the current list of functions bound to this event
60 var handlers = events[type];
62 // Init the event handler queue
64 handlers = events[type] = {};
66 // Check for a special event handler
67 // Only use addEventListener/attachEvent if the special
68 // events handler returns false
69 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
70 // Bind the global event handler to the element
71 if (elem.addEventListener)
72 elem.addEventListener(type, handle, false);
73 else if (elem.attachEvent)
74 elem.attachEvent("on" + type, handle);
78 // Add the function to the element's handler list
79 handlers[handler.guid] = handler;
81 // Keep track of which events have been used, for global triggering
82 jQuery.event.global[type] = true;
85 // Nullify elem to prevent memory leaks in IE
92 // Detach an event or set of events from an element
93 remove: function(elem, types, handler) {
94 // don't do events on text and comment nodes
95 if ( elem.nodeType == 3 || elem.nodeType == 8 )
98 var events = jQuery.data(elem, "events"), ret, index;
101 // Unbind all events for the element
102 if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") )
103 for ( var type in events )
104 this.remove( elem, type + (types || "") );
106 // types is actually an event object here
108 handler = types.handler;
112 // Handle multiple events seperated by a space
113 // jQuery(...).unbind("mouseover mouseout", fn);
114 jQuery.each(types.split(/\s+/), function(index, type){
115 // Namespaced event handlers
116 var parts = type.split(".");
119 if ( events[type] ) {
120 // remove the given handler for the given type
122 delete events[type][handler.guid];
124 // remove all handlers for the given type
126 for ( handler in events[type] )
127 // Handle the removal of namespaced events
128 if ( !parts[1] || events[type][handler].type == parts[1] )
129 delete events[type][handler];
131 // remove generic event handler if no more handlers exist
132 for ( ret in events[type] ) break;
134 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
135 if (elem.removeEventListener)
136 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
137 else if (elem.detachEvent)
138 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
147 // Remove the expando if it's no longer used
148 for ( ret in events ) break;
150 var handle = jQuery.data( elem, "handle" );
151 if ( handle ) handle.elem = null;
152 jQuery.removeData( elem, "events" );
153 jQuery.removeData( elem, "handle" );
158 trigger: function(type, data, elem, donative, extra) {
159 // Clone the incoming data, if any
160 data = jQuery.makeArray(data);
162 if ( type.indexOf("!") >= 0 ) {
163 type = type.slice(0, -1);
164 var exclusive = true;
167 // Handle a global trigger
169 // Only trigger if we've ever bound an event for it
170 if ( this.global[type] )
171 jQuery.each( jQuery.cache, function(){
172 if ( this.events && this.events[type] )
173 jQuery.event.trigger( type, data, this.handle.elem );
176 // Handle triggering a single element
178 // don't do events on text and comment nodes
179 if ( elem.nodeType == 3 || elem.nodeType == 8 )
182 var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
183 // Check to see if we need to provide a fake event, or not
184 event = !data[0] || !data[0].preventDefault;
186 // Pass along a fake event
191 preventDefault: function(){},
192 stopPropagation: function(){},
195 data[0][expando] = true; // no need to fix fake event
198 // Enforce the right trigger type
201 data[0].exclusive = true;
203 // Trigger the event, it is assumed that "handle" is a function
204 var handle = jQuery.data(elem, "handle");
206 val = handle.apply( elem, data );
208 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
209 if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
212 // Extra functions don't get the custom event object
216 // Handle triggering of extra function
217 if ( extra && jQuery.isFunction( extra ) ) {
218 // call the extra function and tack the current return value on the end for possible inspection
219 ret = extra.apply( elem, val == null ? data : data.concat( val ) );
220 // if anything is returned, give it precedence and have it overwrite the previous value
221 if (ret !== undefined)
225 // Trigger the native events (except for clicks on links)
226 if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
227 this.triggered = true;
230 // prevent IE from throwing an error for some hidden elements
234 this.triggered = false;
240 handle: function(event) {
241 // returned undefined or false
242 var val, ret, namespace, all, handlers;
244 event = arguments[0] = jQuery.event.fix( event || window.event );
246 // Namespaced event handlers
247 namespace = event.type.split(".");
248 event.type = namespace[0];
249 namespace = namespace[1];
250 // Cache this now, all = true means, any handler
251 all = !namespace && !event.exclusive;
253 handlers = ( jQuery.data(this, "events") || {} )[event.type];
255 for ( var j in handlers ) {
256 var handler = handlers[j];
258 // Filter the functions by class
259 if ( all || handler.type == namespace ) {
260 // Pass in a reference to the handler function itself
261 // So that we can later remove it
262 event.handler = handler;
263 event.data = handler.data;
265 ret = handler.apply( this, arguments );
270 if ( ret === false ) {
271 event.preventDefault();
272 event.stopPropagation();
280 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 timeStamp toElement type view wheelDelta which".split(" "),
282 fix: function(event) {
283 if ( event[expando] == true )
286 // store a copy of the original event object
287 // and "clone" to set read-only properties
288 var originalEvent = event;
289 event = { originalEvent: originalEvent };
291 for ( var i = this.props.length, prop; i; ){
292 prop = this.props[ --i ];
293 event[ prop ] = originalEvent[ prop ];
297 event[expando] = true;
299 // add preventDefault and stopPropagation since
300 // they will not work on the clone
301 event.preventDefault = function() {
302 // if preventDefault exists run it on the original event
303 if (originalEvent.preventDefault)
304 originalEvent.preventDefault();
305 // otherwise set the returnValue property of the original event to false (IE)
306 originalEvent.returnValue = false;
308 event.stopPropagation = function() {
309 // if stopPropagation exists run it on the original event
310 if (originalEvent.stopPropagation)
311 originalEvent.stopPropagation();
312 // otherwise set the cancelBubble property of the original event to true (IE)
313 originalEvent.cancelBubble = true;
317 event.timeStamp = event.timeStamp || now();
319 // Fix target property, if necessary
321 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
323 // check if target is a textnode (safari)
324 if ( event.target.nodeType == 3 )
325 event.target = event.target.parentNode;
327 // Add relatedTarget, if necessary
328 if ( !event.relatedTarget && event.fromElement )
329 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
331 // Calculate pageX/Y if missing and clientX/Y available
332 if ( event.pageX == null && event.clientX != null ) {
333 var doc = document.documentElement, body = document.body;
334 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
335 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
338 // Add which for key events
339 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
340 event.which = event.charCode || event.keyCode;
342 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
343 if ( !event.metaKey && event.ctrlKey )
344 event.metaKey = event.ctrlKey;
346 // Add which for click: 1 == left; 2 == middle; 3 == right
347 // Note: button is not normalized, so don't use it
348 if ( !event.which && event.button )
349 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
354 proxy: function( fn, proxy ){
355 // Set the guid of unique handler to the same of original handler, so it can be removed
356 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
357 // So proxy can be declared as an argument
364 // Make sure the ready event is setup
369 teardown: function() { return; }
374 if ( jQuery.browser.msie ) return false;
375 jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
379 teardown: function() {
380 if ( jQuery.browser.msie ) return false;
381 jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
385 handler: function(event) {
386 // If we actually just moused on to a sub-element, ignore it
387 if ( withinElement(event, this) ) return true;
388 // Execute the right handlers by setting the event type to mouseenter
389 event.type = "mouseenter";
390 return jQuery.event.handle.apply(this, arguments);
396 if ( jQuery.browser.msie ) return false;
397 jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
401 teardown: function() {
402 if ( jQuery.browser.msie ) return false;
403 jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
407 handler: function(event) {
408 // If we actually just moused on to a sub-element, ignore it
409 if ( withinElement(event, this) ) return true;
410 // Execute the right handlers by setting the event type to mouseleave
411 event.type = "mouseleave";
412 return jQuery.event.handle.apply(this, arguments);
419 bind: function( type, data, fn ) {
420 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
421 jQuery.event.add( this, type, fn || data, fn && data );
425 one: function( type, data, fn ) {
426 var one = jQuery.event.proxy( fn || data, function(event) {
427 jQuery(this).unbind(event, one);
428 return (fn || data).apply( this, arguments );
430 return this.each(function(){
431 jQuery.event.add( this, type, one, fn && data);
435 unbind: function( type, fn ) {
436 return this.each(function(){
437 jQuery.event.remove( this, type, fn );
441 trigger: function( type, data, fn ) {
442 return this.each(function(){
443 jQuery.event.trigger( type, data, this, true, fn );
447 triggerHandler: function( type, data, fn ) {
448 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
451 toggle: function( fn ) {
452 // Save reference to arguments for access in closure
453 var args = arguments, i = 1;
455 // link all the functions, so any of them can unbind this click handler
456 while( i < args.length )
457 jQuery.event.proxy( fn, args[i++] );
459 return this.click( jQuery.event.proxy( fn, function(event) {
460 // Figure out which function to execute
461 this.lastToggle = ( this.lastToggle || 0 ) % i;
463 // Make sure that clicks stop
464 event.preventDefault();
466 // and execute the function
467 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
471 hover: function(fnOver, fnOut) {
472 return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
475 ready: function(fn) {
476 // Attach the listeners
479 // If the DOM is already ready
480 if ( jQuery.isReady )
481 // Execute the function immediately
482 fn.call( document, jQuery );
484 // Otherwise, remember the function for later
486 // Add the function to the wait list
487 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
496 // Handle when the DOM is ready
498 // Make sure that the DOM is not already loaded
499 if ( !jQuery.isReady ) {
500 // Remember that the DOM is ready
501 jQuery.isReady = true;
503 // If there are functions bound, to execute
504 if ( jQuery.readyList ) {
505 // Execute all of them
506 jQuery.each( jQuery.readyList, function(){
507 this.call( document );
510 // Reset the list of functions
511 jQuery.readyList = null;
514 // Trigger any bound ready events
515 jQuery(document).triggerHandler("ready");
520 var readyBound = false;
522 function bindReady(){
523 if ( readyBound ) return;
526 // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
527 if ( document.addEventListener && !jQuery.browser.opera)
528 // Use the handy event callback
529 document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
531 // If IE is used and is not in a frame
532 // Continually check to see if the document is ready
533 if ( jQuery.browser.msie && window == top ) (function(){
534 if (jQuery.isReady) return;
536 // If IE is used, use the trick by Diego Perini
537 // http://javascript.nwbox.com/IEContentLoaded/
538 document.documentElement.doScroll("left");
540 setTimeout( arguments.callee, 0 );
543 // and execute any waiting functions
547 if ( jQuery.browser.opera )
548 document.addEventListener( "DOMContentLoaded", function () {
549 if (jQuery.isReady) return;
550 for (var i = 0; i < document.styleSheets.length; i++)
551 if (document.styleSheets[i].disabled) {
552 setTimeout( arguments.callee, 0 );
555 // and execute any waiting functions
559 if ( jQuery.browser.safari ) {
562 if (jQuery.isReady) return;
563 if ( document.readyState != "loaded" && document.readyState != "complete" ) {
564 setTimeout( arguments.callee, 0 );
567 if ( numStyles === undefined )
568 numStyles = jQuery("style, link[rel=stylesheet]").length;
569 if ( document.styleSheets.length != numStyles ) {
570 setTimeout( arguments.callee, 0 );
573 // and execute any waiting functions
578 // A fallback to window.onload, that will always work
579 jQuery.event.add( window, "load", jQuery.ready );
582 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
583 "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
584 "submit,keydown,keypress,keyup,error").split(","), function(i, name){
586 // Handle event binding
587 jQuery.fn[name] = function(fn){
588 return fn ? this.bind(name, fn) : this.trigger(name);
592 // Checks if an event happened on an element within another element
593 // Used in jQuery.event.special.mouseenter and mouseleave handlers
594 var withinElement = function(event, elem) {
595 // Check if mouse(over|out) are still within the same parent element
596 var parent = event.relatedTarget;
597 // Traverse up the tree
598 while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
599 // Return true if we actually just moused on to a sub-element
600 return parent == elem;
603 // Prevent memory leaks in IE
604 // And prevent errors on refresh with events like mouseover in other browsers
605 // Window isn't included so as not to unbind existing unload events
606 jQuery(window).bind("unload", function() {
607 jQuery("*").add(document).unbind();