]> source.dussan.org Git - jquery.git/commitdiff
jQuery 1.7 event work:
authorDave Methvin <dave.methvin@gmail.com>
Fri, 29 Jul 2011 00:43:23 +0000 (20:43 -0400)
committertimmywil <timmywillisn@gmail.com>
Mon, 19 Sep 2011 19:42:30 +0000 (15:42 -0400)
Add .on() and .off() methods.
Write existing methods in terms of on/off.
Rewrite delegated handling to remove "live" event.
Fix existing code for jQuery style guide.
Fix existing bug in unit tests calling .undelegate()

src/event.js
test/unit/event.js

index 1161386fdd27a05d1ac14b3a8f4da180444c9b22..fd922193f9759b9093dc071690df51c7ca3ac7e9 100644 (file)
@@ -5,8 +5,19 @@ var rnamespaces = /\.(.*)$/,
        rperiod = /\./g,
        rspaces = / /g,
        rescape = /[^\w\s.|`]/g,
-       fcleanup = function( nm ) {
-               return nm.replace(rescape, "\\$&");
+       rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+       rhoverHack =  /\bhover(\.\S+)?/,
+       rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])(:first-child|:last-child|:empty)?$/,
+       delegateTypeMap = {
+               focus: "focusin",
+               blur: "focusout",
+               mouseenter: "mouseover",
+               mouseleave: "mouseout"
+       },
+       quickPseudoMap = {
+               ":empty": "firstChild",
+               ":first-child": "previousSibling",
+               ":last-child": "nextSibling"
        };
 
 /*
@@ -16,48 +27,34 @@ var rnamespaces = /\.(.*)$/,
  */
 jQuery.event = {
 
-       // Bind an event to an element
-       // Original by Dean Edwards
-       add: function( elem, types, handler, data ) {
-               if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
-                       return;
-               }
+       add: function( elem, types, handler, data, selector ) {
 
-               if ( handler === false ) {
-                       handler = returnFalse;
-               } else if ( !handler ) {
-                       // Fixes bug #7229. Fix recommended by jdalton
+               var elemData, eventHandle, events,
+                       t, tns, type, namespaces, handleObj,
+                       handleObjIn, quick, handlers, special;
+
+               // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+               if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
                        return;
                }
 
-               var handleObjIn, handleObj;
-
+               // Caller can pass in an object of custom data in lieu of the handler
                if ( handler.handler ) {
                        handleObjIn = handler;
                        handler = handleObjIn.handler;
                }
 
-               // Make sure that the function being executed has a unique ID
+               // Make sure that the handler has a unique ID, used to find/remove it later
                if ( !handler.guid ) {
                        handler.guid = jQuery.guid++;
                }
 
-               // Init the element's event structure
-               var elemData = jQuery._data( elem );
-
-               // If no elemData is found then we must be trying to bind to one of the
-               // banned noData elements
-               if ( !elemData ) {
-                       return;
-               }
-
-               var events = elemData.events,
-                       eventHandle = elemData.handle;
-
+               // Init the element's event structure and main handler, if this is the first
+               events = elemData.events;
                if ( !events ) {
                        elemData.events = events = {};
                }
-
+               eventHandle = elemData.handle;
                if ( !eventHandle ) {
                        elemData.handle = eventHandle = function( e ) {
                                // Discard the second event of a jQuery.event.trigger() and
@@ -66,50 +63,35 @@ jQuery.event = {
                                        jQuery.event.handle.apply( eventHandle.elem, arguments ) :
                                        undefined;
                        };
+                       // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+                       eventHandle.elem = elem;
                }
 
-               // Add elem as a property of the handle function
-               // This is to prevent a memory leak with non-native events in IE.
-               eventHandle.elem = elem;
-
                // Handle multiple events separated by a space
                // jQuery(...).bind("mouseover mouseout", fn);
-               types = types.split(" ");
-
-               var type, i = 0, namespaces;
-
-               while ( (type = types[ i++ ]) ) {
-                       handleObj = handleObjIn ?
-                               jQuery.extend({}, handleObjIn) :
-                               { handler: handler, data: data };
-
-                       // Namespaced event handlers
-                       if ( type.indexOf(".") > -1 ) {
-                               namespaces = type.split(".");
-                               type = namespaces.shift();
-                               handleObj.namespace = namespaces.slice(0).sort().join(".");
-
-                       } else {
-                               namespaces = [];
-                               handleObj.namespace = "";
-                       }
-
-                       handleObj.type = type;
-                       if ( !handleObj.guid ) {
-                               handleObj.guid = handler.guid;
-                       }
-
-                       // Get the current list of functions bound to this event
-                       var handlers = events[ type ],
-                               special = jQuery.event.special[ type ] || {};
+               types = types.replace( rhoverHack, "mouseover$1 mouseout$1" ).split(" ");
+               for ( t = 0; t < types.length; t++ ) {
+
+                       tns = rtypenamespace.exec( types[t]  ) || [];
+                       type = tns[1];
+                       namespaces = (tns[2] || "").split(".").sort();
+                       handleObj = jQuery.extend({
+                               type: type, 
+                               data: data, 
+                               handler: handler,
+                               guid: handler.guid,
+                               selector: selector,
+                               namespace: namespaces.join(".")
+                       }, handleObjIn);
+                       special = jQuery.event.special[ type ] || {};
 
-                       // Init the event handler queue
+                       // Init the event handler queue if we're the first
+                       handlers = events[ type ];
                        if ( !handlers ) {
                                handlers = events[ type ] = [];
-
-                               // Check for a special event handler
-                               // Only use addEventListener/attachEvent if the special
-                               // events handler returns false
+                               handlers.delegateCount = 0;
+                       
+                               // Only use addEventListener/attachEvent if the special events handler returns false
                                if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
                                        // Bind the global event handler to the element
                                        if ( elem.addEventListener ) {
@@ -121,6 +103,27 @@ jQuery.event = {
                                }
                        }
 
+                       // Delegated event setup
+                       if ( selector ) {
+                               // Rename non-bubbling events to their bubbling counterparts
+                               if ( delegateTypeMap[ type ] ) {
+                                       handleObj.preType = type;
+                                       handleObj.type = delegateTypeMap[ type ];
+                               }
+
+                               // Pre-analyze selector so we can process it quickly on event dispatch
+                               quick = handleObj.quick = rquickIs.exec(selector);
+                               if ( quick ) {
+                                       //   0  1    2   3      4         5          6
+                                       // [ _, tag, id, class, attrName, attrValue, pseudo(:empty :first-child :last-child) ]
+                                       quick[1] = ( quick[1] || "" ).toLowerCase();
+                                       quick[3] = quick[3] && new RegExp( "\\b" + quick[3] + "\\b" );
+                                       quick[6] = quickPseudoMap[ quick[6] ];
+                               } else if ( jQuery.expr.match.POS.test( selector ) ) {
+                                       handleObj.isPositional = true;
+                               }
+                       }
+
                        if ( special.add ) {
                                special.add.call( elem, handleObj );
 
@@ -129,10 +132,14 @@ jQuery.event = {
                                }
                        }
 
-                       // Add the function to the element's handler list
-                       handlers.push( handleObj );
-
-                       // Keep track of which events have been used, for event optimization
+                       // Add to the element's handler list, delegates in front
+                       if ( selector ) {
+                               handlers.splice( handlers.delegateCount++, 0, handleObj );
+                       } else {
+                               handlers.push( handleObj );
+                       }
+                       
+                       // Keep track of which events have ever been used, for event optimization
                        jQuery.event.global[ type ] = true;
                }
 
@@ -143,125 +150,90 @@ jQuery.event = {
        global: {},
 
        // Detach an event or set of events from an element
-       remove: function( elem, types, handler, pos ) {
-               // don't do events on text and comment nodes
-               if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
-                       return;
-               }
-
-               if ( handler === false ) {
-                       handler = returnFalse;
-               }
-
-               var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
-                       elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
-                       events = elemData && elemData.events;
+       remove: function( elem, types, handler, selector ) {
 
-               if ( !elemData || !events ) {
+               var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+                       t, tns, type, namespaces, 
+                       j, events, special, handle, eventType, handleObj;
+                               
+               if ( !elemData || !(events = elemData.events) ) {
                        return;
                }
 
-               // types is actually an event object here
-               if ( types && types.type ) {
+               // For removal, types can be an Event object
+               if ( types && types.type && types.handler ) {
                        handler = types.handler;
                        types = types.type;
+                       selector = types.selector;
                }
 
-               // Unbind all events for the element
-               if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
-                       types = types || "";
+               // Once for each type.namespace in types; type may be omitted
+               types = (types || "").replace( rhoverHack, "mouseover$1 mouseout$1" ).split(" ");
+               for ( t = 0; t < types.length; t++ ) {
+                       tns = rtypenamespace.exec( types[t] ) || [];
+                       type = tns[1];
+                       namespaces = tns[2];
 
-                       for ( type in events ) {
-                               jQuery.event.remove( elem, type + types );
-                       }
-
-                       return;
-               }
-
-               // Handle multiple events separated by a space
-               // jQuery(...).unbind("mouseover mouseout", fn);
-               types = types.split(" ");
-
-               while ( (type = types[ i++ ]) ) {
-                       origType = type;
-                       handleObj = null;
-                       all = type.indexOf(".") < 0;
-                       namespaces = [];
-
-                       if ( !all ) {
-                               // Namespaced event handlers
-                               namespaces = type.split(".");
-                               type = namespaces.shift();
-
-                               namespace = new RegExp("(^|\\.)" +
-                                       jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
-                       }
-
-                       eventType = events[ type ];
-
-                       if ( !eventType ) {
-                               continue;
-                       }
-
-                       if ( !handler ) {
-                               for ( j = 0; j < eventType.length; j++ ) {
-                                       handleObj = eventType[ j ];
-
-                                       if ( all || namespace.test( handleObj.namespace ) ) {
-                                               jQuery.event.remove( elem, origType, handleObj.handler, j );
-                                               eventType.splice( j--, 1 );
-                                       }
+                       // Unbind all events (on this namespace, if provided) for the element
+                       if ( !type  ) {
+                               namespaces = namespaces? "." + namespaces : "";
+                               for ( j in events ) {
+                                       jQuery.event.remove( elem, j + namespaces, handler, selector );
                                }
-
-                               continue;
+                               return;
                        }
 
                        special = jQuery.event.special[ type ] || {};
+                       eventType = events[ type ] || [];
+                       namespaces =  namespaces? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
 
-                       for ( j = pos || 0; j < eventType.length; j++ ) {
-                               handleObj = eventType[ j ];
-
-                               if ( handler.guid === handleObj.guid ) {
-                                       // remove the given handler for the given type
-                                       if ( all || namespace.test( handleObj.namespace ) ) {
-                                               if ( pos == null ) {
-                                                       eventType.splice( j--, 1 );
-                                               }
+                       // Only need to loop for special events or selective removal
+                       if ( handler || namespaces || selector || special.remove ) {
+                               for ( j = 0; j < eventType.length; j++ ) {
+                                       handleObj = eventType[ j ];
 
-                                               if ( special.remove ) {
-                                                       special.remove.call( elem, handleObj );
+                                       if ( !handler || handler.guid === handleObj.guid ) {
+                                               if ( !namespaces || namespaces.test( handleObj.namespace ) ) {
+                                                       if ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) {
+                                                               eventType.splice( j--, 1 );
+
+                                                               if ( handleObj.selector ) {
+                                                                       eventType.delegateCount--;
+                                                               }
+                                                               if ( special.remove ) {
+                                                                       special.remove.call( elem, handleObj );
+                                                               }
+                                                       }
                                                }
                                        }
-
-                                       if ( pos != null ) {
-                                               break;
-                                       }
                                }
+                       } else {
+                               // Removing all events
+                               eventType.length = 0;
                        }
 
                        // remove generic event handler if no more handlers exist
-                       if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+                       if ( eventType.length === 0 ) {
                                if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
                                        jQuery.removeEvent( elem, type, elemData.handle );
                                }
 
-                               ret = null;
                                delete events[ type ];
                        }
-               }
 
-               // Remove the expando if it's no longer used
-               if ( jQuery.isEmptyObject( events ) ) {
-                       var handle = elemData.handle;
-                       if ( handle ) {
-                               handle.elem = null;
-                       }
+                       // Remove the expando if it's no longer used
+                       if ( jQuery.isEmptyObject( events ) ) {
+                               handle = elemData.handle;
+                               if ( handle ) {
+                                       handle.elem = null;
+                               }
 
-                       delete elemData.events;
-                       delete elemData.handle;
+                               delete elemData.events;
+                               delete elemData.handle;
 
-                       if ( jQuery.isEmptyObject( elemData ) ) {
-                               jQuery.removeData( elem, undefined, true );
+                               if ( jQuery.isEmptyObject( elemData ) ) {
+                                       jQuery.removeData( elem, undefined, true );
+                               }
                        }
                }
        },
@@ -278,7 +250,7 @@ jQuery.event = {
                // Event object or event type
                var type = event.type || event,
                        namespaces = [],
-                       exclusive;
+                       exclusive, cur, ontype, handle, old, special;
 
                if ( type.indexOf("!") >= 0 ) {
                        // Exclusive events trigger only for the exact event (no namespaces)
@@ -310,7 +282,7 @@ jQuery.event = {
                event.type = type;
                event.exclusive = exclusive;
                event.namespace = namespaces.join(".");
-               event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)");
+               event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
                
                // triggerHandler() and global events don't bubble or run the default action
                if ( onlyHandlers || !elem ) {
@@ -347,13 +319,13 @@ jQuery.event = {
                data = data != null ? jQuery.makeArray( data ) : [];
                data.unshift( event );
 
-               var cur = elem,
-                       // IE doesn't like method names with a colon (#3533, #8272)
-                       ontype = type.indexOf(":") < 0 ? "on" + type : "";
+               // IE doesn't like method names with a colon (#3533, #8272)
+               ontype = type.indexOf(":") < 0 ? "on" + type : "";
 
                // Fire event on the current element, then bubble up the DOM tree
+               cur = elem;
                do {
-                       var handle = jQuery._data( cur, "handle" );
+                       handle = jQuery._data( cur, "handle" );
 
                        event.currentTarget = cur;
                        if ( handle ) {
@@ -366,14 +338,13 @@ jQuery.event = {
                                event.preventDefault();
                        }
 
-                       // Bubble up to document, then to window
-                       cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window;
+                       // Bubble up to document, then to window; watch for a global parentNode or ownerDocument var (#9724)
+                       cur = (!cur.setInterval && cur.parentNode || cur.ownerDocument) || cur === event.target.ownerDocument && window;
                } while ( cur && !event.isPropagationStopped() );
 
                // If nobody prevented the default action, do it now
                if ( !event.isDefaultPrevented() ) {
-                       var old,
-                               special = jQuery.event.special[ type ] || {};
+                       special = jQuery.event.special[ type ] || {};
 
                        if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) &&
                                !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
@@ -407,43 +378,68 @@ jQuery.event = {
        },
 
        handle: function( event ) {
+
+               // Make a writable jQuery.Event from the native event object
                event = jQuery.event.fix( event || window.event );
-               // Snapshot the handlers list since a called handler may add/remove events.
-               var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0),
-                       run_all = !event.exclusive && !event.namespace,
-                       args = Array.prototype.slice.call( arguments, 0 );
+               
+               var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []),
+                       delegateCount = handlers.delegateCount,
+                       args = Array.prototype.slice.call( arguments, 0 ),
+                       cur = event.target,
+                       i, selMatch, matches, handleObj, sel, matched, related;
+
+               // Copy the handler list, in case one of the existing handlers adds/removes handlers
+               handlers = handlers.slice(0);
 
-               // Use the fix-ed Event rather than the (read-only) native event
+               // Use the fix-ed jQuery.Event rather than the (read-only) native event
                args[0] = event;
-               event.currentTarget = this;
-
-               for ( var j = 0, l = handlers.length; j < l; j++ ) {
-                       var handleObj = handlers[ j ];
-
-                       // Triggered event must 1) be non-exclusive and have no namespace, or
-                       // 2) have namespace(s) a subset or equal to those in the bound event.
-                       if ( run_all || event.namespace_re.test( handleObj.namespace ) ) {
-                               // Pass in a reference to the handler function itself
-                               // So that we can later remove it
-                               event.handler = handleObj.handler;
-                               event.data = handleObj.data;
-                               event.handleObj = handleObj;
-
-                               var ret = handleObj.handler.apply( this, args );
-
-                               if ( ret !== undefined ) {
-                                       event.result = ret;
-                                       if ( ret === false ) {
-                                               event.preventDefault();
-                                               event.stopPropagation();
+
+               // Run delegates first; they may want to stop propagation beneath us
+               // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
+               if ( delegateCount && cur && !cur.disabled && !(event.button && event.type === "click")  ) {
+
+                       // Let handlers know this is delegated, and from where; also used by .one()
+                       event.delegateTarget = this;
+
+                       for ( ; cur && cur != this && !event.isPropagationStopped(); cur = cur.parentNode ) {
+                               selMatch = {};
+                               matches = [];
+                               for ( i = 0; i < delegateCount; i++ ) {
+                                       handleObj = handlers[ i ];
+                                       sel = handleObj.selector;
+
+                                       if ( handleObj.isPositional ) {
+                                               // Since .is() does not work for positionals; see http://jsfiddle.net/eJ4yd/3/
+                                               matched = (selMatch[ sel ] || (selMatch[ sel ] = jQuery( sel ))).index( cur ) >= 0;
+                                       } else {
+                                               matched = selMatch[ sel ] || selMatch[ sel ] !== false && (selMatch[ sel ] = (handleObj.quick? quickIs( cur, handleObj.quick ) : jQuery( cur ).is( sel )));
+                                       }
+                                       if ( matched ) {
+                                               related = null;
+                                               if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+                                                       // Don't match a child element with the same selector
+                                                       related = jQuery( event.relatedTarget ).closest( sel )[0];
+                                                       if ( related && jQuery.contains( cur, related ) ) {
+                                                               related = cur;
+                                                       }
+                                               }
+                                               if ( !related || related !== cur ) {
+                                                       matches.push( handleObj );
+                                               }
                                        }
                                }
-
-                               if ( event.isImmediatePropagationStopped() ) {
-                                       break;
+                               if ( matches.length ) {
+                                       dispatch( cur, event, matches, 0, args );
                                }
                        }
+                       delete event.delegateTarget;
                }
+
+               // Run non-delegated handlers for this level
+               if ( delegateCount < handlers.length ) {
+                       dispatch( this, event, handlers, delegateCount, args );
+               }
+
                return event.result;
        },
 
@@ -518,20 +514,7 @@ jQuery.event = {
        special: {
                ready: {
                        // Make sure the ready event is setup
-                       setup: jQuery.bindReady,
-                       teardown: jQuery.noop
-               },
-
-               live: {
-                       add: function( handleObj ) {
-                               jQuery.event.add( this,
-                                       liveConvert( handleObj.origType, handleObj.selector ),
-                                       jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
-                       },
-
-                       remove: function( handleObj ) {
-                               jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
-                       }
+                       setup: jQuery.bindReady
                },
 
                beforeunload: {
@@ -551,6 +534,47 @@ jQuery.event = {
        }
 };
 
+// Run jQuery handler functions; called from jQuery.event.handle
+function dispatch( target, event, handlers, start, args ) {
+       var run_all = !event.exclusive && !event.namespace;
+
+       event.currentTarget = target;
+       for ( var j = start, l = handlers.length; j < l && !event.isImmediatePropagationStopped(); j++ ) {
+               var handleObj = handlers[ j ];
+
+               // Triggered event must either 1) be non-exclusive and have no namespace, or
+               // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+               if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+               
+                       // Pass in a reference to the handler function itself
+                       // So that we can later remove it
+                       event.handler = handleObj.handler;
+                       event.data = handleObj.data;
+                       event.handleObj = handleObj;
+
+                       var ret = handleObj.handler.apply( target, args );
+
+                       if ( ret !== undefined ) {
+                               event.result = ret;
+                               if ( ret === false ) {
+                                       event.preventDefault();
+                                       event.stopPropagation();
+                               }
+                       }
+               }
+       }
+}
+
+function quickIs( elem, m ) {
+       return (
+               (!m[1] || elem.nodeName.toLowerCase() === m[1]) && 
+               (!m[2] || elem.id === m[2]) && 
+               (!m[3] || m[3].test( elem.className )) &&
+               (!m[4] || elem.getAttribute( m[4] ) == m[5]) &&
+               (!m[6] || !elem[ m[6] ])
+       );
+}
+
 jQuery.removeEvent = document.removeEventListener ?
        function( elem, type, handle ) {
                if ( elem.removeEventListener ) {
@@ -565,7 +589,7 @@ jQuery.removeEvent = document.removeEventListener ?
 
 jQuery.Event = function( src, props ) {
        // Allow instantiation without the 'new' keyword
-       if ( !this.preventDefault ) {
+       if ( !(this instanceof jQuery.Event) ) {
                return new jQuery.Event( src, props );
        }
 
@@ -589,9 +613,8 @@ jQuery.Event = function( src, props ) {
                jQuery.extend( this, props );
        }
 
-       // timeStamp is buggy for some events on Firefox(#3843)
-       // So we won't rely on the native value
-       this.timeStamp = jQuery.now();
+       // Create a timestamp if incoming event doesn't have one
+       this.timeStamp = src.timeStamp || jQuery.now();
 
        // Mark it as fixed
        this[ jQuery.expando ] = true;
@@ -671,13 +694,6 @@ var withinElement = function( event ) {
                        event.type = eventType;
                }
        }
-},
-
-// In case of event delegation, we only need to rename the event.type,
-// liveHandler will take care of the rest.
-delegate = function( event ) {
-       event.type = event.data;
-       jQuery.event.handle.apply( this, arguments );
 };
 
 // Create mouseenter and mouseleave events
@@ -893,74 +909,110 @@ if ( !jQuery.support.focusinBubbles ) {
        });
 }
 
-jQuery.each(["bind", "one"], function( i, name ) {
-       jQuery.fn[ name ] = function( type, data, fn ) {
-               var handler;
+jQuery.fn.extend({
+
+       on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+               var origFn, type;
 
-               // Handle object literals
-               if ( typeof type === "object" ) {
-                       for ( var key in type ) {
-                               this[ name ](key, data, type[key], fn);
+               // Types can be a map of types/handlers
+               if ( typeof types === "object" ) {
+                       // ( types-Object, selector, data )
+                       if ( typeof selector !== "string" ) {
+                               // ( types-Object, data )
+                               data = selector;
+                               selector = undefined;
+                       }
+                       for ( type in types ) {
+                               this.on( type, selector, data, types[type], one );
                        }
                        return this;
                }
 
-               if ( arguments.length === 2 || data === false ) {
-                       fn = data;
-                       data = undefined;
+               if ( data == null && fn == null ) {
+                       // ( types, fn )
+                       fn = selector;
+                       data = selector = undefined;
+               } else if ( fn == null ) {
+                       if ( typeof selector === "string" ) {
+                               // ( types, selector, fn )
+                               fn = data;
+                               data = undefined;
+                       } else {
+                               // ( types, data, fn )
+                               fn = data;
+                               data = selector;
+                               selector = undefined;
+                       }
+               }
+               if ( fn === false ) {
+                       fn = returnFalse;
+               } else if ( !fn ) {
+                       return this;
                }
 
-               if ( name === "one" ) {
-                       handler = function( event ) {
-                               jQuery( this ).unbind( event, handler );
-                               return fn.apply( this, arguments );
+               if ( one === 1 || types === "unload" ) {
+                       origFn = fn;
+                       fn = function( event ) {
+                               jQuery.event.remove( event.delegateTarget || this, event );
+                               return origFn.apply( this, arguments );
                        };
-                       handler.guid = fn.guid || jQuery.guid++;
-               } else {
-                       handler = fn;
+                       // Use same guid so caller can remove using origFn
+                       fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
                }
-
-               if ( type === "unload" && name !== "one" ) {
-                       this.one( type, data, fn );
-
-               } else {
-                       for ( var i = 0, l = this.length; i < l; i++ ) {
-                               jQuery.event.add( this[i], type, handler, data );
+               return this.each( function() {
+                       jQuery.event.add( this, types, fn, data, selector );
+               });
+       },
+       one: function( types, selector, data, fn ) {
+               return this.on.call( this, types, selector, data, fn, 1 );
+       },
+       off: function( types, selector, fn ) {
+               if ( types && types.preventDefault ) {
+                       // ( event )  native or jQuery.Event
+                       return this.off( types.type, types.handler, types.selector );
+               }
+               if ( typeof types === "object" ) {
+                       // ( types-object [, selector] )
+                       for ( var type in types ) {
+                               this.off( type, selector, types[type] );
                        }
+                       return this;
                }
-
-               return this;
-       };
-});
-
-jQuery.fn.extend({
-       unbind: function( type, fn ) {
-               // Handle object literals
-               if ( typeof type === "object" && !type.preventDefault ) {
-                       for ( var key in type ) {
-                               this.unbind(key, type[key]);
-                       }
-
-               } else {
-                       for ( var i = 0, l = this.length; i < l; i++ ) {
-                               jQuery.event.remove( this[i], type, fn );
-                       }
+               if ( typeof selector !== "string" ) {
+                       // ( types [, fn] )
+                       fn = selector;
+                       selector = undefined;
                }
+               if ( fn === false ) {
+                       fn = returnFalse;
+               }
+               return this.each(function() {
+                       jQuery.event.remove( this, types, fn, selector );
+               });
+       },
+       
+       bind: function( types, data, fn ) {
+               return this.on( types, null, data, fn );
+       },
+       unbind: function( types, fn ) {
+               return this.off( types, null, fn );
+       },
 
+       live: function( types, data, fn ) {
+               jQuery( this.context ).on( types, this.selector, data, fn );
+               return this;
+       },
+       die: function( types, fn ) {
+               jQuery( this.context ).off( types, this.selector || "**", fn );
                return this;
        },
 
        delegate: function( selector, types, data, fn ) {
-               return this.live( types, data, fn, selector );
+               return this.on( types, selector, data, fn );
        },
-
        undelegate: function( selector, types, fn ) {
-               if ( arguments.length === 0 ) {
-                       return this.unbind( "live" );
-
-               } else {
-                       return this.die( types, null, fn, selector );
-               }
+               // ( namespace ) or ( selector, types [, fn] )
+               return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
        },
 
        trigger: function( type, data ) {
@@ -968,7 +1020,6 @@ jQuery.fn.extend({
                        jQuery.event.trigger( type, data, this );
                });
        },
-
        triggerHandler: function( type, data ) {
                if ( this[0] ) {
                        return jQuery.event.trigger( type, data, this[0], true );
@@ -982,8 +1033,8 @@ jQuery.fn.extend({
                        i = 0,
                        toggler = function( event ) {
                                // Figure out which function to execute
-                               var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
-                               jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+                               var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+                               jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
 
                                // Make sure that clicks stop
                                event.preventDefault();
@@ -1006,175 +1057,6 @@ jQuery.fn.extend({
        }
 });
 
-var liveMap = {
-       focus: "focusin",
-       blur: "focusout",
-       mouseenter: "mouseover",
-       mouseleave: "mouseout"
-};
-
-jQuery.each(["live", "die"], function( i, name ) {
-       jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
-               var type, i = 0, match, namespaces, preType,
-                       selector = origSelector || this.selector,
-                       context = origSelector ? this : jQuery( this.context );
-
-               if ( typeof types === "object" && !types.preventDefault ) {
-                       for ( var key in types ) {
-                               context[ name ]( key, data, types[key], selector );
-                       }
-
-                       return this;
-               }
-
-               if ( name === "die" && !types &&
-                                       origSelector && origSelector.charAt(0) === "." ) {
-
-                       context.unbind( origSelector );
-
-                       return this;
-               }
-
-               if ( data === false || jQuery.isFunction( data ) ) {
-                       fn = data || returnFalse;
-                       data = undefined;
-               }
-
-               types = (types || "").split(" ");
-
-               while ( (type = types[ i++ ]) != null ) {
-                       match = rnamespaces.exec( type );
-                       namespaces = "";
-
-                       if ( match )  {
-                               namespaces = match[0];
-                               type = type.replace( rnamespaces, "" );
-                       }
-
-                       if ( type === "hover" ) {
-                               types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
-                               continue;
-                       }
-
-                       preType = type;
-
-                       if ( liveMap[ type ] ) {
-                               types.push( liveMap[ type ] + namespaces );
-                               type = type + namespaces;
-
-                       } else {
-                               type = (liveMap[ type ] || type) + namespaces;
-                       }
-
-                       if ( name === "live" ) {
-                               // bind live handler
-                               for ( var j = 0, l = context.length; j < l; j++ ) {
-                                       jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
-                                               { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
-                               }
-
-                       } else {
-                               // unbind live handler
-                               context.unbind( "live." + liveConvert( type, selector ), fn );
-                       }
-               }
-
-               return this;
-       };
-});
-
-function liveHandler( event ) {
-       var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
-               elems = [],
-               selectors = [],
-               events = jQuery._data( this, "events" );
-
-       // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
-       if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
-               return;
-       }
-
-       if ( event.namespace ) {
-               namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
-       }
-
-       event.liveFired = this;
-
-       var live = events.live.slice(0);
-
-       for ( j = 0; j < live.length; j++ ) {
-               handleObj = live[j];
-
-               if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
-                       selectors.push( handleObj.selector );
-
-               } else {
-                       live.splice( j--, 1 );
-               }
-       }
-
-       match = jQuery( event.target ).closest( selectors, event.currentTarget );
-
-       for ( i = 0, l = match.length; i < l; i++ ) {
-               close = match[i];
-
-               for ( j = 0; j < live.length; j++ ) {
-                       handleObj = live[j];
-
-                       if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
-                               elem = close.elem;
-                               related = null;
-
-                               // Those two events require additional checking
-                               if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
-                                       event.type = handleObj.preType;
-                                       related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
-
-                                       // Make sure not to accidentally match a child element with the same selector
-                                       if ( related && jQuery.contains( elem, related ) ) {
-                                               related = elem;
-                                       }
-                               }
-
-                               if ( !related || related !== elem ) {
-                                       elems.push({ elem: elem, handleObj: handleObj, level: close.level });
-                               }
-                       }
-               }
-       }
-
-       for ( i = 0, l = elems.length; i < l; i++ ) {
-               match = elems[i];
-
-               if ( maxLevel && match.level > maxLevel ) {
-                       break;
-               }
-
-               event.currentTarget = match.elem;
-               event.data = match.handleObj.data;
-               event.handleObj = match.handleObj;
-
-               ret = match.handleObj.origHandler.apply( match.elem, arguments );
-
-               if ( ret === false || event.isPropagationStopped() ) {
-                       maxLevel = match.level;
-
-                       if ( ret === false ) {
-                               stop = false;
-                       }
-                       if ( event.isImmediatePropagationStopped() ) {
-                               break;
-                       }
-               }
-       }
-
-       return stop;
-}
-
-function liveConvert( type, selector ) {
-       return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&");
-}
-
 jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
        "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
        "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
index ecff5e331c39ae1fe5a11c17721139f6d948081c..3ae7cad393ae0265329489291e0e73a49c22ff4f 100644 (file)
@@ -371,7 +371,7 @@ test("bind/delegate bubbling, isDefaultPrevented", function() {
        });
        fakeClick( $anchor2 );
        $anchor2.unbind( "click" );
-       $main.undelegate( "#foo", "click" );
+       $main.undelegate( "click" );
        $anchor2.click(function(e) {
                // Let the default action occur
        });
@@ -380,7 +380,7 @@ test("bind/delegate bubbling, isDefaultPrevented", function() {
        });
        fakeClick( $anchor2 );
        $anchor2.unbind( "click" );
-       $main.undelegate( "#foo", "click" );
+       $main.undelegate( "click" );
 });
 
 test("bind(), iframes", function() {
@@ -2206,6 +2206,114 @@ test("custom events with colons (#3533, #8272)", function() {
 
 });
 
+test(".on and .off", function() {
+       expect(9);
+       var counter;
+
+       jQuery( '<div id="onandoff"><p>on<b>and</b>off</p><div>worked<em>or</em>borked?</div></div>' ).appendTo( 'body' );
+
+       // Simple case
+       jQuery( "#onandoff" )
+               .on( "whip", function() {
+                       ok( true, "whipped it good" );
+               })
+               .trigger( "whip" )
+               .off();
+
+       // Direct events only
+       counter = 0;
+       jQuery( "#onandoff em" )
+               .on( "click", 5, function( e, trig ) {
+                       counter += e.data + (trig || 9);        // twice, 5+9+5+17=36
+               })
+               .one( "click", 7, function( e, trig ) {
+                       counter += e.data + (trig || 11);       // once, 7+11=18
+               })
+               .click()
+               .trigger( "click", 17 )
+               .off( "click" );
+       equals( counter, 54, "direct event bindings with data" );
+
+       // Delegated events only
+       counter = 0;
+       jQuery( "#onandoff" )
+               .on( "click", "em", 5, function( e, trig ) {
+                       counter += e.data + (trig || 9);        // twice, 5+9+5+17=36
+               })
+               .one( "click", "em", 7, function( e, trig ) {
+                       counter += e.data + (trig || 11);       // once, 7+11=18
+               })
+               .find("em")
+                       .click()
+                       .trigger( "click", 17 )
+               .end()
+               .off( "click", "em" );
+       equals( counter, 54, "delegated event bindings with data" );
+
+
+       // Mixed event bindings and types
+       counter = 0;
+       mixfn = function(e, trig) {
+               counter += (e.data || 0) + (trig || 1);
+       };
+       jQuery( "#onandoff" )
+               .on( "click clack cluck", "em", 2, mixfn )
+               .on( "cluck", "b", 7, mixfn )
+               .on( "cluck", mixfn )
+               .trigger( "what!" )
+               .each( function() {
+                       equals( counter, 0, "nothing triggered yet" );
+               })
+               .find( "em" )
+                       .one( "cluck", 3, mixfn )
+                       .trigger( "cluck", 8 )                  // 3+8 2+8 + 0+8 = 29
+                       .off()
+                       .trigger( "cluck", 9 )                  // 2+9 + 0+9 = 20
+               .end()
+               .each( function() {
+                       equals( counter, 49, "after triggering em element" );
+               })
+               .trigger( "cluck", 2 )                          // 0+2 = 2
+               .each( function() {
+                       equals( counter, 51, "after triggering #onandoff cluck" );
+               })
+               .find( "b" )
+                       .on( "click", 95, mixfn )
+                       .on( "clack", "p", 97, mixfn )
+                       .one( "cluck", 3, mixfn )
+                       .trigger( "quack", 19 )                 // 0
+                       .off( "click clack cluck" )
+               .end()
+               .each( function() {
+                       equals( counter, 51, "after triggering b" );
+               })
+               .trigger( "cluck", 3 )                          // 0+3 = 3
+               .off( "clack", "em" )
+               .find( "em" )
+                       .trigger( "clack" )                             // 0
+               .end()
+               .each( function() {
+                       equals( counter, 54, "final triggers" );
+               })
+               .off( "click cluck" );
+
+       // We should have removed all the event handlers ... kinda hacky way to check this
+       var data = jQuery.data[ jQuery( "#onandoff" )[0].expando ] || {};
+       equals( data.events, null, "no events left" );
+       
+       jQuery("#onandoff").remove();
+});
+
+test("delegated events quickIs", function() {
+       expect(1);
+       ok( false, "write a unit test you lazy slob!" );
+//TODO: specific unit tests for quickIs selector cases
+//TODO: make a test to ensure == matches the null returned by getAttribute for [name]
+//ALSO: will quick[5] return undefined in all regexp impls? If not fix it
+
+});
+
+
 (function(){
        // This code must be run before DOM ready!
        var notYetReady, noEarlyExecution,