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 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(" "),
279 fix: function(event) {
280 if ( event[expando] == true )
283 // store a copy of the original event object
284 // and "clone" to set read-only properties
285 var originalEvent = event;
286 event = { originalEvent: originalEvent };
288 for ( var i = this.props.length, prop; i; ){
289 prop = this.props[ --i ];
290 event[ prop ] = originalEvent[ prop ];
294 event[expando] = true;
296 // add preventDefault and stopPropagation since
297 // they will not work on the clone
298 event.preventDefault = function() {
299 // if preventDefault exists run it on the original event
300 if (originalEvent.preventDefault)
301 originalEvent.preventDefault();
302 // otherwise set the returnValue property of the original event to false (IE)
303 originalEvent.returnValue = false;
305 event.stopPropagation = function() {
306 // if stopPropagation exists run it on the original event
307 if (originalEvent.stopPropagation)
308 originalEvent.stopPropagation();
309 // otherwise set the cancelBubble property of the original event to true (IE)
310 originalEvent.cancelBubble = true;
314 event.timeStamp = event.timeStamp || now();
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
361 // Make sure the ready event is setup
366 teardown: function() { return; }
371 if ( jQuery.browser.msie ) return false;
372 jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
376 teardown: function() {
377 if ( jQuery.browser.msie ) return false;
378 jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
382 handler: function(event) {
383 // If we actually just moused on to a sub-element, ignore it
384 if ( withinElement(event, this) ) return true;
385 // Execute the right handlers by setting the event type to mouseenter
386 event.type = "mouseenter";
387 return jQuery.event.handle.apply(this, arguments);
393 if ( jQuery.browser.msie ) return false;
394 jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
398 teardown: function() {
399 if ( jQuery.browser.msie ) return false;
400 jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
404 handler: function(event) {
405 // If we actually just moused on to a sub-element, ignore it
406 if ( withinElement(event, this) ) return true;
407 // Execute the right handlers by setting the event type to mouseleave
408 event.type = "mouseleave";
409 return jQuery.event.handle.apply(this, arguments);
416 bind: function( type, data, fn ) {
417 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
418 jQuery.event.add( this, type, fn || data, fn && data );
422 one: function( type, data, fn ) {
423 var one = jQuery.event.proxy( fn || data, function(event) {
424 jQuery(this).unbind(event, one);
425 return (fn || data).apply( this, arguments );
427 return this.each(function(){
428 jQuery.event.add( this, type, one, fn && data);
432 unbind: function( type, fn ) {
433 return this.each(function(){
434 jQuery.event.remove( this, type, fn );
438 trigger: function( type, data, fn ) {
439 return this.each(function(){
440 jQuery.event.trigger( type, data, this, true, fn );
444 triggerHandler: function( type, data, fn ) {
445 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
448 toggle: function( fn ) {
449 // Save reference to arguments for access in closure
450 var args = arguments, i = 1;
452 // link all the functions, so any of them can unbind this click handler
453 while( i < args.length )
454 jQuery.event.proxy( fn, args[i++] );
456 return this.click( jQuery.event.proxy( fn, function(event) {
457 // Figure out which function to execute
458 this.lastToggle = ( this.lastToggle || 0 ) % i;
460 // Make sure that clicks stop
461 event.preventDefault();
463 // and execute the function
464 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
468 hover: function(fnOver, fnOut) {
469 return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
472 ready: function(fn) {
473 // Attach the listeners
476 // If the DOM is already ready
477 if ( jQuery.isReady )
478 // Execute the function immediately
479 fn.call( document, jQuery );
481 // Otherwise, remember the function for later
483 // Add the function to the wait list
484 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
493 // Handle when the DOM is ready
495 // Make sure that the DOM is not already loaded
496 if ( !jQuery.isReady ) {
497 // Remember that the DOM is ready
498 jQuery.isReady = true;
500 // If there are functions bound, to execute
501 if ( jQuery.readyList ) {
502 // Execute all of them
503 jQuery.each( jQuery.readyList, function(){
504 this.call( document );
507 // Reset the list of functions
508 jQuery.readyList = null;
511 // Trigger any bound ready events
512 jQuery(document).triggerHandler("ready");
517 var readyBound = false;
519 function bindReady(){
520 if ( readyBound ) return;
523 // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
524 if ( document.addEventListener && !jQuery.browser.opera)
525 // Use the handy event callback
526 document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
528 // If IE is used and is not in a frame
529 // Continually check to see if the document is ready
530 if ( jQuery.browser.msie && window == top ) (function(){
531 if (jQuery.isReady) return;
533 // If IE is used, use the trick by Diego Perini
534 // http://javascript.nwbox.com/IEContentLoaded/
535 document.documentElement.doScroll("left");
537 setTimeout( arguments.callee, 0 );
540 // and execute any waiting functions
544 if ( jQuery.browser.opera )
545 document.addEventListener( "DOMContentLoaded", function () {
546 if (jQuery.isReady) return;
547 for (var i = 0; i < document.styleSheets.length; i++)
548 if (document.styleSheets[i].disabled) {
549 setTimeout( arguments.callee, 0 );
552 // and execute any waiting functions
556 if ( jQuery.browser.safari ) {
559 if (jQuery.isReady) return;
560 if ( document.readyState != "loaded" && document.readyState != "complete" ) {
561 setTimeout( arguments.callee, 0 );
564 if ( numStyles === undefined )
565 numStyles = jQuery("style, link[rel=stylesheet]").length;
566 if ( document.styleSheets.length != numStyles ) {
567 setTimeout( arguments.callee, 0 );
570 // and execute any waiting functions
575 // A fallback to window.onload, that will always work
576 jQuery.event.add( window, "load", jQuery.ready );
579 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
580 "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
581 "submit,keydown,keypress,keyup,error").split(","), function(i, name){
583 // Handle event binding
584 jQuery.fn[name] = function(fn){
585 return fn ? this.bind(name, fn) : this.trigger(name);
589 // Checks if an event happened on an element within another element
590 // Used in jQuery.event.special.mouseenter and mouseleave handlers
591 var withinElement = function(event, elem) {
592 // Check if mouse(over|out) are still within the same parent element
593 var parent = event.relatedTarget;
594 // Traverse up the tree
595 while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
596 // Return true if we actually just moused on to a sub-element
597 return parent == elem;
600 // Prevent memory leaks in IE
601 // And prevent errors on refresh with events like mouseover in other browsers
602 // Window isn't included so as not to unbind existing unload events
603 jQuery(window).bind("unload", function() {
604 jQuery("*").add(document).unbind();