.unbind() without any arguments now also unbinds namespaced events. fixes #4609 and...
[jquery.git] / src / event.js
index 5f4b807..ff6067f 100644 (file)
@@ -13,7 +13,7 @@ jQuery.event = {
 
                // For whatever reason, IE has trouble passing the window object
                // around, causing it to be cloned in the process
-               if ( elem.setInterval && elem != window )
+               if ( elem.setInterval && ( elem != window && !elem.frameElement ) )
                        elem = window;
 
                // Make sure that the function being executed has a unique ID
@@ -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;
@@ -59,7 +56,7 @@ jQuery.event = {
 
                        // Get the current list of functions bound to this event
                        var handlers = events[type];
-                       
+
                        if ( jQuery.event.specialAll[type] )
                                jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
 
@@ -119,7 +116,8 @@ jQuery.event = {
                                        // Namespaced event handlers
                                        var namespaces = type.split(".");
                                        type = namespaces.shift();
-                                       var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+                                       var all = !namespaces.length,
+                                               namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
 
                                        if ( events[type] ) {
                                                // remove the given handler for the given type
@@ -128,11 +126,11 @@ 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 ( all || 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 +161,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 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
-               } else {
+                       // 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,6 +241,7 @@ 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(".");
@@ -270,8 +249,8 @@ 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];
 
@@ -302,7 +281,7 @@ jQuery.event = {
                }
        },
 
-       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(" "),
+       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(" "),
 
        fix: function(event) {
                if ( event[expando] )
@@ -354,6 +333,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
@@ -367,7 +347,7 @@ jQuery.event = {
                        teardown: function() {}
                }
        },
-       
+
        specialAll: {
                live: {
                        setup: function( selector, namespaces ){
@@ -375,14 +355,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 );
                                }
                        }
@@ -394,18 +374,19 @@ jQuery.Event = function( src ){
        // Allow instantiation without the 'new' keyword
        if( !this.preventDefault )
                return new jQuery.Event(src);
-       
+
        // Event object
        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;
 };
@@ -461,7 +442,7 @@ var withinElement = function(event) {
        while ( parent && parent != this )
                try { parent = parent.parentNode; }
                catch(e) { parent = this; }
-       
+
        if( parent != this ){
                // set the correct event type
                event.type = event.data;
@@ -469,9 +450,9 @@ var withinElement = function(event) {
                jQuery.event.handle.apply( this, arguments );
        }
 };
-       
-jQuery.each({ 
-       mouseover: 'mouseenter', 
+
+jQuery.each({
+       mouseover: 'mouseenter',
        mouseout: 'mouseleave'
 }, function( orig, fix ){
        jQuery.event.special[ fix ] = {
@@ -481,7 +462,7 @@ jQuery.each({
                teardown: function(){
                        jQuery.event.remove( this, orig, withinElement );
                }
-       };                         
+       };
 });
 
 jQuery.fn.extend({
@@ -507,20 +488,20 @@ 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;
-               }               
+               }
        },
 
        toggle: function( fn ) {
@@ -559,38 +540,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 +604,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 +644,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,9 +678,12 @@ 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
-jQuery( window ).bind( 'unload', function(){ 
+// 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
                if ( id != 1 && jQuery.cache[ id ].handle )
                        jQuery.event.remove( jQuery.cache[ id ].handle.elem );
-}); 
+});