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,data) === 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
363 // Make sure the ready event is setup
365 teardown: function() {}
370 if ( !jQuery.browser.msie ){
371 // Checks if an event happened on an element within another element
372 // Used in jQuery.event.special.mouseenter and mouseleave handlers
373 var withinElement = function(event) {
374 // Check if mouse(over|out) are still within the same parent element
375 var parent = event.relatedTarget;
376 // Traverse up the tree
377 while ( parent && parent != this )
378 try { parent = parent.parentNode; }
379 catch(e) { parent = this; }
381 if( parent != this ){
382 // set the correct event type
383 event.type = event.data;
384 // handle event if we actually just moused on to a non sub-element
385 jQuery.event.handle.apply( this, arguments );
390 mouseover: 'mouseenter',
391 mouseout: 'mouseleave'
392 }, function( orig, fix ){
393 jQuery.event.special[ fix ] = {
395 jQuery.event.add( this, orig, withinElement, fix );
397 teardown: function(){
398 jQuery.event.remove( this, orig, withinElement );
405 bind: function( type, data, fn ) {
406 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
407 jQuery.event.add( this, type, fn || data, fn && data );
411 one: function( type, data, fn ) {
412 var one = jQuery.event.proxy( fn || data, function(event) {
413 jQuery(this).unbind(event, one);
414 return (fn || data).apply( this, arguments );
416 return this.each(function(){
417 jQuery.event.add( this, type, one, fn && data);
421 unbind: function( type, fn ) {
422 return this.each(function(){
423 jQuery.event.remove( this, type, fn );
427 trigger: function( type, data, fn ) {
428 return this.each(function(){
429 jQuery.event.trigger( type, data, this, true, fn );
433 triggerHandler: function( type, data, fn ) {
434 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
437 toggle: function( fn ) {
438 // Save reference to arguments for access in closure
439 var args = arguments, i = 1;
441 // link all the functions, so any of them can unbind this click handler
442 while( i < args.length )
443 jQuery.event.proxy( fn, args[i++] );
445 return this.click( jQuery.event.proxy( fn, function(event) {
446 // Figure out which function to execute
447 this.lastToggle = ( this.lastToggle || 0 ) % i;
449 // Make sure that clicks stop
450 event.preventDefault();
452 // and execute the function
453 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
457 hover: function(fnOver, fnOut) {
458 return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
461 ready: function(fn) {
462 // Attach the listeners
465 // If the DOM is already ready
466 if ( jQuery.isReady )
467 // Execute the function immediately
468 fn.call( document, jQuery );
470 // Otherwise, remember the function for later
472 // Add the function to the wait list
473 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
482 // Handle when the DOM is ready
484 // Make sure that the DOM is not already loaded
485 if ( !jQuery.isReady ) {
486 // Remember that the DOM is ready
487 jQuery.isReady = true;
489 // If there are functions bound, to execute
490 if ( jQuery.readyList ) {
491 // Execute all of them
492 jQuery.each( jQuery.readyList, function(){
493 this.call( document );
496 // Reset the list of functions
497 jQuery.readyList = null;
500 // Trigger any bound ready events
501 jQuery(document).triggerHandler("ready");
506 var readyBound = false;
508 function bindReady(){
509 if ( readyBound ) return;
512 // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
513 if ( document.addEventListener && !jQuery.browser.opera)
514 // Use the handy event callback
515 document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
517 // If IE is used and is not in a frame
518 // Continually check to see if the document is ready
519 if ( jQuery.browser.msie && window == top ) (function(){
520 if (jQuery.isReady) return;
522 // If IE is used, use the trick by Diego Perini
523 // http://javascript.nwbox.com/IEContentLoaded/
524 document.documentElement.doScroll("left");
526 setTimeout( arguments.callee, 0 );
529 // and execute any waiting functions
533 if ( jQuery.browser.opera )
534 document.addEventListener( "DOMContentLoaded", function () {
535 if (jQuery.isReady) return;
536 for (var i = 0; i < document.styleSheets.length; i++)
537 if (document.styleSheets[i].disabled) {
538 setTimeout( arguments.callee, 0 );
541 // and execute any waiting functions
545 if ( jQuery.browser.safari ) {
548 if (jQuery.isReady) return;
549 if ( document.readyState != "loaded" && document.readyState != "complete" ) {
550 setTimeout( arguments.callee, 0 );
553 if ( numStyles === undefined )
554 numStyles = jQuery("style, link[rel=stylesheet]").length;
555 if ( document.styleSheets.length != numStyles ) {
556 setTimeout( arguments.callee, 0 );
559 // and execute any waiting functions
564 // A fallback to window.onload, that will always work
565 jQuery.event.add( window, "load", jQuery.ready );
568 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
569 "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
570 "submit,keydown,keypress,keyup,error").split(","), function(i, name){
572 // Handle event binding
573 jQuery.fn[name] = function(fn){
574 return fn ? this.bind(name, fn) : this.trigger(name);
578 // Prevent memory leaks in IE
579 // And prevent errors on refresh with events like mouseover in other browsers
580 // Window isn't included so as not to unbind existing unload events
581 jQuery( window ).bind( 'unload', function(){
582 for ( var id in jQuery.cache )
584 if ( id != 1 && jQuery.cache[ id ].handle )
585 jQuery.event.remove( jQuery.cache[ id ].handle.elem );