2 * A number of helper functions used for managing events.
3 * Many of the ideas behind this code orignated 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("*").add([window, document]).trigger(type, data);
173 // Handle triggering a single element
175 // don't do events on text and comment nodes
176 if ( elem.nodeType == 3 || elem.nodeType == 8 )
179 var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
180 // Check to see if we need to provide a fake event, or not
181 event = !data[0] || !data[0].preventDefault;
183 // Pass along a fake event
188 preventDefault: function(){},
189 stopPropagation: function(){},
192 data[0][expando] = true; // no need to fix fake event
195 // Enforce the right trigger type
198 data[0].exclusive = true;
200 // Trigger the event, it is assumed that "handle" is a function
201 var handle = jQuery.data(elem, "handle");
203 val = handle.apply( elem, data );
205 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
206 if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
209 // Extra functions don't get the custom event object
213 // Handle triggering of extra function
214 if ( extra && jQuery.isFunction( extra ) ) {
215 // call the extra function and tack the current return value on the end for possible inspection
216 ret = extra.apply( elem, val == null ? data : data.concat( val ) );
217 // if anything is returned, give it precedence and have it overwrite the previous value
218 if (ret !== undefined)
222 // Trigger the native events (except for clicks on links)
223 if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
224 this.triggered = true;
227 // prevent IE from throwing an error for some hidden elements
231 this.triggered = false;
237 handle: function(event) {
238 // returned undefined or false
239 var val, ret, namespace, all, handlers;
241 event = arguments[0] = jQuery.event.fix( event || window.event );
243 // Namespaced event handlers
244 namespace = event.type.split(".");
245 event.type = namespace[0];
246 namespace = namespace[1];
247 // Cache this now, all = true means, any handler
248 all = !namespace && !event.exclusive;
250 handlers = ( jQuery.data(this, "events") || {} )[event.type];
252 for ( var j in handlers ) {
253 var handler = handlers[j];
255 // Filter the functions by class
256 if ( all || handler.type == namespace ) {
257 // Pass in a reference to the handler function itself
258 // So that we can later remove it
259 event.handler = handler;
260 event.data = handler.data;
262 ret = handler.apply( this, arguments );
267 if ( ret === false ) {
268 event.preventDefault();
269 event.stopPropagation();
277 fix: function(event) {
278 if ( event[expando] == true )
281 // store a copy of the original event object
282 // and "clone" to set read-only properties
283 var originalEvent = event;
284 event = { originalEvent: originalEvent };
285 var 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(" ");
286 for ( var i=props.length; i; i-- )
287 event[ props[i] ] = originalEvent[ props[i] ];
290 event[expando] = true;
292 // add preventDefault and stopPropagation since
293 // they will not work on the clone
294 event.preventDefault = function() {
295 // if preventDefault exists run it on the original event
296 if (originalEvent.preventDefault)
297 originalEvent.preventDefault();
298 // otherwise set the returnValue property of the original event to false (IE)
299 originalEvent.returnValue = false;
301 event.stopPropagation = function() {
302 // if stopPropagation exists run it on the original event
303 if (originalEvent.stopPropagation)
304 originalEvent.stopPropagation();
305 // otherwise set the cancelBubble property of the original event to true (IE)
306 originalEvent.cancelBubble = true;
310 event.timeStamp = event.timeStamp || now();
312 // Fix target property, if necessary
314 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
316 // check if target is a textnode (safari)
317 if ( event.target.nodeType == 3 )
318 event.target = event.target.parentNode;
320 // Add relatedTarget, if necessary
321 if ( !event.relatedTarget && event.fromElement )
322 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
324 // Calculate pageX/Y if missing and clientX/Y available
325 if ( event.pageX == null && event.clientX != null ) {
326 var doc = document.documentElement, body = document.body;
327 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
328 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
331 // Add which for key events
332 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
333 event.which = event.charCode || event.keyCode;
335 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
336 if ( !event.metaKey && event.ctrlKey )
337 event.metaKey = event.ctrlKey;
339 // Add which for click: 1 == left; 2 == middle; 3 == right
340 // Note: button is not normalized, so don't use it
341 if ( !event.which && event.button )
342 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
347 proxy: function( fn, proxy ){
348 // Set the guid of unique handler to the same of original handler, so it can be removed
349 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
350 // So proxy can be declared as an argument
357 // Make sure the ready event is setup
362 teardown: function() { return; }
367 if ( jQuery.browser.msie ) return false;
368 jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
372 teardown: function() {
373 if ( jQuery.browser.msie ) return false;
374 jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
378 handler: function(event) {
379 // If we actually just moused on to a sub-element, ignore it
380 if ( withinElement(event, this) ) return true;
381 // Execute the right handlers by setting the event type to mouseenter
382 event.type = "mouseenter";
383 return jQuery.event.handle.apply(this, arguments);
389 if ( jQuery.browser.msie ) return false;
390 jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
394 teardown: function() {
395 if ( jQuery.browser.msie ) return false;
396 jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
400 handler: function(event) {
401 // If we actually just moused on to a sub-element, ignore it
402 if ( withinElement(event, this) ) return true;
403 // Execute the right handlers by setting the event type to mouseleave
404 event.type = "mouseleave";
405 return jQuery.event.handle.apply(this, arguments);
412 bind: function( type, data, fn ) {
413 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
414 jQuery.event.add( this, type, fn || data, fn && data );
418 one: function( type, data, fn ) {
419 var one = jQuery.event.proxy( fn || data, function(event) {
420 jQuery(this).unbind(event, one);
421 return (fn || data).apply( this, arguments );
423 return this.each(function(){
424 jQuery.event.add( this, type, one, fn && data);
428 unbind: function( type, fn ) {
429 return this.each(function(){
430 jQuery.event.remove( this, type, fn );
434 trigger: function( type, data, fn ) {
435 return this.each(function(){
436 jQuery.event.trigger( type, data, this, true, fn );
440 triggerHandler: function( type, data, fn ) {
441 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
444 toggle: function( fn ) {
445 // Save reference to arguments for access in closure
446 var args = arguments, i = 1;
448 // link all the functions, so any of them can unbind this click handler
449 while( i < args.length )
450 jQuery.event.proxy( fn, args[i++] );
452 return this.click( jQuery.event.proxy( fn, function(event) {
453 // Figure out which function to execute
454 this.lastToggle = ( this.lastToggle || 0 ) % i;
456 // Make sure that clicks stop
457 event.preventDefault();
459 // and execute the function
460 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
464 hover: function(fnOver, fnOut) {
465 return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
468 ready: function(fn) {
469 // Attach the listeners
472 // If the DOM is already ready
473 if ( jQuery.isReady )
474 // Execute the function immediately
475 fn.call( document, jQuery );
477 // Otherwise, remember the function for later
479 // Add the function to the wait list
480 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
489 // Handle when the DOM is ready
491 // Make sure that the DOM is not already loaded
492 if ( !jQuery.isReady ) {
493 // Remember that the DOM is ready
494 jQuery.isReady = true;
496 // If there are functions bound, to execute
497 if ( jQuery.readyList ) {
498 // Execute all of them
499 jQuery.each( jQuery.readyList, function(){
500 this.call( document );
503 // Reset the list of functions
504 jQuery.readyList = null;
507 // Trigger any bound ready events
508 jQuery(document).triggerHandler("ready");
513 var readyBound = false;
515 function bindReady(){
516 if ( readyBound ) return;
519 // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
520 if ( document.addEventListener && !jQuery.browser.opera)
521 // Use the handy event callback
522 document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
524 // If IE is used and is not in a frame
525 // Continually check to see if the document is ready
526 if ( jQuery.browser.msie && window == top ) (function(){
527 if (jQuery.isReady) return;
529 // If IE is used, use the trick by Diego Perini
530 // http://javascript.nwbox.com/IEContentLoaded/
531 document.documentElement.doScroll("left");
533 setTimeout( arguments.callee, 0 );
536 // and execute any waiting functions
540 if ( jQuery.browser.opera )
541 document.addEventListener( "DOMContentLoaded", function () {
542 if (jQuery.isReady) return;
543 for (var i = 0; i < document.styleSheets.length; i++)
544 if (document.styleSheets[i].disabled) {
545 setTimeout( arguments.callee, 0 );
548 // and execute any waiting functions
552 if ( jQuery.browser.safari ) {
555 if (jQuery.isReady) return;
556 if ( document.readyState != "loaded" && document.readyState != "complete" ) {
557 setTimeout( arguments.callee, 0 );
560 if ( numStyles === undefined )
561 numStyles = jQuery("style, link[rel=stylesheet]").length;
562 if ( document.styleSheets.length != numStyles ) {
563 setTimeout( arguments.callee, 0 );
566 // and execute any waiting functions
571 // A fallback to window.onload, that will always work
572 jQuery.event.add( window, "load", jQuery.ready );
575 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
576 "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
577 "submit,keydown,keypress,keyup,error").split(","), function(i, name){
579 // Handle event binding
580 jQuery.fn[name] = function(fn){
581 return fn ? this.bind(name, fn) : this.trigger(name);
585 // Checks if an event happened on an element within another element
586 // Used in jQuery.event.special.mouseenter and mouseleave handlers
587 var withinElement = function(event, elem) {
588 // Check if mouse(over|out) are still within the same parent element
589 var parent = event.relatedTarget;
590 // Traverse up the tree
591 while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
592 // Return true if we actually just moused on to a sub-element
593 return parent == elem;
596 // Prevent memory leaks in IE
597 // And prevent errors on refresh with events like mouseover in other browsers
598 // Window isn't included so as not to unbind existing unload events
599 jQuery(window).bind("unload", function() {
600 jQuery("*").add(document).unbind();