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 ( elem.setInterval && elem != window )
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 );
31 // Store data in unique handler
35 // Init the element's event structure
36 var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
37 handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
38 // Handle the second event of a trigger and when
39 // an event is called after a page has unloaded
40 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
41 jQuery.event.handle.apply(arguments.callee.elem, arguments) :
44 // Add elem as a property of the handle function
45 // This is to prevent a memory leak with non-native
49 // Handle multiple events separated by a space
50 // jQuery(...).bind("mouseover mouseout", fn);
51 jQuery.each(types.split(/\s+/), function(index, type) {
52 // Namespaced event handlers
53 var namespaces = type.split(".");
54 type = namespaces.shift();
55 handler.type = namespaces.slice().sort().join(".");
57 // Get the current list of functions bound to this event
58 var handlers = events[type];
60 if ( jQuery.event.specialAll[type] )
61 jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
63 // Init the event handler queue
65 handlers = events[type] = {};
67 // Check for a special event handler
68 // Only use addEventListener/attachEvent if the special
69 // events handler returns false
70 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
71 // Bind the global event handler to the element
72 if (elem.addEventListener)
73 elem.addEventListener(type, handle, false);
74 else if (elem.attachEvent)
75 elem.attachEvent("on" + type, handle);
79 // Add the function to the element's handler list
80 handlers[handler.guid] = handler;
82 // Keep track of which events have been used, for global triggering
83 jQuery.event.global[type] = true;
86 // Nullify elem to prevent memory leaks in IE
93 // Detach an event or set of events from an element
94 remove: function(elem, types, handler) {
95 // don't do events on text and comment nodes
96 if ( elem.nodeType == 3 || elem.nodeType == 8 )
99 var events = jQuery.data(elem, "events"), ret, index;
102 // Unbind all events for the element
103 if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
104 for ( var type in events )
105 this.remove( elem, type + (types || "") );
107 // types is actually an event object here
109 handler = types.handler;
113 // Handle multiple events seperated by a space
114 // jQuery(...).unbind("mouseover mouseout", fn);
115 jQuery.each(types.split(/\s+/), function(index, type){
116 // Namespaced event handlers
117 var namespaces = type.split(".");
118 type = namespaces.shift();
119 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
121 if ( events[type] ) {
122 // remove the given handler for the given type
124 delete events[type][handler.guid];
126 // remove all handlers for the given type
128 for ( var handle in events[type] )
129 // Handle the removal of namespaced events
130 if ( namespace.test(events[type][handle].type) )
131 delete events[type][handle];
133 if ( jQuery.event.specialAll[type] )
134 jQuery.event.specialAll[type].teardown.call(elem, namespaces);
136 // remove generic event handler if no more handlers exist
137 for ( ret in events[type] ) break;
139 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
140 if (elem.removeEventListener)
141 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
142 else if (elem.detachEvent)
143 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
152 // Remove the expando if it's no longer used
153 for ( ret in events ) break;
155 var handle = jQuery.data( elem, "handle" );
156 if ( handle ) handle.elem = null;
157 jQuery.removeData( elem, "events" );
158 jQuery.removeData( elem, "handle" );
163 // bubbling is internal
164 trigger: function( event, data, elem, bubbling ) {
165 // Event object or event type
166 var type = event.type || event;
169 event = typeof event === "object" ?
170 // jQuery.Event object
171 event[expando] ? event :
173 jQuery.extend( jQuery.Event(type), event ) :
174 // Just the event type (string)
177 if ( type.indexOf("!") >= 0 ) {
178 event.type = type = type.slice(0, -1);
179 event.exclusive = true;
182 // Handle a global trigger
184 // Don't bubble custom events when global (to avoid too much overhead)
185 event.stopPropagation();
186 // Only trigger if we've ever bound an event for it
187 if ( this.global[type] )
188 jQuery.each( jQuery.cache, function(){
189 if ( this.events && this.events[type] )
190 jQuery.event.trigger( event, data, this.handle.elem );
194 // Handle triggering a single element
196 // don't do events on text and comment nodes
197 if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
200 // Clean up in case it is reused
201 event.result = undefined;
204 // Clone the incoming data, if any
205 data = jQuery.makeArray(data);
206 data.unshift( event );
209 event.currentTarget = elem;
211 // Trigger the event, it is assumed that "handle" is a function
212 var handle = jQuery.data(elem, "handle");
214 handle.apply( elem, data );
216 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
217 if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
218 event.result = false;
220 // Trigger the native events (except for clicks on links)
221 if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
222 this.triggered = true;
225 // prevent IE from throwing an error for some hidden elements
229 this.triggered = false;
231 if ( !event.isPropagationStopped() ) {
232 var parent = elem.parentNode || elem.ownerDocument;
234 jQuery.event.trigger(event, data, parent, true);
238 handle: function(event) {
239 // returned undefined or false
242 event = arguments[0] = jQuery.event.fix( event || window.event );
244 // Namespaced event handlers
245 var namespaces = event.type.split(".");
246 event.type = namespaces.shift();
248 // Cache this now, all = true means, any handler
249 all = !namespaces.length && !event.exclusive;
251 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
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 || namespace.test(handler.type) ) {
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 var ret = handler.apply(this, arguments);
267 if( ret !== undefined ){
269 if ( ret === false ) {
270 event.preventDefault();
271 event.stopPropagation();
275 if( event.isImmediatePropagationStopped() )
282 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 toElement view wheelDelta which".split(" "),
284 fix: function(event) {
285 if ( event[expando] )
288 // store a copy of the original event object
289 // and "clone" to set read-only properties
290 var originalEvent = event;
291 event = jQuery.Event( originalEvent );
293 for ( var i = this.props.length, prop; i; ){
294 prop = this.props[ --i ];
295 event[ prop ] = originalEvent[ prop ];
298 // Fix target property, if necessary
300 event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
302 // check if target is a textnode (safari)
303 if ( event.target.nodeType == 3 )
304 event.target = event.target.parentNode;
306 // Add relatedTarget, if necessary
307 if ( !event.relatedTarget && event.fromElement )
308 event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
310 // Calculate pageX/Y if missing and clientX/Y available
311 if ( event.pageX == null && event.clientX != null ) {
312 var doc = document.documentElement, body = document.body;
313 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
314 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
317 // Add which for key events
318 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
319 event.which = event.charCode || event.keyCode;
321 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
322 if ( !event.metaKey && event.ctrlKey )
323 event.metaKey = event.ctrlKey;
325 // Add which for click: 1 == left; 2 == middle; 3 == right
326 // Note: button is not normalized, so don't use it
327 if ( !event.which && event.button )
328 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
333 proxy: function( fn, proxy ){
334 proxy = proxy || function(){ return fn.apply(this, arguments); };
335 // Set the guid of unique handler to the same of original handler, so it can be removed
336 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
337 // So proxy can be declared as an argument
343 // Make sure the ready event is setup
345 teardown: function() {}
351 setup: function( selector, namespaces ){
352 jQuery.event.add( this, namespaces[0], liveHandler );
354 teardown: function( namespaces ){
355 if ( namespaces.length ) {
356 var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
358 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
359 if ( name.test(this.type) )
364 jQuery.event.remove( this, namespaces[0], liveHandler );
371 jQuery.Event = function( src ){
372 // Allow instantiation without the 'new' keyword
373 if( !this.preventDefault )
374 return new jQuery.Event(src);
377 if( src && src.type ){
378 this.originalEvent = src;
379 this.type = src.type;
384 // timeStamp is buggy for some events on Firefox(#3843)
385 // So we won't rely on the native value
386 this.timeStamp = now();
389 this[expando] = true;
392 function returnFalse(){
395 function returnTrue(){
399 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
400 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
401 jQuery.Event.prototype = {
402 preventDefault: function() {
403 this.isDefaultPrevented = returnTrue;
405 var e = this.originalEvent;
408 // if preventDefault exists run it on the original event
409 if (e.preventDefault)
411 // otherwise set the returnValue property of the original event to false (IE)
412 e.returnValue = false;
414 stopPropagation: function() {
415 this.isPropagationStopped = returnTrue;
417 var e = this.originalEvent;
420 // if stopPropagation exists run it on the original event
421 if (e.stopPropagation)
423 // otherwise set the cancelBubble property of the original event to true (IE)
424 e.cancelBubble = true;
426 stopImmediatePropagation:function(){
427 this.isImmediatePropagationStopped = returnTrue;
428 this.stopPropagation();
430 isDefaultPrevented: returnFalse,
431 isPropagationStopped: returnFalse,
432 isImmediatePropagationStopped: returnFalse
434 // Checks if an event happened on an element within another element
435 // Used in jQuery.event.special.mouseenter and mouseleave handlers
436 var withinElement = function(event) {
437 // Check if mouse(over|out) are still within the same parent element
438 var parent = event.relatedTarget;
439 // Traverse up the tree
440 while ( parent && parent != this )
441 try { parent = parent.parentNode; }
442 catch(e) { parent = this; }
444 if( parent != this ){
445 // set the correct event type
446 event.type = event.data;
447 // handle event if we actually just moused on to a non sub-element
448 jQuery.event.handle.apply( this, arguments );
453 mouseover: 'mouseenter',
454 mouseout: 'mouseleave'
455 }, function( orig, fix ){
456 jQuery.event.special[ fix ] = {
458 jQuery.event.add( this, orig, withinElement, fix );
460 teardown: function(){
461 jQuery.event.remove( this, orig, withinElement );
467 bind: function( type, data, fn ) {
468 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
469 jQuery.event.add( this, type, fn || data, fn && data );
473 one: function( type, data, fn ) {
474 var one = jQuery.event.proxy( fn || data, function(event) {
475 jQuery(this).unbind(event, one);
476 return (fn || data).apply( this, arguments );
478 return this.each(function(){
479 jQuery.event.add( this, type, one, fn && data);
483 unbind: function( type, fn ) {
484 return this.each(function(){
485 jQuery.event.remove( this, type, fn );
489 trigger: function( type, data ) {
490 return this.each(function(){
491 jQuery.event.trigger( type, data, this );
495 triggerHandler: function( type, data ) {
497 var event = jQuery.Event(type);
498 event.preventDefault();
499 event.stopPropagation();
500 jQuery.event.trigger( event, data, this[0] );
505 toggle: function( fn ) {
506 // Save reference to arguments for access in closure
507 var args = arguments, i = 1;
509 // link all the functions, so any of them can unbind this click handler
510 while( i < args.length )
511 jQuery.event.proxy( fn, args[i++] );
513 return this.click( jQuery.event.proxy( fn, function(event) {
514 // Figure out which function to execute
515 this.lastToggle = ( this.lastToggle || 0 ) % i;
517 // Make sure that clicks stop
518 event.preventDefault();
520 // and execute the function
521 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
525 hover: function(fnOver, fnOut) {
526 return this.mouseenter(fnOver).mouseleave(fnOut);
529 ready: function(fn) {
530 // Attach the listeners
533 // If the DOM is already ready
534 if ( jQuery.isReady )
535 // Execute the function immediately
536 fn.call( document, jQuery );
538 // Otherwise, remember the function for later
540 // Add the function to the wait list
541 jQuery.readyList.push( fn );
546 live: function( type, fn ){
547 var proxy = jQuery.event.proxy( fn );
548 proxy.guid += this.selector + type;
550 jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );
555 die: function( type, fn ){
556 jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
561 function liveHandler( event ){
562 var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
566 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
567 if ( check.test(fn.type) ) {
568 var elem = jQuery(event.target).closest(fn.data)[0];
570 elems.push({ elem: elem, fn: fn });
574 jQuery.each(elems, function(){
575 if ( this.fn.call(this.elem, event, this.fn.data) === false )
582 function liveConvert(type, selector){
583 return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
589 // Handle when the DOM is ready
591 // Make sure that the DOM is not already loaded
592 if ( !jQuery.isReady ) {
593 // Remember that the DOM is ready
594 jQuery.isReady = true;
596 // If there are functions bound, to execute
597 if ( jQuery.readyList ) {
598 // Execute all of them
599 jQuery.each( jQuery.readyList, function(){
600 this.call( document, jQuery );
603 // Reset the list of functions
604 jQuery.readyList = null;
607 // Trigger any bound ready events
608 jQuery(document).triggerHandler("ready");
613 var readyBound = false;
615 function bindReady(){
616 if ( readyBound ) return;
619 // Mozilla, Opera and webkit nightlies currently support this event
620 if ( document.addEventListener ) {
621 // Use the handy event callback
622 document.addEventListener( "DOMContentLoaded", function(){
623 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
627 // If IE event model is used
628 } else if ( document.attachEvent ) {
629 // ensure firing before onload,
630 // maybe late but safe also for iframes
631 document.attachEvent("onreadystatechange", function(){
632 if ( document.readyState === "complete" ) {
633 document.detachEvent( "onreadystatechange", arguments.callee );
638 // If IE and not an iframe
639 // continually check to see if the document is ready
640 if ( document.documentElement.doScroll && window == window.top ) (function(){
641 if ( jQuery.isReady ) return;
644 // If IE is used, use the trick by Diego Perini
645 // http://javascript.nwbox.com/IEContentLoaded/
646 document.documentElement.doScroll("left");
648 setTimeout( arguments.callee, 0 );
652 // and execute any waiting functions
657 // A fallback to window.onload, that will always work
658 jQuery.event.add( window, "load", jQuery.ready );
661 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
662 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
663 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
665 // Handle event binding
666 jQuery.fn[name] = function(fn){
667 return fn ? this.bind(name, fn) : this.trigger(name);
671 // Prevent memory leaks in IE
672 // And prevent errors on refresh with events like mouseover in other browsers
673 // Window isn't included so as not to unbind existing unload events
674 jQuery( window ).bind( 'unload', function(){
675 for ( var id in jQuery.cache )
677 if ( id != 1 && jQuery.cache[ id ].handle )
678 jQuery.event.remove( jQuery.cache[ id ].handle.elem );