Make sure that expando properties aren't set on embed, applet, or object elements...
[jquery.git] / src / event.js
index 014fb73..128f78f 100644 (file)
@@ -63,13 +63,7 @@ jQuery.event = {
                        var handlers = events[ type ],
                                special = this.special[ type ] || {};
 
                        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
                        if ( !handlers ) {
 
                        // Init the event handler queue
                        if ( !handlers ) {
@@ -78,7 +72,7 @@ jQuery.event = {
                                // Check for a special event handler
                                // Only use addEventListener/attachEvent if the special
                                // events handler returns false
                                // Check for a special event handler
                                // Only use addEventListener/attachEvent if the special
                                // events handler returns false
-                               if ( !special.setup || special.setup.call( elem, data, namespaces ) === false ) {
+                               if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) {
                                        // Bind the global event handler to the element
                                        if ( elem.addEventListener ) {
                                                elem.addEventListener( type, handle, false );
                                        // Bind the global event handler to the element
                                        if ( elem.addEventListener ) {
                                                elem.addEventListener( type, handle, false );
@@ -87,7 +81,15 @@ jQuery.event = {
                                        }
                                }
                        }
                                        }
                                }
                        }
-
+                       
+                       if ( special.add ) { 
+                               var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers ); 
+                               if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) { 
+                                       modifiedHandler.guid = modifiedHandler.guid || handler.guid; 
+                                       handler = modifiedHandler; 
+                               } 
+                       } 
+                       
                        // Add the function to the element's handler list
                        handlers[ handler.guid ] = handler;
 
                        // Add the function to the element's handler list
                        handlers[ handler.guid ] = handler;
 
@@ -109,7 +111,7 @@ jQuery.event = {
                        return;
                }
 
                        return;
                }
 
