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 all = !namespace && !event.exclusive; //cache this now, all = true means, any handler
249 handlers = ( jQuery.data(this, "events") || {} )[event.type];
251 for ( var j in handlers ) {
252 var handler = handlers[j];
254 // Filter the functions by class
255 if ( all || handler.type == namespace ) {
256 // Pass in a reference to the handler function itself
257 // So that we can later remove it
258 event.handler = handler;
259 event.data = handler.data;
261 ret = handler.apply( this, arguments );
266 if ( ret === false ) {
267 event.preventDefault();
268 event.stopPropagation();
276 fix: function(event) {
277 if ( event[expando] == true )
280 // store a copy of the original event object
281 // and "clone" to set read-only properties
282 var originalEvent = event;
283 event = { originalEvent: originalEvent };
284 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(" ");
285 for ( var i=props.length; i; i-- )
286 event[ props[i] ] = originalEvent[ props[i] ];
289 event[expando] = true;
291 // add preventDefault and stopPropagation since
292 // they will not work on the clone
293 event.preventDefault = function() {
294 // if preventDefault exists run it on the original event
295 if (originalEvent.preventDefault)
296 originalEvent.preventDefault();
297 // otherwise set the returnValue property of the original event to false (IE)
298 originalEvent.returnValue = false;
300 event.stopPropagation = function() {
301 // if stopPropagation exists run it on the original event
302 if (originalEvent.stopPropagation)
303 originalEvent.stopPropagation();
304 // otherwise set the cancelBubble property of the original event to true (IE)
305 originalEvent.cancelBubble = true;
309 event.timeStamp = event.timeStamp || now();
311 // Fix target property, if necessary
313 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
315 // check if target is a textnode (safari)
316 if ( event.target.nodeType == 3 )
317 event.target = event.target.parentNode;
319 // Add relatedTarget, if necessary
320 if ( !event.relatedTarget && event.fromElement )
321 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
323 // Calculate pageX/Y if missing and clientX/Y available
324 if ( event.pageX == null && event.clientX != null ) {
325 var doc = document.documentElement, body = document.body;
326 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
327 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
330 // Add which for key events
331 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
332 event.which = event.charCode || event.keyCode;
334 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
335 if ( !event.metaKey && event.ctrlKey )
336 event.metaKey = event.ctrlKey;
338 // Add which for click: 1 == left; 2 == middle; 3 == right
339 // Note: button is not normalized, so don't use it
340 if ( !event.which && event.button )
341 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
346 proxy: function( fn, proxy ){
347 // Set the guid of unique handler to the same of original handler, so it can be removed
348 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
349 return proxy;//so proxy can be declared as an argument
355 // Make sure the ready event is setup
360 teardown: function() { return; }
365 if ( jQuery.browser.msie ) return false;
366 jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
370 teardown: function() {
371 if ( jQuery.browser.msie ) return false;
372 jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
376 handler: function(event) {
377 // If we actually just moused on to a sub-element, ignore it
378 if ( withinElement(event, this) ) return true;
379 // Execute the right handlers by setting the event type to mouseenter
380 event.type = "mouseenter";
381 return jQuery.event.handle.apply(this, arguments);
387 if ( jQuery.browser.msie ) return false;
388 jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
392 teardown: function() {
393 if ( jQuery.browser.msie ) return false;
394 jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
398 handler: function(event) {
399 // If we actually just moused on to a sub-element, ignore it
400 if ( withinElement(event, this) ) return true;
401 // Execute the right handlers by setting the event type to mouseleave
402 event.type = "mouseleave";
403 return jQuery.event.handle.apply(this, arguments);
410 bind: function( type, data, fn ) {
411 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
412 jQuery.event.add( this, type, fn || data, fn && data );
416 one: function( type, data, fn ) {
417 var one = jQuery.event.proxy( fn || data, function(event) {
418 jQuery(this).unbind(event, one);
419 return (fn || data).apply( this, arguments );
421 return this.each(function(){
422 jQuery.event.add( this, type, one, fn && data);
426 unbind: function( type, fn ) {
427 return this.each(function(){
428 jQuery.event.remove( this, type, fn );
432 trigger: function( type, data, fn ) {
433 return this.each(function(){
434 jQuery.event.trigger( type, data, this, true, fn );
438 triggerHandler: function( type, data, fn ) {
439 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
442 toggle: function( fn ) {
443 // Save reference to arguments for access in closure
444 var args = arguments, i = 1;
446 // link all the functions, so any of them can unbind this click handler
447 while( i < args.length )
448 jQuery.event.proxy( fn, args[i++] );
450 return this.click( jQuery.event.proxy( fn, function(event) {
451 // Figure out which function to execute
452 this.lastToggle = ( this.lastToggle || 0 ) % i;
454 // Make sure that clicks stop
455 event.preventDefault();
457 // and execute the function
458 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
462 hover: function(fnOver, fnOut) {
463 return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
466 ready: function(fn) {
467 // Attach the listeners
470 // If the DOM is already ready
471 if ( jQuery.isReady )
472 // Execute the function immediately
473 fn.call( document, jQuery );
475 // Otherwise, remember the function for later
477 // Add the function to the wait list
478 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
487 // Handle when the DOM is ready
489 // Make sure that the DOM is not already loaded
490 if ( !jQuery.isReady ) {
491 // Remember that the DOM is ready
492 jQuery.isReady = true;
494 // If there are functions bound, to execute
495 if ( jQuery.readyList ) {
496 // Execute all of them
497 jQuery.each( jQuery.readyList, function(){
498 this.apply( document );
501 // Reset the list of functions
502 jQuery.readyList = null;
505 // Trigger any bound ready events
506 jQuery(document).triggerHandler("ready");
511 var readyBound = false;
513 function bindReady(){
514 if ( readyBound ) return;
517 // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
518 if ( document.addEventListener && !jQuery.browser.opera)
519 // Use the handy event callback
520 document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
522 // If IE is used and is not in a frame
523 // Continually check to see if the document is ready
524 if ( jQuery.browser.msie && window == top ) (function(){
525 if (jQuery.isReady) return;
527 // If IE is used, use the trick by Diego Perini
528 // http://javascript.nwbox.com/IEContentLoaded/
529 document.documentElement.doScroll("left");
531 setTimeout( arguments.callee, 0 );
534 // and execute any waiting functions
538 if ( jQuery.browser.opera )
539 document.addEventListener( "DOMContentLoaded", function () {
540 if (jQuery.isReady) return;
541 for (var i = 0; i < document.styleSheets.length; i++)
542 if (document.styleSheets[i].disabled) {
543 setTimeout( arguments.callee, 0 );
546 // and execute any waiting functions
550 if ( jQuery.browser.safari ) {
553 if (jQuery.isReady) return;
554 if ( document.readyState != "loaded" && document.readyState != "complete" ) {
555 setTimeout( arguments.callee, 0 );
558 if ( numStyles === undefined )
559 numStyles = jQuery("style, link[rel=stylesheet]").length;
560 if ( document.styleSheets.length != numStyles ) {
561 setTimeout( arguments.callee, 0 );
564 // and execute any waiting functions
569 // A fallback to window.onload, that will always work
570 jQuery.event.add( window, "load", jQuery.ready );
573 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
574 "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
575 "submit,keydown,keypress,keyup,error").split(","), function(i, name){
577 // Handle event binding
578 jQuery.fn[name] = function(fn){
579 return fn ? this.bind(name, fn) : this.trigger(name);
583 // Checks if an event happened on an element within another element
584 // Used in jQuery.event.special.mouseenter and mouseleave handlers
585 var withinElement = function(event, elem) {
586 // Check if mouse(over|out) are still within the same parent element
587 var parent = event.relatedTarget;
588 // Traverse up the tree
589 while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
590 // Return true if we actually just moused on to a sub-element
591 return parent == elem;
594 // Prevent memory leaks in IE
595 // And prevent errors on refresh with events like mouseover in other browsers
596 // Window isn't included so as not to unbind existing unload events
597 jQuery(window).bind("unload", function() {
598 jQuery("*").add(document).unbind();