fix for #3533, triggering an event with a colon in the name on a table no longer...
[jquery.git] / src / event.js
index 7a28080..9c1f287 100644 (file)
@@ -60,10 +60,15 @@ jQuery.event = {
                        handler.type = namespaces.slice().sort().join(".");
 
                        // Get the current list of functions bound to this event
-                       var handlers = events[ type ];
-
-                       if ( this.specialAll[ type ] ) {
-                               this.specialAll[ type ].setup.call( elem, data, namespaces );
+                       var handlers = events[ type ],
+                               special = this.special[ type ] || {};
+
+                       if ( special.add ) {
+                               var modifiedHandler = special.add.call( elem, handler, data, namespaces );
+                               if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
+                                       modifiedHandler.guid = modifiedHandler.guid || handler.guid;
+                                       handler = modifiedHandler;
+                               }
                        }
 
                        // Init the event handler queue
@@ -73,7 +78,7 @@ jQuery.event = {
                                // Check for a special event handler
                                // Only use addEventListener/attachEvent if the special
                                // events handler returns false
-                               if ( !this.special[ type ] || this.special[ type ].setup.call( elem, data, namespaces ) === false ) {
+                               if ( !special.setup || special.setup.call( elem, data, namespaces ) === false ) {
                                        // Bind the global event handler to the element
                                        if ( elem.addEventListener ) {
                                                elem.addEventListener( type, handle, false );
@@ -128,9 +133,10 @@ jQuery.event = {
                                        var namespaces = type.split(".");
                                        type = namespaces.shift();
                                        var all = !namespaces.length,
-                                               namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+                                               namespace = new RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"),
+                                               special = this.special[ type ] || {};
 
-                                       if ( events[type] ) {
+                                       if ( events[ type ] ) {
                                                // remove the given handler for the given type
                                                if ( handler ) {
                                                        delete events[ type ][ handler.guid ];
@@ -145,8 +151,8 @@ jQuery.event = {
                                                        }
                                                }
 
-                                               if ( this.specialAll[ type ] ) {
-                                                       this.specialAll[ type ].teardown.call( elem, namespaces );
+                                               if ( special.remove ) {
+                                                       special.remove.call( elem, namespaces );
                                                }
 
                                                // remove generic event handler if no more handlers exist
@@ -154,7 +160,7 @@ jQuery.event = {
                                                        break;
                                                }
                                                if ( !ret ) {
-                                                       if ( !this.special[ type ] || this.special[ type ].teardown.call( elem, namespaces ) === false ) {
+                                                       if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
                                                                if ( elem.removeEventListener ) {
                                                                        elem.removeEventListener( type, jQuery.data( elem, "handle" ), false );
                                                                } else if ( elem.detachEvent ) {
@@ -184,9 +190,10 @@ jQuery.event = {
        },
 
        // bubbling is internal
-       trigger: function( event, data, elem, bubbling ) {
+       trigger: function( event, data, elem /*, bubbling */ ) {
                // Event object or event type
-               var type = event.type || event;
+               var type = event.type || event,
+                       bubbling = arguments[3];
 
                if ( !bubbling ) {
                        event = typeof event === "object" ?
@@ -208,11 +215,11 @@ jQuery.event = {
                                event.stopPropagation();
                                // Only trigger if we've ever bound an event for it
                                if ( this.global[ type ] ) {
-                                       for ( var cached in jQuery.cache ) {
-                                               if ( cached.events && cached.events[ type ] ) {
-                                                       this.trigger( event, data, cached.handle.elem );
+                                       jQuery.each( jQuery.cache, function() {
+                                               if ( this.events && this.events[type] ) {
+                                                       jQuery.event.trigger( event, data, this.handle.elem );
                                                }
-                                       }
+                                       });
                                }
                        }
 
@@ -240,16 +247,22 @@ jQuery.event = {
                        handle.apply( elem, data );
                }
 
+               var nativeFn, nativeHandler;
+               try {
+                       nativeFn = elem[ type ];
+                       nativeHandler = elem[ "on" + type ];
+               // prevent IE from throwing an error for some elements with some event types, see #3533
+               } 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 ) {
+               if ( (!nativeFn || (jQuery.nodeName(elem, 'a') && type === "click")) && nativeHandler && nativeHandler.apply( elem, data ) === false ) {
                        event.result = false;
                }
 
                // Trigger the native events (except for clicks on links)
-               if ( !bubbling && elem[ type ] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type === "click") ) {
+               if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type === "click") ) {
                        this.triggered = true;
                        try {
-                               elem[ type ]();
+                               nativeFn();
                        // prevent IE from throwing an error for some hidden elements
                        } catch (e) {}
                }
@@ -345,8 +358,8 @@ jQuery.event = {
                // Calculate pageX/Y if missing and clientX/Y available
                if ( event.pageX == null && event.clientX != null ) {
                        var doc = document.documentElement, body = document.body;
-                       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
-                       event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc.clientTop  || 0);
+                       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+                       event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
                }
 
                // Add which for key events
@@ -361,15 +374,20 @@ jQuery.event = {
 
                // Add which for click: 1 == left; 2 == middle; 3 == right
                // Note: button is not normalized, so don't use it
-               if ( !event.which && event.button ) {
+               if ( !event.which && event.button !== undefined ) {
                        event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
                }
 
                return event;
        },
 
-       proxy: function( fn, proxy ) {
-               proxy = proxy || function() { return fn.apply( this, arguments ); };
+       proxy: function( fn, proxy, thisObject ) {
+               if ( proxy !== undefined && !jQuery.isFunction( proxy ) ) {
+                       thisObject = proxy;
+                       proxy = undefined;
+               }
+               // FIXME: Should proxy be redefined to be applied with thisObject if defined?
+               proxy = proxy || function() { return fn.apply( thisObject !== undefined ? thisObject : 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
@@ -381,15 +399,16 @@ jQuery.event = {
                        // Make sure the ready event is setup
                        setup: bindReady,
                        teardown: function() {}
-               }
-       },
+               },
 
-       specialAll: {
                live: {
-                       setup: function( selector, namespaces ) {
-                               jQuery.event.add( this, namespaces[0], liveHandler );
+                       add: function( proxy, data, namespaces ) {
+                               jQuery.extend( proxy, data || {} );
+                               proxy.guid += data.selector + data.live;
+                               jQuery.event.add( this, data.live, liveHandler );
                        },
-                       teardown:  function( namespaces ) {
+
+                       remove: function( namespaces ) {
                                if ( namespaces.length ) {
                                        var remove = 0, name = new RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
 
@@ -484,8 +503,11 @@ var withinElement = function( event ) {
        var parent = event.relatedTarget;
        // Traverse up the tree
        while ( parent && parent != this ) {
+               // Firefox sometimes assigns relatedTarget a XUL element
+               // which we cannot access the parentNode property of
                try { parent = parent.parentNode; }
-               catch(e) { parent = this; }
+               // assuming we've left the element since we most likely mousedover a xul element
+               catch(e) { break; }
        }
 
        if ( parent != this ) {
@@ -511,19 +533,35 @@ jQuery.each({
 });
 
 jQuery.fn.extend({
-       bind: function( type, data, fn ) {
-               return type === "unload" ? this.one(type, data, fn) : this.each(function() {
-                       jQuery.event.add( this, type, fn || data, fn && data );
+       bind: function( type, data, fn, thisObject ) {
+               if ( jQuery.isFunction( data ) ) {
+                       if ( fn !== undefined ) {
+                               thisObject = fn;
+                       }
+                       fn = data;
+                       data = undefined;
+               }
+               fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
+               return type === "unload" ? this.one(type, data, fn, thisObject) : this.each(function() {
+                       jQuery.event.add( this, type, fn, data );
                });
        },
 
-       one: function( type, data, fn ) {
-               var one = jQuery.event.proxy( fn || data, function( event ) {
+       one: function( type, data, fn, thisObject ) {
+               if ( jQuery.isFunction( data ) ) {
+                       if ( fn !== undefined ) {
+                               thisObject = fn;
+                       }
+                       fn = data;
+                       data = undefined;
+               }
+               fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
+               var one = jQuery.event.proxy( fn, function( event ) {
                        jQuery( this ).unbind( event, one );
-                       return (fn || data).apply( this, arguments );
+                       return fn.apply( this, arguments );
                });
                return this.each(function() {
-                       jQuery.event.add( this, type, one, fn && data );
+                       jQuery.event.add( this, type, one, data );
                });
        },
 
@@ -571,7 +609,7 @@ jQuery.fn.extend({
        },
 
        hover: function( fnOver, fnOut ) {
-               return this.mouseenter( fnOver ).mouseleave( fnOut );
+               return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
        },
 
        ready: function( fn ) {
@@ -592,28 +630,32 @@ jQuery.fn.extend({
                return this;
        },
 
-       live: function( type, fn ) {
-               var proxy = jQuery.event.proxy( fn );
-               proxy.guid += this.selector + type;
-
-               jQuery( this.context ).bind( liveConvert( type, this.selector ), this.selector, proxy );
-
+       live: function( type, data, fn, thisObject ) {
+               if ( jQuery.isFunction( data ) ) {
+                       if ( fn !== undefined ) {
+                               thisObject = fn;
+                       }
+                       fn = data;
+                       data = undefined;
+               }
+               jQuery( this.context ).bind( liveConvert( type, this.selector ), {
+                       data: data, selector: this.selector, live: type
+               }, fn, thisObject );
                return this;
        },
 
        die: function( type, fn ) {
-               jQuery( this.context ).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
+               jQuery( this.context ).unbind( liveConvert( type, this.selector ), fn ? { guid: fn.guid + this.selector + type } : null );
                return this;
        }
 });
 
 function liveHandler( event ) {
-       var check = new RegExp("(^|\\.)" + event.type + "(\\.|$)"),
-               stop = true, elems = [];
+       var stop = true, elems = [], args = arguments;
 
        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 ( fn.live === event.type ) {
+                       var elem = jQuery( event.target ).closest( fn.selector )[0];
                        if ( elem ) {
                                elems.push({ elem: elem, fn: fn });
                        }
@@ -626,7 +668,8 @@ function liveHandler( event ) {
 
        jQuery.each(elems, function() {
                event.currentTarget = this.elem;
-               if ( this.fn.call( this.elem, event, this.fn.data ) === false ) {
+               event.data = this.fn.data;
+               if ( this.fn.apply( this.elem, args ) === false ) {
                        return (stop = false);
                }
        });
@@ -722,7 +765,7 @@ jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
 
        // Handle event binding
        jQuery.fn[ name ] = function( fn ) {
-               return fn ? this.bind (name, fn ) : this.trigger( name );
+               return fn ? this.bind( name, fn ) : this.trigger( name );
        };
 });