.bind() now accepts an optional thisObject as the last argument which is used to...
[jquery.git] / src / event.js
index 7a28080..1aafcfa 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 ) {
@@ -208,11 +214,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 );
                                                }
-                                       }
+                                       });
                                }
                        }
 
@@ -345,8 +351,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
@@ -368,8 +374,13 @@ jQuery.event = {
                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,28 +392,17 @@ 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 ) {
-                               if ( namespaces.length ) {
-                                       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 ) {
-                                               jQuery.event.remove( this, namespaces[0], liveHandler );
-                                       }
-                               }
+                       teardown: function( namespaces ) {
+                               jQuery.event.remove( this, namespaces[0], liveHandler );
                        }
                }
        }
@@ -484,8 +484,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 +514,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, thisObject );
                });
        },
 
-       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, thisObject );
                });
        },
 
@@ -571,7 +590,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 +611,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 +649,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 +746,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 );
        };
 });