Adding in cross-browser onbeforeunload support. Fixes #4106.
[jquery.git] / src / event.js
1 /*
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.
5  */
6 jQuery.event = {
7
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 ) {
12                         return;
13                 }
14
15                 // For whatever reason, IE has trouble passing the window object
16                 // around, causing it to be cloned in the process
17                 if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
18                         elem = window;
19                 }
20
21                 // Make sure that the function being executed has a unique ID
22                 if ( !handler.guid ) {
23                         handler.guid = this.guid++;
24                 }
25
26                 // if data is passed, bind to handler
27                 if ( data !== undefined ) {
28                         // Create temporary function pointer to original handler
29                         var fn = handler;
30
31                         // Create unique handler function, wrapped around original handler
32                         handler = this.proxy( fn );
33
34                         // Store data in unique handler
35                         handler.data = data;
36                 }
37
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 ) :
45                                         undefined;
46                         });
47                 // Add elem as a property of the handle function
48                 // This is to prevent a memory leak with non-native
49                 // event in IE.
50                 handle.elem = elem;
51
52                 // Handle multiple events separated by a space
53                 // jQuery(...).bind("mouseover mouseout", fn);
54                 types = types.split( /\s+/ );
55                 var type, i=0;
56                 while ( (type = types[ i++ ]) ) {
57                         // Namespaced event handlers
58                         var namespaces = type.split(".");
59                         type = namespaces.shift();
60                         handler.type = namespaces.slice(0).sort().join(".");
61
62                         // Get the current list of functions bound to this event
63                         var handlers = events[ type ],
64                                 special = this.special[ type ] || {};
65
66                         
67
68                         // Init the event handler queue
69                         if ( !handlers ) {
70                                 handlers = events[ type ] = {};
71
72                                 // Check for a special event handler
73                                 // Only use addEventListener/attachEvent if the special
74                                 // events handler returns false
75                                 if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) {
76                                         // Bind the global event handler to the element
77                                         if ( elem.addEventListener ) {
78                                                 elem.addEventListener( type, handle, false );
79                                         } else if ( elem.attachEvent ) {
80                                                 elem.attachEvent( "on" + type, handle );
81                                         }
82                                 }
83                         }
84                         
85                         if ( special.add ) { 
86                                 var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers ); 
87                                 if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) { 
88                                         modifiedHandler.guid = modifiedHandler.guid || handler.guid; 
89                                         handler = modifiedHandler; 
90                                 } 
91                         } 
92                         
93                         // Add the function to the element's handler list
94                         handlers[ handler.guid ] = handler;
95
96                         // Keep track of which events have been used, for global triggering
97                         this.global[ type ] = true;
98                 }
99
100                 // Nullify elem to prevent memory leaks in IE
101                 elem = null;
102         },
103
104         guid: 1,
105         global: {},
106
107         // Detach an event or set of events from an element
108         remove: function( elem, types, handler ) {
109                 // don't do events on text and comment nodes
110                 if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
111                         return;
112                 }
113
114                 var events = jQuery.data( elem, "events" ), ret, type, fn;
115
116                 if ( events ) {
117                         // Unbind all events for the element
118                         if ( types === undefined || (typeof types === "string" && types.charAt(0) === ".") ) {
119                                 for ( type in events ) {
120                                         this.remove( elem, type + (types || "") );
121                                 }
122                         } else {
123                                 // types is actually an event object here
124                                 if ( types.type ) {
125                                         handler = types.handler;
126                                         types = types.type;
127                                 }
128
129                                 // Handle multiple events separated by a space
130                                 // jQuery(...).unbind("mouseover mouseout", fn);
131                                 types = types.split(/\s+/);
132                                 var i = 0;
133                                 while ( (type = types[ i++ ]) ) {
134                                         // Namespaced event handlers
135                                         var namespaces = type.split(".");
136                                         type = namespaces.shift();
137                                         var all = !namespaces.length,
138                                                 cleaned = jQuery.map( namespaces.slice(0).sort() , function(nm){ return nm.replace(/[^\w\s\.\|`]/g, function(ch){return "\\"+ch;  }); }),
139                                                 namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
140                                                 special = this.special[ type ] || {};
141
142                                         if ( events[ type ] ) {
143                                                 // remove the given handler for the given type
144                                                 if ( handler ) {
145                                                         fn = events[ type ][ handler.guid ];
146                                                         delete events[ type ][ handler.guid ];
147
148                                                 // remove all handlers for the given type
149                                                 } else {
150                                                         for ( var handle in events[ type ] ) {
151                                                                 // Handle the removal of namespaced events
152                                                                 if ( all || namespace.test( events[ type ][ handle ].type ) ) {
153                                                                         delete events[ type ][ handle ];
154                                                                 }
155                                                         }
156                                                 }
157
158                                                 if ( special.remove ) {
159                                                         special.remove.call( elem, namespaces, fn);
160                                                 }
161
162                                                 // remove generic event handler if no more handlers exist
163                                                 for ( ret in events[ type ] ) {
164                                                         break;
165                                                 }
166                                                 if ( !ret ) {
167                                                         if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
168                                                                 if ( elem.removeEventListener ) {
169                                                                         elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
170                                                                 } else if ( elem.detachEvent ) {
171                                                                         elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) );
172                                                                 }
173                                                         }
174                                                         ret = null;
175                                                         delete events[ type ];
176                                                 }
177                                         }
178                                 }
179                         }
180
181                         // Remove the expando if it's no longer used
182                         for ( ret in events ) {
183                                 break;
184                         }
185                         if ( !ret ) {
186                                 var handle = jQuery.data( elem, "handle" );
187                                 if ( handle ) {
188                                         handle.elem = null;
189                                 }
190                                 jQuery.removeData( elem, "events" );
191                                 jQuery.removeData( elem, "handle" );
192                         }
193                 }
194         },
195
196         // bubbling is internal
197         trigger: function( event, data, elem /*, bubbling */ ) {
198                 // Event object or event type
199                 var type = event.type || event,
200                         bubbling = arguments[3];
201
202                 if ( !bubbling ) {
203                         event = typeof event === "object" ?
204                                 // jQuery.Event object
205                                 event[expando] ? event :
206                                 // Object literal
207                                 jQuery.extend( jQuery.Event(type), event ) :
208                                 // Just the event type (string)
209                                 jQuery.Event(type);
210
211                         if ( type.indexOf("!") >= 0 ) {
212                                 event.type = type = type.slice(0, -1);
213                                 event.exclusive = true;
214                         }
215
216                         // Handle a global trigger
217                         if ( !elem ) {
218                                 // Don't bubble custom events when global (to avoid too much overhead)
219                                 event.stopPropagation();
220                                 // Only trigger if we've ever bound an event for it
221                                 if ( this.global[ type ] ) {
222                                         jQuery.each( jQuery.cache, function() {
223                                                 if ( this.events && this.events[type] ) {
224                                                         jQuery.event.trigger( event, data, this.handle.elem );
225                                                 }
226                                         });
227                                 }
228                         }
229
230                         // Handle triggering a single element
231
232                         // don't do events on text and comment nodes
233                         if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
234                                 return undefined;
235                         }
236
237                         // Clean up in case it is reused
238                         event.result = undefined;
239                         event.target = elem;
240
241                         // Clone the incoming data, if any
242                         data = jQuery.makeArray( data );
243                         data.unshift( event );
244                 }
245
246                 event.currentTarget = elem;
247
248                 // Trigger the event, it is assumed that "handle" is a function
249                 var handle = jQuery.data( elem, "handle" );
250                 if ( handle ) {
251                         handle.apply( elem, data );
252                 }
253
254                 var nativeFn, nativeHandler;
255                 try {
256                         nativeFn = elem[ type ];
257                         nativeHandler = elem[ "on" + type ];
258                 // prevent IE from throwing an error for some elements with some event types, see #3533
259                 } catch (e) {}
260
261                 var isClick = jQuery.nodeName(elem, "a") && type === "click";
262
263                 // Trigger the native events (except for clicks on links)
264                 if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
265                         this.triggered = true;
266                         try {
267                                 nativeFn();
268                         // prevent IE from throwing an error for some hidden elements
269                         } catch (e) {}
270
271                 // Handle triggering native .onfoo handlers
272                 } else if ( nativeHandler && nativeHandler.apply( elem, data ) === false ) {
273                         event.result = false;
274                 }
275
276                 this.triggered = false;
277
278                 if ( !event.isPropagationStopped() ) {
279                         var parent = elem.parentNode || elem.ownerDocument;
280                         if ( parent ) {
281                                 jQuery.event.trigger( event, data, parent, true );
282                         }
283                 }
284         },
285
286         handle: function( event ) {
287                 // returned undefined or false
288                 var all, handlers;
289
290                 event = arguments[0] = jQuery.event.fix( event || window.event );
291                 event.currentTarget = this;
292
293                 // Namespaced event handlers
294                 var namespaces = event.type.split(".");
295                 event.type = namespaces.shift();
296
297                 // Cache this now, all = true means, any handler
298                 all = !namespaces.length && !event.exclusive;
299
300                 var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
301
302                 handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
303
304                 for ( var j in handlers ) {
305                         var handler = handlers[ j ];
306
307                         // Filter the functions by class
308                         if ( all || namespace.test(handler.type) ) {
309                                 // Pass in a reference to the handler function itself
310                                 // So that we can later remove it
311                                 event.handler = handler;
312                                 event.data = handler.data;
313
314                                 var ret = handler.apply( this, arguments );
315
316                                 if ( ret !== undefined ) {
317                                         event.result = ret;
318                                         if ( ret === false ) {
319                                                 event.preventDefault();
320                                                 event.stopPropagation();
321                                         }
322                                 }
323
324                                 if ( event.isImmediatePropagationStopped() ) {
325                                         break;
326                                 }
327
328                         }
329                 }
330
331                 return event.result;
332         },
333
334         props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
335
336         fix: function( event ) {
337                 if ( event[ expando ] ) {
338                         return event;
339                 }
340
341                 // store a copy of the original event object
342                 // and "clone" to set read-only properties
343                 var originalEvent = event;
344                 event = jQuery.Event( originalEvent );
345
346                 for ( var i = this.props.length, prop; i; ) {
347                         prop = this.props[ --i ];
348                         event[ prop ] = originalEvent[ prop ];
349                 }
350
351                 // Fix target property, if necessary
352                 if ( !event.target ) {
353                         event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
354                 }
355
356                 // check if target is a textnode (safari)
357                 if ( event.target.nodeType === 3 ) {
358                         event.target = event.target.parentNode;
359                 }
360
361                 // Add relatedTarget, if necessary
362                 if ( !event.relatedTarget && event.fromElement ) {
363                         event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
364                 }
365
366                 // Calculate pageX/Y if missing and clientX/Y available
367                 if ( event.pageX == null && event.clientX != null ) {
368                         var doc = document.documentElement, body = document.body;
369                         event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
370                         event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
371                 }
372
373                 // Add which for key events
374                 if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
375                         event.which = event.charCode || event.keyCode;
376                 }
377
378                 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
379                 if ( !event.metaKey && event.ctrlKey ) {
380                         event.metaKey = event.ctrlKey;
381                 }
382
383                 // Add which for click: 1 == left; 2 == middle; 3 == right
384                 // Note: button is not normalized, so don't use it
385                 if ( !event.which && event.button !== undefined ) {
386                         event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
387                 }
388
389                 return event;
390         },
391
392         proxy: function( fn, proxy, thisObject ) {
393                 if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) {
394                         thisObject = proxy;
395                         proxy = undefined;
396                 }
397                 // FIXME: Should proxy be redefined to be applied with thisObject if defined?
398                 proxy = proxy || function() { return fn.apply( thisObject !== undefined ? thisObject : this, arguments ); };
399                 // Set the guid of unique handler to the same of original handler, so it can be removed
400                 proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
401                 // So proxy can be declared as an argument
402                 return proxy;
403         },
404
405         special: {
406                 ready: {
407                         // Make sure the ready event is setup
408                         setup: bindReady,
409                         teardown: function() {}
410                 },
411
412                 live: {
413                         add: function( proxy, data, namespaces, live ) {
414                                 jQuery.extend( proxy, data || {} );
415
416                                 proxy.guid += data.selector + data.live; 
417                                 jQuery.event.add( this, data.live, liveHandler, data ); 
418                                 
419                         },
420
421                         remove: function( namespaces ) {
422                                 if ( namespaces.length ) {
423                                         var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
424
425                                         jQuery.each( (jQuery.data(this, "events").live || {}), function() {
426                                                 if ( name.test(this.type) ) {
427                                                         remove++;
428                                                 }
429                                         });
430
431                                         if ( remove < 1 ) {
432                                                 jQuery.event.remove( this, namespaces[0], liveHandler );
433                                         }
434                                 }
435                         },
436                         special: {}
437                 },
438                 beforeunload: {
439                         setup: function( data, namespaces, fn ) {
440                                 // We only want to do this special case on windows
441                                 if ( this.setInterval ) {
442                                         this.onbeforeunload = fn;
443                                 }
444
445                                 return false;
446                         },
447                         teardown: function( namespaces, fn ) {
448                                 if ( this.onbeforeunload === fn ) {
449                                         this.onbeforeunload = null;
450                                 }
451                         }
452                 }
453         }
454 };
455
456 jQuery.Event = function( src ){
457         // Allow instantiation without the 'new' keyword
458         if ( !this.preventDefault ) {
459                 return new jQuery.Event( src );
460         }
461
462         // Event object
463         if ( src && src.type ) {
464                 this.originalEvent = src;
465                 this.type = src.type;
466         // Event type
467         } else {
468                 this.type = src;
469         }
470
471         // timeStamp is buggy for some events on Firefox(#3843)
472         // So we won't rely on the native value
473         this.timeStamp = now();
474
475         // Mark it as fixed
476         this[ expando ] = true;
477 };
478
479 function returnFalse() {
480         return false;
481 }
482 function returnTrue() {
483         return true;
484 }
485
486 // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
487 // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
488 jQuery.Event.prototype = {
489         preventDefault: function() {
490                 this.isDefaultPrevented = returnTrue;
491
492                 var e = this.originalEvent;
493                 if ( !e ) {
494                         return;
495                 }
496                 
497                 // if preventDefault exists run it on the original event
498                 if ( e.preventDefault ) {
499                         e.preventDefault();
500                 }
501                 // otherwise set the returnValue property of the original event to false (IE)
502                 e.returnValue = false;
503         },
504         stopPropagation: function() {
505                 this.isPropagationStopped = returnTrue;
506
507                 var e = this.originalEvent;
508                 if ( !e ) {
509                         return;
510                 }
511                 // if stopPropagation exists run it on the original event
512                 if ( e.stopPropagation ) {
513                         e.stopPropagation();
514                 }
515                 // otherwise set the cancelBubble property of the original event to true (IE)
516                 e.cancelBubble = true;
517         },
518         stopImmediatePropagation: function(){
519                 this.isImmediatePropagationStopped = returnTrue;
520                 this.stopPropagation();
521         },
522         isDefaultPrevented: returnFalse,
523         isPropagationStopped: returnFalse,
524         isImmediatePropagationStopped: returnFalse
525 };
526 // Checks if an event happened on an element within another element
527 // Used in jQuery.event.special.mouseenter and mouseleave handlers
528 var withinElement = function( event ) {
529         // Check if mouse(over|out) are still within the same parent element
530         var parent = event.relatedTarget;
531         // Traverse up the tree
532         while ( parent && parent != this ) {
533                 // Firefox sometimes assigns relatedTarget a XUL element
534                 // which we cannot access the parentNode property of
535                 try { parent = parent.parentNode; }
536                 // assuming we've left the element since we most likely mousedover a xul element
537                 catch(e) { break; }
538         }
539
540         if ( parent != this ) {
541                 // set the correct event type
542                 event.type = event.data;
543                 // handle event if we actually just moused on to a non sub-element
544                 jQuery.event.handle.apply( this, arguments );
545         }
546
547 },
548
549 // In case of event delegation, we only need to rename the event.type,
550 // liveHandler will take care of the rest.
551 delegate = function( event ) {
552         event.type = event.data;
553         jQuery.event.handle.apply( this, arguments );
554 };
555
556 // Create mouseenter and mouseleave events
557 jQuery.each({
558         mouseenter: "mouseover",
559         mouseleave: "mouseout"
560 }, function( orig, fix ) {
561         jQuery.event.special[ orig ] = {
562                 setup: function(data){
563                         jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
564                 },
565                 teardown: function(data){
566                         jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
567                 }
568         };
569 });
570
571 // submit delegation
572 jQuery.event.special.submit = {
573         setup: function( data, namespaces, fn ) {
574                 if ( !jQuery.support.submitBubbles && this.nodeName.toLowerCase() !== "form" ) {
575                         jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
576                                 var elem = e.target, type = elem.type;
577
578                                 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
579                                         return trigger( "submit", this, arguments );
580                                 }
581                         });
582          
583                         jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
584                                 var elem = e.target, type = elem.type;
585
586                                 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
587                                         return trigger( "submit", this, arguments );
588                                 }
589                         });
590                 }
591
592                 return false;
593         },
594
595         remove: function( namespaces, fn ) {
596                 jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
597                 jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
598         }
599 };
600
601 // change delegation, happens here so we have bind.
602 jQuery.event.special.change = {
603         filters: {
604                 click: function( e ) { 
605                         var elem = e.target;
606
607                         if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) {
608                                 return trigger( "change", this, arguments );
609                         }
610
611                         return changeFilters.keyup.call( this, e );
612                 }, 
613                 keyup: function( e ) { 
614                         var elem = e.target, data, index = elem.selectedIndex + "";
615
616                         if ( elem.nodeName.toLowerCase() === "select" ) {
617                                 data = jQuery.data( elem, "_change_data" );
618                                 jQuery.data( elem, "_change_data", index );
619
620                                 if ( (elem.type === "select-multiple" || data != null) && data !== index ) {
621                                         return trigger( "change", this, arguments );
622                                 }
623                         }
624                 },
625                 beforeactivate: function( e ) {
626                         var elem = e.target;
627
628                         if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) {
629                                 return trigger( "change", this, arguments );
630                         }
631                 },
632                 blur: function( e ) {
633                         var elem = e.target, nodeName = elem.nodeName.toLowerCase();
634
635                         if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password")))
636                                 && jQuery.data(elem, "_change_data") !== elem.value ) {
637
638                                 return trigger( "change", this, arguments );
639                         }
640                 },
641                 focus: function( e ) {
642                         var elem = e.target, nodeName = elem.nodeName.toLowerCase();
643
644                         if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) {
645                                 jQuery.data( elem, "_change_data", elem.value );
646                         }
647                 }
648         },
649         setup: function( data, namespaces, fn ) {
650                 // return false if we bubble
651                 if ( !jQuery.support.changeBubbles ) {
652                         for ( var type in changeFilters ) {
653                                 jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
654                         }
655                 }
656                 
657                 // always want to listen for change for trigger
658                 return false;
659         },
660         remove: function( namespaces, fn ) {
661                 if ( !jQuery.support.changeBubbles ) {
662                         for ( var type in changeFilters ) {
663                                 jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
664                         }
665                 }
666         }
667 };
668
669 var changeFilters = jQuery.event.special.change.filters;
670
671 function trigger( type, elem, args ) {
672         args[0].type = type;
673         return jQuery.event.handle.apply( elem, args );
674 }
675
676 // Create "bubbling" focus and blur events
677 jQuery.each({
678         focus: "focusin",
679         blur: "focusout"
680 }, function( orig, fix ){
681         var event = jQuery.event,
682                 handle = event.handle;
683         
684         function ieHandler() { 
685                 arguments[0].type = orig;
686                 return handle.apply(this, arguments);
687         }
688
689         event.special[orig] = {
690                 setup:function() {
691                         if ( this.addEventListener ) {
692                                 this.addEventListener( orig, handle, true );
693                         } else {
694                                 event.add( this, fix, ieHandler );
695                         }
696                 }, 
697                 teardown:function() { 
698                         if ( this.removeEventListener ) {
699                                 this.removeEventListener( orig, handle, true );
700                         } else {
701                                 event.remove( this, fix, ieHandler );
702                         }
703                 }
704         };
705 });
706
707 jQuery.fn.extend({
708         // TODO: make bind(), unbind() and one() DRY!
709         bind: function( type, data, fn, thisObject ) {
710                 // Handle object literals
711                 if ( typeof type === "object" ) {
712                         for ( var key in type ) {
713                                 this.bind(key, data, type[key], fn);
714                         }
715                         return this;
716                 }
717                 
718                 if ( jQuery.isFunction( data ) ) {
719                         thisObject = fn;
720                         fn = data;
721                         data = undefined;
722                 }
723                 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
724                 return type === "unload" ? this.one(type, data, fn, thisObject) : this.each(function() {
725                         jQuery.event.add( this, type, fn, data );
726                 });
727         },
728
729         one: function( type, data, fn, thisObject ) {
730                 // Handle object literals
731                 if ( typeof type === "object" ) {
732                         for ( var key in type ) {
733                                 this.one(key, data, type[key], fn);
734                         }
735                         return this;
736                 }
737                 
738                 if ( jQuery.isFunction( data ) ) {
739                         thisObject = fn;
740                         fn = data;
741                         data = undefined;
742                 }
743                 fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
744                 var one = jQuery.event.proxy( fn, function( event ) {
745                         jQuery( this ).unbind( event, one );
746                         return fn.apply( this, arguments );
747                 });
748                 return this.each(function() {
749                         jQuery.event.add( this, type, one, data );
750                 });
751         },
752
753         unbind: function( type, fn ) {
754                 // Handle object literals
755                 if ( typeof type === "object" && !type.preventDefault ) {
756                         for ( var key in type ) {
757                                 this.unbind(key, type[key]);
758                         }
759                         return this;
760                 }
761                 
762                 return this.each(function() {
763                         jQuery.event.remove( this, type, fn );
764                 });
765         },
766
767         trigger: function( type, data ) {
768                 return this.each(function() {
769                         jQuery.event.trigger( type, data, this );
770                 });
771         },
772
773         triggerHandler: function( type, data ) {
774                 if ( this[0] ) {
775                         var event = jQuery.Event( type );
776                         event.preventDefault();
777                         event.stopPropagation();
778                         jQuery.event.trigger( event, data, this[0] );
779                         return event.result;
780                 }
781         },
782
783         toggle: function( fn ) {
784                 // Save reference to arguments for access in closure
785                 var args = arguments, i = 1;
786
787                 // link all the functions, so any of them can unbind this click handler
788                 while( i < args.length ) {
789                         jQuery.event.proxy( fn, args[ i++ ] );
790                 }
791
792                 return this.click( jQuery.event.proxy( fn, function( event ) {
793                         // Figure out which function to execute
794                         var lastToggle = ( jQuery.data( this, 'lastToggle' + fn.guid ) || 0 ) % i;
795                         jQuery.data( this, 'lastToggle' + fn.guid, lastToggle + 1 );
796
797                         // Make sure that clicks stop
798                         event.preventDefault();
799
800                         // and execute the function
801                         return args[ lastToggle ].apply( this, arguments ) || false;
802                 }));
803         },
804
805         hover: function( fnOver, fnOut ) {
806                 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
807         },
808
809         ready: function( fn ) {
810                 // Attach the listeners
811                 bindReady();
812
813                 // If the DOM is already ready
814                 if ( jQuery.isReady ) {
815                         // Execute the function immediately
816                         fn.call( document, jQuery );
817
818                 // Otherwise, remember the function for later
819                 } else {
820                         // Add the function to the wait list
821                         jQuery.readyList.push( fn );
822                 }
823
824                 return this;
825         },
826
827         live: function( type, data, fn, thisObject ) {
828                 if ( jQuery.isFunction( data ) ) {
829                         if ( fn !== undefined ) {
830                                 thisObject = fn;
831                         }
832                         fn = data;
833                         data = undefined;
834                 }
835                 jQuery( this.context ).bind( liveConvert( type, this.selector ), {
836                         data: data, selector: this.selector, live: type
837                 }, fn, thisObject );
838                 return this;
839         },
840
841         die: function( type, fn ) {
842                 jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
843                 return this;
844         }
845 });
846
847 function liveHandler( event ) {
848         var stop = true, elems = [], selectors = [], args = arguments,
849                 related, match, fn, elem, j, i, data,
850                 live = jQuery.extend({}, jQuery.data( this, "events" ).live);
851
852         for ( j in live ) {
853                 fn = live[j];
854                 if ( fn.live === event.type ||
855                                 fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
856
857                         data = fn.data;
858                         if ( !(data.beforeFilter && data.beforeFilter[event.type] && 
859                                         !data.beforeFilter[event.type](event)) ) {
860                                 selectors.push( fn.selector );
861                         }
862                 } else {
863                         delete live[j];
864                 }
865         }
866
867         match = jQuery( event.target ).closest( selectors, event.currentTarget );
868
869         for ( i = 0, l = match.length; i < l; i++ ) {
870                 for ( j in live ) {
871                         fn = live[j];
872                         elem = match[i].elem;
873                         related = null;
874
875                         if ( match[i].selector === fn.selector ) {
876                                 // Those two events require additional checking
877                                 if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
878                                         related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
879                                 }
880
881                                 if ( !related || related !== elem ) {
882                                         elems.push({ elem: elem, fn: fn });
883                                 }
884                         }
885                 }
886         }
887
888         for ( i = 0, l = elems.length; i < l; i++ ) {
889                 match = elems[i];
890                 event.currentTarget = match.elem;
891                 event.data = match.fn.data;
892                 if ( match.fn.apply( match.elem, args ) === false ) {
893                         stop = false;
894                         break;
895                 }
896         }
897
898         return stop;
899 }
900
901 function liveConvert( type, selector ) {
902         return ["live", type, selector//.replace(/[^\w\s\.]/g, function(ch){ return "\\"+ch})
903                                                                   .replace(/\./g, "`")
904                                                                   .replace(/ /g, "|")].join(".");
905 }
906
907 jQuery.extend({
908         isReady: false,
909         readyList: [],
910         // Handle when the DOM is ready
911         ready: function() {
912                 // Make sure that the DOM is not already loaded
913                 if ( !jQuery.isReady ) {
914                         if ( !document.body ) {
915                                 return setTimeout( jQuery.ready, 13 );
916                         }
917
918                         // Remember that the DOM is ready
919                         jQuery.isReady = true;
920
921                         // If there are functions bound, to execute
922                         if ( jQuery.readyList ) {
923                                 // Execute all of them
924                                 var fn, i = 0;
925                                 while ( (fn = jQuery.readyList[ i++ ]) ) {
926                                         fn.call( document, jQuery );
927                                 }
928
929                                 // Reset the list of functions
930                                 jQuery.readyList = null;
931                         }
932
933                         // Trigger any bound ready events
934                         jQuery( document ).triggerHandler( "ready" );
935                 }
936         }
937 });
938
939 var readyBound = false;
940
941 function bindReady() {
942         if ( readyBound ) { return; }
943         readyBound = true;
944
945         // Catch cases where $(document).ready() is called after the
946         // browser event has already occurred.
947         if ( document.readyState === "complete" ) {
948                 return jQuery.ready();
949         }
950
951         // Mozilla, Opera and webkit nightlies currently support this event
952         if ( document.addEventListener ) {
953                 // Use the handy event callback
954                 document.addEventListener( "DOMContentLoaded", function() {
955                         document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
956                         jQuery.ready();
957                 }, false );
958
959         // If IE event model is used
960         } else if ( document.attachEvent ) {
961                 // ensure firing before onload,
962                 // maybe late but safe also for iframes
963                 document.attachEvent("onreadystatechange", function() {
964                         // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
965                         if ( document.readyState === "complete" ) {
966                                 document.detachEvent( "onreadystatechange", arguments.callee );
967                                 jQuery.ready();
968                         }
969                 });
970
971                 // If IE and not a frame
972                 // continually check to see if the document is ready
973                 var toplevel = false;
974
975                 try {
976                         toplevel = window.frameElement == null;
977                 } catch(e){}
978
979                 if ( document.documentElement.doScroll && toplevel ) {
980                         (function() {
981                                 if ( jQuery.isReady ) {
982                                         return;
983                                 }
984
985                                 try {
986                                         // If IE is used, use the trick by Diego Perini
987                                         // http://javascript.nwbox.com/IEContentLoaded/
988                                         document.documentElement.doScroll("left");
989                                 } catch( error ) {
990                                         setTimeout( arguments.callee, 0 );
991                                         return;
992                                 }
993
994                                 // and execute any waiting functions
995                                 jQuery.ready();
996                         })();
997                 }
998         }
999
1000         // A fallback to window.onload, that will always work
1001         jQuery.event.add( window, "load", jQuery.ready );
1002 }
1003
1004 jQuery.each( ("blur focus load resize scroll unload click dblclick " +
1005         "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
1006         "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
1007
1008         // Handle event binding
1009         jQuery.fn[ name ] = function( fn ) {
1010                 return fn ? this.bind( name, fn ) : this.trigger( name );
1011         };
1012 });
1013
1014 // Prevent memory leaks in IE
1015 // Window isn't included so as not to unbind existing unload events
1016 // More info:
1017 //  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
1018 /*@cc_on
1019 jQuery( window ).bind( 'unload', function() {
1020         for ( var id in jQuery.cache ) {
1021                 // Skip the window
1022                 if ( id != 1 && jQuery.cache[ id ].handle ) {
1023                         jQuery.event.remove( jQuery.cache[ id ].handle.elem );
1024                 }
1025         }
1026 });
1027 @*/