-               var events = jQuery.data( elem, "events" ), ret, type;
+               var events = jQuery.data( elem, "events" ), ret, type, fn;
 
                if ( events ) {
                        // Unbind all events for the element
 
                if ( events ) {
                        // Unbind all events for the element
@@ -124,7 +126,7 @@ jQuery.event = {
                                        types = types.type;
                                }
 
                                        types = types.type;
                                }
 
-                               // Handle multiple events seperated by a space
+                               // Handle multiple events separated by a space
                                // jQuery(...).unbind("mouseover mouseout", fn);
                                types = types.split(/\s+/);
                                var i = 0;
                                // jQuery(...).unbind("mouseover mouseout", fn);
                                types = types.split(/\s+/);
                                var i = 0;
@@ -133,12 +135,14 @@ jQuery.event = {
                                        var namespaces = type.split(".");
                                        type = namespaces.shift();
                                        var all = !namespaces.length,
                                        var namespaces = type.split(".");
                                        type = namespaces.shift();
                                        var all = !namespaces.length,
-                                               namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join(".*\\.") + "(\\.|$)"),
+                                               cleaned = jQuery.map( namespaces.slice(0).sort() , function(nm){ return nm.replace(/[^\w\s\.\|`]/g, function(ch){return "\\"+ch;  }); }),
+                                               namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
                                                special = this.special[ type ] || {};
 
                                        if ( events[ type ] ) {
                                                // remove the given handler for the given type
                                                if ( handler ) {
                                                special = this.special[ type ] || {};
 
                                        if ( events[ type ] ) {
                                                // remove the given handler for the given type
                                                if ( handler ) {
+                                                       fn = events[ type ][ handler.guid ];
                                                        delete events[ type ][ handler.guid ];
 
                                                // remove all handlers for the given type
                                                        delete events[ type ][ handler.guid ];
 
                                                // remove all handlers for the given type
@@ -152,7 +156,7 @@ jQuery.event = {
                                                }
 
                                                if ( special.remove ) {
                                                }
 
                                                if ( special.remove ) {
-                                                       special.remove.call( elem, namespaces );
+                                                       special.remove.call( elem, namespaces, fn);
                                                }
 
                                                // remove generic event handler if no more handlers exist
                                                }
 
                                                // remove generic event handler if no more handlers exist
@@ -253,18 +257,20 @@ jQuery.event = {
                        nativeHandler = elem[ "on" + type ];
                // prevent IE from throwing an error for some elements with some event types, see #3533
                } catch (e) {}
                        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 ( (!nativeFn || (jQuery.nodeName(elem, 'a') && type === "click")) && nativeHandler && nativeHandler.apply( elem, data ) === false ) {
-                       event.result = false;
-               }
+
+               var isClick = jQuery.nodeName(elem, "a") && type === "click";
 
                // Trigger the native events (except for clicks on links)
 
                // Trigger the native events (except for clicks on links)
-               if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type === "click") ) {
+               if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
                        this.triggered = true;
                        try {
                                nativeFn();
                        // prevent IE from throwing an error for some hidden elements
                        } catch (e) {}
                        this.triggered = true;
                        try {
                                nativeFn();
                        // prevent IE from throwing an error for some hidden elements
                        } catch (e) {}
+
+               // Handle triggering native .onfoo handlers
+               } else if ( nativeHandler && nativeHandler.apply( elem, data ) === false ) {
+                       event.result = false;
                }
 
                this.triggered = false;
                }
 
                this.triggered = false;
@@ -291,7 +297,7 @@ jQuery.event = {
                // Cache this now, all = true means, any handler
                all = !namespaces.length && !event.exclusive;
 
                // Cache this now, all = true means, any handler
                all = !namespaces.length && !event.exclusive;
 
-               var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join(".*\\.") + "(\\.|$)");
+               var namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
 
                handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
 
 
                handlers = ( jQuery.data(this, "events") || {} )[ event.type ];
 
@@ -321,6 +327,8 @@ jQuery.event = {
 
                        }
                }
 
                        }
                }
+
+               return event.result;
        },
 
        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(" "),
        },
 
        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(" "),
@@ -402,10 +410,12 @@ jQuery.event = {
                },
 
                live: {
                },
 
                live: {
-                       add: function( proxy, data, namespaces ) {
+                       add: function( proxy, data, namespaces, live ) {
                                jQuery.extend( proxy, data || {} );
                                jQuery.extend( proxy, data || {} );
-                               proxy.guid += data.selector + data.live;
-                               jQuery.event.add( this, data.live, liveHandler, data );
+
+                               proxy.guid += data.selector + data.live; 
+                               jQuery.event.add( this, data.live, liveHandler, data ); 
+                               
                        },
 
                        remove: function( namespaces ) {
                        },
 
                        remove: function( namespaces ) {
@@ -422,6 +432,22 @@ jQuery.event = {
                                                jQuery.event.remove( this, namespaces[0], liveHandler );
                                        }
                                }
                                                jQuery.event.remove( this, namespaces[0], liveHandler );
                                        }
                                }
+                       },
+                       special: {}
+               },
+               beforeunload: {
+                       setup: function( data, namespaces, fn ) {
+                               // We only want to do this special case on windows
+                               if ( this.setInterval ) {
+                                       this.onbeforeunload = fn;
+                               }
+
+                               return false;
+                       },
+                       teardown: function( namespaces, fn ) {
+                               if ( this.onbeforeunload === fn ) {
+                                       this.onbeforeunload = null;
+                               }
                        }
                }
        }
                        }
                }
        }
@@ -529,83 +555,144 @@ delegate = function( event ) {
 
 // Create mouseenter and mouseleave events
 jQuery.each({
 
 // Create mouseenter and mouseleave events
 jQuery.each({
-       mouseover: "mouseenter",
-       mouseout: "mouseleave"
+       mouseenter: "mouseover",
+       mouseleave: "mouseout"
 }, function( orig, fix ) {
 }, function( orig, fix ) {
-       jQuery.event.special[ fix ] = {
+       jQuery.event.special[ orig ] = {
                setup: function(data){
                setup: function(data){
-                       jQuery.event.add( this, orig, data && data.selector ? delegate : withinElement, fix );
+                       jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
                },
                teardown: function(data){
                },
                teardown: function(data){
-                       jQuery.event.remove( this, orig, data && data.selector ? delegate : withinElement );
+                       jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
                }
        };
 });
 
                }
        };
 });
 
-(function() {
-       
-       var event = jQuery.event,
-               special = event.special,
-               handle  = event.handle;
-
-       special.submit = {
-               setup: function(data, namespaces) {
-                       if(data.selector) {
-                               event.add(this, 'click.specialSubmit', function(e, eventData) {
-                                       if(jQuery(e.target).filter(":submit, :image").closest(data.selector).length) {
-                                               e.type = "submit";
-                                               return handle.call( this, e, eventData );
-                                       }
-                               });
-                               
-                               event.add(this, 'keypress.specialSubmit', function( e, eventData ) {
-                                       if(jQuery(e.target).filter(":text, :password").closest(data.selector).length) {
-                                               e.type = "submit";
-                                               return handle.call( this, e, eventData );
-                                       }
-                               });
-                       } else {
-                               return false;
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+jQuery.event.special.submit = {
+       setup: function( data, namespaces, fn ) {
+               if ( this.nodeName.toLowerCase() !== "form" ) {
+                       jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
+                               var elem = e.target, type = elem.type;
+
+                               if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+                                       return trigger( "submit", this, arguments );
+                               }
+                       });
+        
+                       jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
+                               var elem = e.target, type = elem.type;
+
+                               if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+                                       return trigger( "submit", this, arguments );
+                               }
+                       });
+               }
+       },
+
+       remove: function( namespaces, fn ) {
+               jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
+               jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
+       }
+};
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+jQuery.event.special.change = {
+       filters: {
+               click: function( e ) { 
+                       var elem = e.target;
+
+                       if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) {
+                               return trigger( "change", this, arguments );
+                       }
+
+                       return changeFilters.keyup.call( this, e );
+               }, 
+               keyup: function( e ) { 
+                       var elem = e.target, data, index = elem.selectedIndex + "";
+
+                       if ( elem.nodeName.toLowerCase() === "select" ) {
+                               data = jQuery.data( elem, "_change_data" );
+                               jQuery.data( elem, "_change_data", index );
+
+                               if ( (elem.type === "select-multiple" || data != null) && data !== index ) {
+                                       return trigger( "change", this, arguments );
+                               }
                        }
                },
                        }
                },
+               beforeactivate: function( e ) {
+                       var elem = e.target;
+
+                       if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) {
+                               return trigger( "change", this, arguments );
+                       }
+               },
+               blur: function( e ) {
+                       var elem = e.target, nodeName = elem.nodeName.toLowerCase();
+
+                       if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password")))
+                               && jQuery.data(elem, "_change_data") !== elem.value ) {
+
+                               return trigger( "change", this, arguments );
+                       }
+               },
+               focus: function( e ) {
+                       var elem = e.target, nodeName = elem.nodeName.toLowerCase();
+
+                       if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) {
+                               jQuery.data( elem, "_change_data", elem.value );
+                       }
+               }
+       },
+       setup: function( data, namespaces, fn ) {
+               for ( var type in changeFilters ) {
+                       jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
+               }
                
                
-               remove: function(namespaces) {
-                       event.remove(this, 'click.specialSubmit');
-                       event.remove(this, 'keypress.specialSubmit');
+               // always want to listen for change for trigger
+               return false;
+       },
+       remove: function( namespaces, fn ) {
+               for ( var type in changeFilters ) {
+                       jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
                }
                }
-       };
-       
-})();
+       }
+};
+
+var changeFilters = jQuery.event.special.change.filters;
+
+function trigger( type, elem, args ) {
+       args[0].type = type;
+       return jQuery.event.handle.apply( elem, args );
+}
 
 // Create "bubbling" focus and blur events
 
 // Create "bubbling" focus and blur events
-jQuery.each({
-       focus: "focusin",
-       blur: "focusout"
-}, function( orig, fix ){
-       var event = jQuery.event,
-               handle = event.handle;
-       
-       function ieHandler() { 
-               arguments[0].type = orig;
-               return handle.apply(this, arguments);
-       }
+if ( !jQuery.support.focusBubbles ) {
 
 
-       event.special[orig] = {
-               setup:function() {
-                       if ( this.addEventListener )
-                               this.addEventListener( orig, handle, true );
-                       else
-                               event.add( this, fix, ieHandler );
+jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){
+       event.special[ orig ] = {
+               setup: function() {
+                       jQuery.event.add( this, fix, ieHandler );
                }, 
                }, 
-               teardown:function() { 
-                       if ( this.removeEventListener )
-                               this.removeEventListener( orig, handle, true );
-                       else
-                               event.remove( this, fix, ieHandler );
+               teardown: function() { 
+                       jQuery.event.remove( this, fix, ieHandler );
                }
        };
                }
        };
+
+       function ieHandler() { 
+               arguments[0].type = orig;
+               return jQuery.event.handle.apply(this, arguments);
+       }
 });
 
 });
 
+}
+
 jQuery.fn.extend({
        // TODO: make bind(), unbind() and one() DRY!
        bind: function( type, data, fn, thisObject ) {
 jQuery.fn.extend({
        // TODO: make bind(), unbind() and one() DRY!
        bind: function( type, data, fn, thisObject ) {
@@ -747,13 +834,34 @@ jQuery.fn.extend({
 });
 
 function liveHandler( event ) {
 });
 
 function liveHandler( event ) {
-       var stop = true, elems = [], args = arguments;
+       var stop = true, elems = [], selectors = [], args = arguments,
+               related, match, fn, elem, j, i, data,
+               live = jQuery.extend({}, jQuery.data( this, "events" ).live);
+
+       for ( j in live ) {
+               fn = live[j];
+               if ( fn.live === event.type ||
+                               fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
+
+                       data = fn.data;
+                       if ( !(data.beforeFilter && data.beforeFilter[event.type] && 
+                                       !data.beforeFilter[event.type](event)) ) {
+                               selectors.push( fn.selector );
+                       }
+               } else {
+                       delete live[j];
+               }
+       }
+
+       match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+       for ( i = 0, l = match.length; i < l; i++ ) {
+               for ( j in live ) {
+                       fn = live[j];
+                       elem = match[i].elem;
+                       related = null;
 
 
-       jQuery.each( jQuery.data( this, "events" ).live || [], function( i, fn ) {
-               if ( fn.live === event.type ) {
-                       var elem = jQuery( event.target ).closest( fn.selector, event.currentTarget )[0],
-                               related;
-                       if ( elem ) {
+                       if ( match[i].selector === fn.selector ) {
                                // Those two events require additional checking
                                if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
                                        related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
                                // Those two events require additional checking
                                if ( fn.live === "mouseenter" || fn.live === "mouseleave" ) {
                                        related = jQuery( event.relatedTarget ).closest( fn.selector )[0];
@@ -764,25 +872,25 @@ function liveHandler( event ) {
                                }
                        }
                }
                                }
                        }
                }
-       });
-
-       elems.sort(function( a, b ) {
-               return a.closer - b.closer;
-       });
+       }
 
 
-       jQuery.each(elems, function() {
-               event.currentTarget = this.elem;
-               event.data = this.fn.data;
-               if ( this.fn.apply( this.elem, args ) === false ) {
-                       return (stop = false);
+       for ( i = 0, l = elems.length; i < l; i++ ) {
+               match = elems[i];
+               event.currentTarget = match.elem;
+               event.data = match.fn.data;
+               if ( match.fn.apply( match.elem, args ) === false ) {
+                       stop = false;
+                       break;
                }
                }
-       });
+       }
 
        return stop;
 }
 
 function liveConvert( type, selector ) {
 
        return stop;
 }
 
 function liveConvert( type, selector ) {
-       return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
+       return ["live", type, selector//.replace(/[^\w\s\.]/g, function(ch){ return "\\"+ch})
+                                                                 .replace(/\./g, "`")
+                                                                 .replace(/ /g, "|")].join(".");
 }
 
 jQuery.extend({
 }
 
 jQuery.extend({
@@ -820,7 +928,7 @@ jQuery.extend({
 var readyBound = false;
 
 function bindReady() {
 var readyBound = false;
 
 function bindReady() {
-       if ( readyBound ) return;
+       if ( readyBound ) { return; }
        readyBound = true;
 
        // Catch cases where $(document).ready() is called after the
        readyBound = true;
 
        // Catch cases where $(document).ready() is called after the
@@ -857,23 +965,25 @@ function bindReady() {
                        toplevel = window.frameElement == null;
                } catch(e){}
 
                        toplevel = window.frameElement == null;
                } catch(e){}
 
-               if ( document.documentElement.doScroll && toplevel ) (function() {
-                       if ( jQuery.isReady ) {
-                               return;
-                       }
+               if ( document.documentElement.doScroll && toplevel ) {
+                       (function() {
+                               if ( jQuery.isReady ) {
+                                       return;
+                               }
 
 
-                       try {
-                               // If IE is used, use the trick by Diego Perini
-                               // http://javascript.nwbox.com/IEContentLoaded/
-                               document.documentElement.doScroll("left");
-                       } catch( error ) {
-                               setTimeout( arguments.callee, 0 );
-                               return;
-                       }
+                               try {
+                                       // If IE is used, use the trick by Diego Perini
+                                       // http://javascript.nwbox.com/IEContentLoaded/
+                                       document.documentElement.doScroll("left");
+                               } catch( error ) {
+                                       setTimeout( arguments.callee, 0 );
+                                       return;
+                               }
 
 
-                       // and execute any waiting functions
-                       jQuery.ready();
-               })();
+                               // and execute any waiting functions
+                               jQuery.ready();
+                       })();
+               }
        }
 
        // A fallback to window.onload, that will always work
        }
 
        // A fallback to window.onload, that will always work