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, 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 return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
44 jQuery.event.handle.apply(arguments.callee.elem, arguments) :
47 // Add elem as a property of the handle function
48 // This is to prevent a memory leak with non-native
52 // Handle multiple events separated by a space
53 // jQuery(...).bind("mouseover mouseout", fn);
54 jQuery.each(types.split(/\s+/), function(index, type) {
55 // Namespaced event handlers
56 var namespaces = type.split(".");
57 type = namespaces.shift();
58 handler.type = namespaces.slice().sort().join(".");
60 // Get the current list of functions bound to this event
61 var handlers = events[type];
63 if ( jQuery.event.specialAll[type] )
64 jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
66 // Init the event handler queue
68 handlers = events[type] = {};
70 // Check for a special event handler
71 // Only use addEventListener/attachEvent if the special
72 // events handler returns false
73 if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
74 // Bind the global event handler to the element
75 if (elem.addEventListener)
76 elem.addEventListener(type, handle, false);
77 else if (elem.attachEvent)
78 elem.attachEvent("on" + type, handle);
82 // Add the function to the element's handler list
83 handlers[handler.guid] = handler;
85 // Keep track of which events have been used, for global triggering
86 jQuery.event.global[type] = true;
89 // Nullify elem to prevent memory leaks in IE
96 // Detach an event or set of events from an element
97 remove: function(elem, types, handler) {
98 // don't do events on text and comment nodes
99 if ( elem.nodeType == 3 || elem.nodeType == 8 )
102 var events = jQuery.data(elem, "events"), ret, index;
105 // Unbind all events for the element
106 if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
107 for ( var type in events )
108 this.remove( elem, type + (types || "") );
110 // types is actually an event object here
112 handler = types.handler;
116 // Handle multiple events seperated by a space
117 // jQuery(...).unbind("mouseover mouseout", fn);
118 jQuery.each(types.split(/\s+/), function(index, type){
119 // Namespaced event handlers
120 var namespaces = type.split(".");
121 type = namespaces.shift();
122 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
124 if ( events[type] ) {
125 // remove the given handler for the given type
127 delete events[type][handler.guid];
129 // remove all handlers for the given type
131 for ( handler in events[type] )
132 // Handle the removal of namespaced events
133 if ( namespace.test(events[type][handler].type) )
134 delete events[type][handler];
136 if ( jQuery.event.specialAll[type] )
137 jQuery.event.specialAll[type].teardown.call(elem, namespaces);
139 // remove generic event handler if no more handlers exist
140 for ( ret in events[type] ) break;
142 if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
143 if (elem.removeEventListener)
144 elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
145 else if (elem.detachEvent)
146 elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
155 // Remove the expando if it's no longer used
156 for ( ret in events ) break;
158 var handle = jQuery.data( elem, "handle" );
159 if ( handle ) handle.elem = null;
160 jQuery.removeData( elem, "events" );
161 jQuery.removeData( elem, "handle" );
166 trigger: function( e, data, elem, donative, extra, dohandlers) {
167 // Event object or event type
168 var type = e.type || e;
170 // Handle a global trigger
172 // Only trigger if we've ever bound an event for it
173 if ( this.global[type] )
174 jQuery.each( jQuery.cache, function(){
175 if ( this.events && this.events[type] )
176 jQuery.event.trigger( e, data, this.handle.elem, false );
179 // Handle triggering a single element
182 // don't do events on text and comment nodes
183 if ( elem.nodeType == 3 || elem.nodeType == 8 )
186 // Clone the incoming data, if any
187 data = jQuery.makeArray(data);
189 if ( type.indexOf("!") >= 0 ) {
190 type = type.slice(0, -1);
191 var exclusive = true;
194 e = typeof e === "object" ?
195 // jQuery.Event object
198 jQuery.extend( new jQuery.Event(type), e ) :
199 // Just the event type (string)
200 new jQuery.Event(type);
202 e.target = e.target || elem;
203 e.currentTarget = elem;
204 e.exclusive = exclusive;
208 var val, ret, fn = jQuery.isFunction( elem[ type ] );
210 if ( dohandlers !== false ) {
211 // Trigger the event, it is assumed that "handle" is a function
212 var handle = jQuery.data(elem, "handle");
214 val = handle.apply( elem, data );
216 // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
217 if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
221 if ( donative !== false && val !== false ) {
222 var parent = elem.parentNode || elem.ownerDocument;
224 jQuery.event.trigger(e, data, parent, donative);
227 // Extra functions don't get the custom event object
230 // Handle triggering of extra function
231 if ( extra && jQuery.isFunction( extra ) ) {
232 // call the extra function and tack the current return value on the end for possible inspection
233 ret = extra.apply( elem, val == null ? data : data.concat( val ) );
234 // if anything is returned, give it precedence and have it overwrite the previous value
235 if ( ret !== undefined )
239 // Trigger the native events (except for clicks on links)
240 if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
241 this.triggered = true;
244 // prevent IE from throwing an error for some hidden elements
248 this.triggered = false;
254 handle: function(event) {
255 // returned undefined or false
256 var val, ret, all, handlers;
258 event = arguments[0] = jQuery.event.fix( event || window.event );
260 // Namespaced event handlers
261 var namespaces = event.type.split(".");
262 event.type = namespaces.shift();
264 // Cache this now, all = true means, any handler
265 all = !namespaces.length && !event.exclusive;
267 var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
269 handlers = ( jQuery.data(this, "events") || {} )[event.type];
271 for ( var j in handlers ) {
272 var handler = handlers[j];
274 // Filter the functions by class
275 if ( all || namespace.test(handler.type) ) {
276 // Pass in a reference to the handler function itself
277 // So that we can later remove it
278 event.handler = handler;
279 event.data = handler.data;
281 ret = handler.apply( this, arguments );
286 if ( ret === false ) {
287 event.preventDefault();
288 event.stopPropagation();
300 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(" "),
302 fix: function(event) {
303 if ( event[expando] )
306 // store a copy of the original event object
307 // and "clone" to set read-only properties
308 var originalEvent = event;
309 event = new jQuery.Event( originalEvent );
311 for ( var i = this.props.length, prop; i; ){
312 prop = this.props[ --i ];
313 event[ prop ] = originalEvent[ prop ];
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
360 // Make sure the ready event is setup
362 teardown: function() {}
368 setup: function( selector, namespaces ){
369 jQuery.event.add( this, namespaces[0], liveHandler );
371 teardown: function( namespaces ){
372 if ( namespaces.length ) {
373 var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
375 jQuery.each( (jQuery.data(this, "events").live || {}), function(){
376 if ( name.test(this.type) )
381 jQuery.event.remove( this, namespaces[0], liveHandler );
388 jQuery.Event = function( src ){
390 if( src && src.type ){
391 this.originalEvent = src;
392 this.type = src.type;
395 this.timeStamp = src.timeStamp || now();
401 this[expando] = true;
404 jQuery.Event.prototype = {
405 // add preventDefault and stopPropagation since
406 // they will not work on the clone
407 preventDefault: function() {
408 var e = this.originalEvent;
411 // if preventDefault exists run it on the original event
412 if (e.preventDefault)
414 // otherwise set the returnValue property of the original event to false (IE)
415 e.returnValue = false;
417 stopPropagation: function() {
418 var e = this.originalEvent;
421 // if stopPropagation exists run it on the original event
422 if (e.stopPropagation)
424 // otherwise set the cancelBubble property of the original event to true (IE)
425 e.cancelBubble = true;
427 stopImmediatePropagation:function(){
429 this.stopPropagation();
432 // Checks if an event happened on an element within another element
433 // Used in jQuery.event.special.mouseenter and mouseleave handlers
434 var withinElement = function(event) {
435 // Check if mouse(over|out) are still within the same parent element
436 var parent = event.relatedTarget;
437 // Traverse up the tree
438 while ( parent && parent != this )
439 try { parent = parent.parentNode; }
440 catch(e) { parent = this; }
442 if( parent != this ){
443 // set the correct event type
444 event.type = event.data;
445 // handle event if we actually just moused on to a non sub-element
446 jQuery.event.handle.apply( this, arguments );
451 mouseover: 'mouseenter',
452 mouseout: 'mouseleave'
453 }, function( orig, fix ){
454 jQuery.event.special[ fix ] = {
456 jQuery.event.add( this, orig, withinElement, fix );
458 teardown: function(){
459 jQuery.event.remove( this, orig, withinElement );
465 bind: function( type, data, fn ) {
466 return type == "unload" ? this.one(type, data, fn) : this.each(function(){
467 jQuery.event.add( this, type, fn || data, fn && data );
471 one: function( type, data, fn ) {
472 var one = jQuery.event.proxy( fn || data, function(event) {
473 jQuery(this).unbind(event, one);
474 return (fn || data).apply( this, arguments );
476 return this.each(function(){
477 jQuery.event.add( this, type, one, fn && data);
481 unbind: function( type, fn ) {
482 return this.each(function(){
483 jQuery.event.remove( this, type, fn );
487 trigger: function( type, data, fn ) {
488 return this.each(function(){
489 jQuery.event.trigger( type, data, this, true, fn );
493 triggerHandler: function( type, data, fn ) {
494 return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
497 toggle: function( fn ) {
498 // Save reference to arguments for access in closure
499 var args = arguments, i = 1;
501 // link all the functions, so any of them can unbind this click handler
502 while( i < args.length )
503 jQuery.event.proxy( fn, args[i++] );
505 return this.click( jQuery.event.proxy( fn, function(event) {
506 // Figure out which function to execute
507 this.lastToggle = ( this.lastToggle || 0 ) % i;
509 // Make sure that clicks stop
510 event.preventDefault();
512 // and execute the function
513 return args[ this.lastToggle++ ].apply( this, arguments ) || false;
517 hover: function(fnOver, fnOut) {
518 return this.mouseenter(fnOver).mouseleave(fnOut);
521 ready: function(fn) {
522 // Attach the listeners
525 // If the DOM is already ready
526 if ( jQuery.isReady )
527 // Execute the function immediately
528 fn.call( document, jQuery );
530 // Otherwise, remember the function for later
532 // Add the function to the wait list
533 jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
538 live: function( type, fn ){
539 jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
543 die: function( type, fn ){
544 jQuery(document).unbind( liveConvert(type, this.selector), fn );
549 function liveHandler( event ){
550 var check = RegExp("(^|\\.)" + event.type + "(\\.|$)");
551 jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
552 if ( check.test(fn.type) ) {
553 var elem = jQuery(event.target).closest(fn.data)[0];
555 jQuery.event.trigger( event.type, fn.data, elem, false, fn, false );
560 function liveConvert(type, selector){
561 return ["live", type, selector.replace(/\./g, "_")].join(".");
567 // Handle when the DOM is ready
569 // Make sure that the DOM is not already loaded
570 if ( !jQuery.isReady ) {
571 // Remember that the DOM is ready
572 jQuery.isReady = true;
574 // If there are functions bound, to execute
575 if ( jQuery.readyList ) {
576 // Execute all of them
577 jQuery.each( jQuery.readyList, function(){
578 this.call( document );
581 // Reset the list of functions
582 jQuery.readyList = null;
585 // Trigger any bound ready events
586 jQuery(document).triggerHandler("ready");
591 var readyBound = false;
593 function bindReady(){
594 if ( readyBound ) return;
597 // Mozilla, Opera and webkit nightlies currently support this event
598 if ( document.addEventListener ) {
599 // Use the handy event callback
600 document.addEventListener( "DOMContentLoaded", function(){
601 document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
605 // If IE event model is used
606 } else if ( document.attachEvent ) {
607 // ensure firing before onload,
608 // maybe late but safe also for iframes
609 document.attachEvent("onreadystatechange", function(){
610 if ( document.readyState === "complete" ) {
611 document.detachEvent( "onreadystatechange", arguments.callee );
616 // If IE and not an iframe
617 // continually check to see if the document is ready
618 if ( document.documentElement.doScroll && !window.frameElement ) (function(){
619 if ( jQuery.isReady ) return;
622 // If IE is used, use the trick by Diego Perini
623 // http://javascript.nwbox.com/IEContentLoaded/
624 document.documentElement.doScroll("left");
626 setTimeout( arguments.callee, 0 );
630 // and execute any waiting functions
635 // A fallback to window.onload, that will always work
636 jQuery.event.add( window, "load", jQuery.ready );
639 jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
640 "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
641 "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
643 // Handle event binding
644 jQuery.fn[name] = function(fn){
645 return fn ? this.bind(name, fn) : this.trigger(name);
649 // Prevent memory leaks in IE
650 // And prevent errors on refresh with events like mouseover in other browsers
651 // Window isn't included so as not to unbind existing unload events
652 jQuery( window ).bind( 'unload', function(){
653 for ( var id in jQuery.cache )
655 if ( id != 1 && jQuery.cache[ id ].handle )
656 jQuery.event.remove( jQuery.cache[ id ].handle.elem );