fix for #4189, live/die now work with contexts other than just document
[jquery.git] / src / event.js
index 5f4b807..e5f6a45 100644 (file)
@@ -26,10 +26,7 @@ jQuery.event = {
                        var fn = handler;
 
                        // Create unique handler function, wrapped around original handler
-                       handler = this.proxy( fn, function() {
-                               // Pass arguments and context to original handler
-                               return fn.apply(this, arguments);
-                       });
+                       handler = this.proxy( fn );
 
                        // Store data in unique handler
                        handler.data = data;
@@ -119,7 +116,7 @@ jQuery.event = {
                                        // Namespaced event handlers
                                        var namespaces = type.split(".");
                                        type = namespaces.shift();
-                                       var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+                                       var namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
 
                                        if ( events[type] ) {
                                                // remove the given handler for the given type
@@ -128,10 +125,10 @@ jQuery.event = {
 
                                                // remove all handlers for the given type
                                                else
-                                                       for ( handler in events[type] )
+                                                       for ( var handle in events[type] )
                                                                // Handle the removal of namespaced events
-                                                               if ( namespace.test(events[type][handler].type) )
-                                                                       delete events[type][handler];
+                                                               if ( namespace.test(events[type][handle].type) )
+                                                                       delete events[type][handle];
                                                                        
                                                if ( jQuery.event.specialAll[type] )
                                                        jQuery.event.specialAll[type].teardown.call(elem, namespaces);
@@ -163,98 +160,78 @@ jQuery.event = {
                }
        },
 
-       trigger: function( event, data, elem, extra) {
+       // bubbling is internal
+       trigger: function( event, data, elem, bubbling ) {
                // Event object or event type
                var type = event.type || event;
 
-               event = typeof event === "object" ?
-                       // jQuery.Event object
-                       event[expando] ? event :
-                       // Object literal
-                       jQuery.extend( jQuery.Event(type), event ) :
-                       // Just the event type (string)
-                       jQuery.Event(type);
-
-               if ( type.indexOf("!") >= 0 ) {
-                       event.type = type = type.slice(0, -1);
-                       event.exclusive = true;
-               }
-                       
-               // Handle a global trigger
-               if ( !elem ) {
-                       // Don't bubble custom events when global (to avoid too much overhead)
-                       event.stopPropagation();
-                       // Only trigger if we've ever bound an event for it
-                       if ( this.global[type] )
-                               jQuery.each( jQuery.cache, function(){
-                                       if ( this.events && this.events[type] )
-                                               jQuery.event.trigger( event, data, this.handle.elem );
-                               });
+               if( !bubbling ){
+                       event = typeof event === "object" ?
+                               // jQuery.Event object
+                               event[expando] ? event :
+                               // Object literal
+                               jQuery.extend( jQuery.Event(type), event ) :
+                               // Just the event type (string)
+                               jQuery.Event(type);
+
+                       if ( type.indexOf("!") >= 0 ) {
+                               event.type = type = type.slice(0, -1);
+                               event.exclusive = true;
+                       }
 
-               // Handle triggering a single element
-               } else {
+                       // Handle a global trigger
+                       if ( !elem ) {
+                               // Don't bubble custom events when global (to avoid too much overhead)
+                               event.stopPropagation();
+                               // Only trigger if we've ever bound an event for it
+                               if ( this.global[type] )
+                                       jQuery.each( jQuery.cache, function(){
+                                               if ( this.events && this.events[type] )
+                                                       jQuery.event.trigger( event, data, this.handle.elem );
+                                       });
+                       }
+
+                       // Handle triggering a single element
 
                        // don't do events on text and comment nodes
-                       if ( elem.nodeType == 3 || elem.nodeType == 8 )
+                       if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
                                return undefined;
-
+                       
+                       // Clean up in case it is reused
+                       event.result = undefined;
+                       event.target = elem;
+                       
                        // Clone the incoming data, if any
                        data = jQuery.makeArray(data);
-
-                       // AT_TARGET phase (not bubbling)
-                       if( !event.target ){
-                               // Clean up in case it is reused
-                               event.result = undefined;
-                               event.target = elem;
-                       }
-
-                       // Fix for custom events
-                       event.currentTarget = elem;
-
                        data.unshift( event );
+               }
 
-                       var fn = jQuery.isFunction( elem[ type ] );
-
-                       // Trigger the event, it is assumed that "handle" is a function
-                       var handle = jQuery.data(elem, "handle");
-                       if ( handle )
-                               handle.apply( elem, data );
-
-                       // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
-                       if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
-                               event.result = false;
-
-                       // Extra functions don't get the custom event object
-                       data.shift();
+               event.currentTarget = elem;
 
-                       // Handle triggering of extra function
-                       if ( extra && jQuery.isFunction( extra ) ) {
-                               // call the extra function and tack the current return value on the end for possible inspection
-                               var ret = extra.apply( elem, event.result == null ? data : data.concat( event.result ) );
-                               // if anything is returned, give it precedence and have it overwrite the previous value
-                               if ( ret !== undefined )
-                                       event.result = ret;
-                       }
+               // Trigger the event, it is assumed that "handle" is a function
+               var handle = jQuery.data(elem, "handle");
+               if ( handle )
+                       handle.apply( elem, data );
 
-                       // Trigger the native events (except for clicks on links)
-                       if ( event.target === elem && fn && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
-                               this.triggered = true;
-                               try {
-                                       elem[ type ]();
-                               // prevent IE from throwing an error for some hidden elements
-                               } catch (e) {}
-                       }
+               // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+               if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+                       event.result = false;
 
-                       if ( !event.isPropagationStopped() ) {
-                               var parent = elem.parentNode || elem.ownerDocument;
-                               if ( parent )
-                                       jQuery.event.trigger(event, data, parent);
-                       }
+               // Trigger the native events (except for clicks on links)
+               if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+                       this.triggered = true;
+                       try {
+                               elem[ type ]();
+                       // prevent IE from throwing an error for some hidden elements
+                       } catch (e) {}
+               }
 
-                       // Clean up, in case the event object is reused
-                       event.target = null;
+               this.triggered = false;
 
-                       this.triggered = false;
+               if ( !event.isPropagationStopped() ) {
+                       var parent = elem.parentNode || elem.ownerDocument;
+                       if ( parent )
+                               jQuery.event.trigger(event, data, parent, true);
                }
        },
 
@@ -263,7 +240,8 @@ jQuery.event = {
                var all, handlers;
 
                event = arguments[0] = jQuery.event.fix( event || window.event );
-
+               event.currentTarget = this;
+               
                // Namespaced event handlers
                var namespaces = event.type.split(".");
                event.type = namespaces.shift();
@@ -271,7 +249,7 @@ jQuery.event = {
                // Cache this now, all = true means, any handler
                all = !namespaces.length && !event.exclusive;
                
-               var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+               var namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
 
                handlers = ( jQuery.data(this, "events") || {} )[event.type];
 
@@ -354,6 +332,7 @@ jQuery.event = {
        },
 
        proxy: function( fn, proxy ){
+               proxy = proxy || function(){ return fn.apply(this, arguments); };
                // Set the guid of unique handler to the same of original handler, so it can be removed
                proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
                // So proxy can be declared as an argument
@@ -375,14 +354,14 @@ jQuery.event = {
                        },
                        teardown:  function( namespaces ){
                                if ( namespaces.length ) {
-                                       var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
+                                       var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
                                        
                                        jQuery.each( (jQuery.data(this, "events").live || {}), function(){
                                                if ( name.test(this.type) )
                                                        remove++;
                                        });
                                        
-                                       if ( remove <= 1 )
+                                       if ( remove < 1 )
                                                jQuery.event.remove( this, namespaces[0], liveHandler );
                                }
                        }
@@ -399,13 +378,14 @@ jQuery.Event = function( src ){
        if( src && src.type ){
                this.originalEvent = src;
                this.type = src.type;
-               
-               // Fix timeStamp
-               this.timeStamp = src.timeStamp || now();
        // Event type
        }else
                this.type = src;
 
+       // timeStamp is buggy for some events on Firefox(#3843)
+       // So we won't rely on the native value
+       this.timeStamp = now();
+       
        // Mark it as fixed
        this[expando] = true;
 };
@@ -507,18 +487,18 @@ jQuery.fn.extend({
                });
        },
 
-       trigger: function( type, data, fn ) {
+       trigger: function( type, data ) {
                return this.each(function(){
-                       jQuery.event.trigger( type, data, this, fn );
+                       jQuery.event.trigger( type, data, this );
                });
        },
 
-       triggerHandler: function( type, data, fn ) {
+       triggerHandler: function( type, data ) {
                if( this[0] ){
                        var event = jQuery.Event(type);
                        event.preventDefault();
                        event.stopPropagation();
-                       jQuery.event.trigger( event, data, this[0], fn );
+                       jQuery.event.trigger( event, data, this[0] );
                        return event.result;
                }               
        },
@@ -559,38 +539,54 @@ jQuery.fn.extend({
                // Otherwise, remember the function for later
                else
                        // Add the function to the wait list
-                       jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+                       jQuery.readyList.push( fn );
 
                return this;
        },
        
        live: function( type, fn ){
-               jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn );
+               var proxy = jQuery.event.proxy( fn );
+               proxy.guid += this.selector + type;
+
+               jQuery( this.context ).bind( liveConvert(type, this.selector), this.selector, proxy );
+
                return this;
        },
        
        die: function( type, fn ){
-               jQuery(document).unbind( liveConvert(type, this.selector), fn );
+               jQuery( this.context ).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
                return this;
        }
 });
 
 function liveHandler( event ){
-       var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
-               stop = true;
+       var check = new RegExp("(^|\\.)" + event.type + "(\\.|$)"),
+               stop = true,
+               elems = [];
 
        jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
                if ( check.test(fn.type) ) {
                        var elem = jQuery(event.target).closest(fn.data)[0];
-                       if ( elem && fn.call(elem, event, fn.data) === false )
-                               stop = false;
+                       if ( elem )
+                               elems.push({ elem: elem, fn: fn });
                }
        });
+
+       elems.sort(function(a,b) {
+               return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
+       });
+       
+       jQuery.each(elems, function(){
+               event.currentTarget = this.elem;
+               if ( this.fn.call(this.elem, event, this.fn.data) === false )
+                       return (stop = false);
+       });
+
        return stop;
 }
 
 function liveConvert(type, selector){
-       return ["live", type, selector.replace(/\./g, "_")].join(".");
+       return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
 }
 
 jQuery.extend({
@@ -607,7 +603,7 @@ jQuery.extend({
                        if ( jQuery.readyList ) {
                                // Execute all of them
                                jQuery.each( jQuery.readyList, function(){
-                                       this.call( document );
+                                       this.call( document, jQuery );
                                });
 
                                // Reset the list of functions
@@ -647,7 +643,7 @@ function bindReady(){
 
                // If IE and not an iframe
                // continually check to see if the document is ready
-               if ( document.documentElement.doScroll && !window.frameElement ) (function(){
+               if ( document.documentElement.doScroll && window == window.top ) (function(){
                        if ( jQuery.isReady ) return;
 
                        try {
@@ -681,6 +677,9 @@ jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
 // Prevent memory leaks in IE
 // And prevent errors on refresh with events like mouseover in other browsers
 // Window isn't included so as not to unbind existing unload events
+// More info:
+//  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
+//  - https://bugzilla.mozilla.org/show_bug.cgi?id=252542
 jQuery( window ).bind( 'unload', function(){ 
        for ( var id in jQuery.cache )
                // Skip the window