Used the patch from Alexander as the basis for a rewrite of the IE change event logic...
[jquery.git] / src / event.js
index d2e106a..983e9e5 100644 (file)
@@ -37,13 +37,20 @@ jQuery.event = {
 
                // Init the element's event structure
                var events = jQuery.data( elem, "events" ) || jQuery.data( elem, "events", {} ),
-                       handle = jQuery.data( elem, "handle" ) || jQuery.data( elem, "handle", function eventHandle() {
+                       handle = jQuery.data( elem, "handle" ), eventHandle;
+
+               if ( !handle ) {
+                       eventHandle = function() {
                                // Handle the second event of a trigger and when
                                // an event is called after a page has unloaded
                                return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
                                        jQuery.event.handle.apply( eventHandle.elem, arguments ) :
                                        undefined;
-                       });
+                       };
+
+                       handle = jQuery.data( elem, "handle", eventHandle );
+               }
+
                // Add elem as a property of the handle function
                // This is to prevent a memory leak with non-native
                // event in IE.
@@ -253,8 +260,10 @@ jQuery.event = {
 
                var nativeFn, nativeHandler;
                try {
-                       nativeFn = elem[ type ];
-                       nativeHandler = elem[ "on" + type ];
+                       if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
+                               nativeFn = elem[ type ];
+                               nativeHandler = elem[ "on" + type ];
+                       }
                // prevent IE from throwing an error for some elements with some event types, see #3533
                } catch (e) {}
 
@@ -264,12 +273,12 @@ jQuery.event = {
                if ( !bubbling && nativeFn && !event.isDefaultPrevented() && !isClick ) {
                        this.triggered = true;
                        try {
-                               nativeFn();
+                               elem[ type ]();
                        // 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 ) {
+               } else if ( nativeHandler && elem[ "on" + type ].apply( elem, data ) === false ) {
                        event.result = false;
                }
 
@@ -603,50 +612,85 @@ jQuery.event.special.submit = {
 // change delegation, happens here so we have bind.
 if ( !jQuery.support.changeBubbles ) {
 
-jQuery.event.special.change = {
-       filters: {
-               click: function( e ) { 
-                       var elem = e.target;
+var formElems = /textarea|input|select/i;
 
-                       if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) {
-                               return trigger( "change", this, arguments );
-                       }
+function getVal( elem ) {
+       var type = elem.type, val = elem.value;
 
-                       return changeFilters.keyup.call( this, e );
-               }, 
-               keyup: function( e ) { 
-                       var elem = e.target, data, index = elem.selectedIndex + "";
+       if ( type === "radio" || type === "checkbox" ) {
+               val = elem.checked;
 
-                       if ( elem.nodeName.toLowerCase() === "select" ) {
-                               data = jQuery.data( elem, "_change_data" );
-                               jQuery.data( elem, "_change_data", index );
+       } else if ( type === "select-multiple" ) {
+               val = elem.selectedIndex > -1 ?
+                       jQuery.map( elem.options, function( elem ) {
+                               return elem.selected;
+                       }).join("-") :
+                       "";
 
-                               if ( (elem.type === "select-multiple" || data != null) && data !== index ) {
-                                       return trigger( "change", this, arguments );
-                               }
-                       }
-               },
-               beforeactivate: function( e ) {
-                       var elem = e.target;
+       } else if ( elem.nodeName.toLowerCase() === "select" ) {
+               val = elem.selectedIndex;
+       }
+
+       return val;
+}
 
-                       if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) {
-                               return trigger( "change", this, arguments );
+function testChange( e ) {
+               var elem = e.target, data, val;
+
+               if ( !formElems.test( elem.nodeName ) || elem.readOnly ) {
+                       return;
+               }
+
+               data = jQuery.data( elem, "_change_data" );
+               val = getVal(elem);
+
+               if ( val === data ) {
+                       return;
+               }
+
+               // the current data will be also retrieved by beforeactivate
+               if ( e.type !== "focusout" || elem.type !== "radio" ) {
+                       jQuery.data( elem, "_change_data", val );
+               }
+
+               if ( elem.type !== "select" && (data != null || val) ) {
+                       e.type = "change";
+                       return jQuery.event.trigger( e, arguments[1], this );
+               }
+}
+
+jQuery.event.special.change = {
+       filters: {
+               focusout: testChange, 
+
+               click: function( e ) {
+                       var elem = e.target, type = elem.type;
+
+                       if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
+                               return testChange.call( this, e );
                        }
                },
-               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 ) {
+               // Change has to be called before submit
+               // Keydown will be called before keypress, wich is used in submit-event delegation
+               keydown: function( e ) {
+                       var elem = e.target, type = elem.type;
 
-                               return trigger( "change", this, arguments );
+                       if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
+                               (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+                               type === "select-multiple" ) {
+                               return testChange.call( this, e );
                        }
                },
-               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 );
+               // Beforeactivate happens also before the previous element is blurred
+               // with this event you can't trigger a change event, but you can store
+               // information/focus[in] is not needed anymore
+               beforeactivate: function( e ) {
+                       var elem = e.target;
+
+                       if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" ) {
+                               return jQuery.data( elem, "_change_data", getVal(elem) );
                        }
                }
        },
@@ -654,14 +698,15 @@ jQuery.event.special.change = {
                for ( var type in changeFilters ) {
                        jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
                }
-               
-               // always want to listen for change for trigger
-               return false;
+
+               return formElems.test( this.nodeName );
        },
        remove: function( namespaces, fn ) {
                for ( var type in changeFilters ) {
                        jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
                }
+
+               return formElems.test( this.nodeName );
        }
 };
 
@@ -678,7 +723,7 @@ function trigger( type, elem, args ) {
 if ( !jQuery.support.focusBubbles ) {
 
 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){
-       event.special[ orig ] = {
+       jQuery.event.special[ orig ] = {
                setup: function() {
                        jQuery.event.add( this, fix, ieHandler );
                }, 
@@ -695,13 +740,12 @@ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ){
 
 }
 
-jQuery.fn.extend({
-       // TODO: make bind(), unbind() and one() DRY!
-       bind: function( type, data, fn, thisObject ) {
+jQuery.each(["bind", "one"], function(i, name) {
+       jQuery.fn[ name ] = function( type, data, fn, thisObject ) {
                // Handle object literals
                if ( typeof type === "object" ) {
                        for ( var key in type ) {
-                               this.bind(key, data, type[key], fn);
+                               this[ name ](key, data, type[key], fn);
                        }
                        return this;
                }
@@ -712,35 +756,17 @@ jQuery.fn.extend({
                        data = undefined;
                }
                fn = thisObject === undefined ? fn : jQuery.event.proxy( fn, thisObject );
-               return type === "unload" ? this.one(type, data, fn, thisObject) : this.each(function() {
-                       jQuery.event.add( this, type, fn, data );
-               });
-       },
-
-       one: function( type, data, fn, thisObject ) {
-               // Handle object literals
-               if ( typeof type === "object" ) {
-                       for ( var key in type ) {
-                               this.one(key, data, type[key], fn);
-                       }
-                       return this;
-               }
-               
-               if ( jQuery.isFunction( data ) ) {
-                       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 );
+               var handler = name == "one" ? jQuery.event.proxy( fn, function( event ) {
+                       jQuery( this ).unbind( event, handler );
                        return fn.apply( this, arguments );
+               }) : fn;
+               return type === "unload" ? this.one(type, data, handler, thisObject) : this.each(function() {
+                       jQuery.event.add( this, type, handler, data );
                });
-               return this.each(function() {
-                       jQuery.event.add( this, type, one, data );
-               });
-       },
+       };
+});
 
+jQuery.fn.extend({
        unbind: function( type, fn ) {
                // Handle object literals
                if ( typeof type === "object" && !type.preventDefault ) {
@@ -749,12 +775,11 @@ jQuery.fn.extend({
                        }
                        return this;
                }
-               
+
                return this.each(function() {
                        jQuery.event.remove( this, type, fn );
                });
        },
-
        trigger: function( type, data ) {
                return this.each(function() {
                        jQuery.event.trigger( type, data, this );
@@ -872,9 +897,7 @@ function liveHandler( event ) {
 }
 
 function liveConvert( type, selector ) {
-       return ["live", type, selector//.replace(/[^\w\s\.]/g, function(ch){ return "\\"+ch})
-                                                                 .replace(/\./g, "`")
-                                                                 .replace(/ /g, "|")].join(".");
+       return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "&")].join(".");
 }
 
 jQuery.each( ("blur focus load resize scroll unload click dblclick " +
@@ -885,19 +908,25 @@ jQuery.each( ("blur focus load resize scroll unload click dblclick " +
        jQuery.fn[ name ] = function( fn ) {
                return fn ? this.bind( name, fn ) : this.trigger( name );
        };
+
+       if ( jQuery.fnAttr ) {
+               jQuery.fnAttr[ name ] = true;
+       }
 });
 
 // Prevent memory leaks in IE
 // Window isn't included so as not to unbind existing unload events
 // More info:
 //  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
-/*@cc_on
-jQuery( window ).bind( 'unload', function() {
-       for ( var id in jQuery.cache ) {
-               // Skip the window
-               if ( id != 1 && jQuery.cache[ id ].handle ) {
-                       jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+if ( window.attachEvent && !window.addEventListener ) {
+       window.attachEvent("onunload", function() {
+               for ( var id in jQuery.cache ) {
+                       if ( jQuery.cache[ id ].handle ) {
+                               // Try/Catch is to handle iframes being unloaded, see #4280
+                               try {
+                                       jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+                               } catch(e) {}
+                       }
                }
-       }
-});
-@*/
+       });
+}