Make sure that focusin/focusout bubbles in non-IE browsers.
[jquery.git] / src / event.js
index fb78bfe..d830c4d 100644 (file)
@@ -7,7 +7,8 @@ var rnamespaces = /\.(.*)$/,
        rescape = /[^\w\s.|`]/g,
        fcleanup = function( nm ) {
                return nm.replace(rescape, "\\$&");
-       };
+       },
+       focusCounts = { focusin: 0, focusout: 0 };
 
 /*
  * A number of helper functions used for managing events.
@@ -54,8 +55,28 @@ jQuery.event = {
                        return;
                }
 
-               var events = elemData.events = elemData.events || {},
+               // Use a key less likely to result in collisions for plain JS objects.
+               // Fixes bug #7150.
+               var eventKey = elem.nodeType ? "events" : "__events__",
+                       events = elemData[ eventKey ],
                        eventHandle = elemData.handle;
+                       
+               if ( typeof events === "function" ) {
+                       // On plain objects events is a fn that holds the the data
+                       // which prevents this data from being JSON serialized
+                       // the function does not need to be called, it just contains the data
+                       eventHandle = events.handle;
+                       events = events.events;
+
+               } else if ( !events ) {
+                       if ( !elem.nodeType ) {
+                               // On plain objects, create a fn that acts as the holder
+                               // of the values to avoid JSON serialization of event data
+                               elemData[ eventKey ] = elemData = function(){};
+                       }
+
+                       elemData.events = events = {};
+               }
 
                if ( !eventHandle ) {
                        elemData.handle = eventHandle = function() {
@@ -153,12 +174,18 @@ jQuery.event = {
                }
 
                var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+                       eventKey = elem.nodeType ? "events" : "__events__",
                        elemData = jQuery.data( elem ),
-                       events = elemData && elemData.events;
+                       events = elemData && elemData[ eventKey ];
 
                if ( !elemData || !events ) {
                        return;
                }
+               
+               if ( typeof events === "function" ) {
+                       elemData = events;
+                       events = events.events;
+               }
 
                // types is actually an event object here
                if ( types && types.type ) {
@@ -259,7 +286,10 @@ jQuery.event = {
                        delete elemData.events;
                        delete elemData.handle;
 
-                       if ( jQuery.isEmptyObject( elemData ) ) {
+                       if ( typeof elemData === "function" ) {
+                               jQuery.removeData( elem, eventKey );
+
+                       } else if ( jQuery.isEmptyObject( elemData ) ) {
                                jQuery.removeData( elem );
                        }
                }
@@ -319,7 +349,10 @@ jQuery.event = {
                event.currentTarget = elem;
 
                // Trigger the event, it is assumed that "handle" is a function
-               var handle = jQuery.data( elem, "handle" );
+               var handle = elem.nodeType ?
+                       jQuery.data( elem, "handle" ) :
+                       (jQuery.data( elem, "__events__" ) || {}).handle;
+
                if ( handle ) {
                        handle.apply( elem, data );
                }
@@ -331,6 +364,7 @@ jQuery.event = {
                        if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
                                if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
                                        event.result = false;
+                                       event.preventDefault();
                                }
                        }
 
@@ -391,7 +425,12 @@ jQuery.event = {
 
                event.namespace = event.namespace || namespace_sort.join(".");
 
-               events = jQuery.data(this, "events");
+               events = jQuery.data(this, this.nodeType ? "events" : "__events__");
+
+               if ( typeof events === "function" ) {
+                       events = events.events;
+               }
+
                handlers = (events || {})[ event.type ];
 
                if ( events && handlers ) {
@@ -469,8 +508,8 @@ jQuery.event = {
                }
 
                // Add which for key events
-               if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
-                       event.which = event.charCode || event.keyCode;
+               if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
+                       event.which = event.charCode != null ? event.charCode : event.keyCode;
                }
 
                // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
@@ -672,6 +711,7 @@ if ( !jQuery.support.submitBubbles ) {
                                        var elem = e.target, type = elem.type;
 
                                        if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+                                               e.liveFired = undefined;
                                                return trigger( "submit", this, arguments );
                                        }
                                });
@@ -680,6 +720,7 @@ if ( !jQuery.support.submitBubbles ) {
                                        var elem = e.target, type = elem.type;
 
                                        if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+                                               e.liveFired = undefined;
                                                return trigger( "submit", this, arguments );
                                        }
                                });
@@ -742,6 +783,7 @@ if ( !jQuery.support.changeBubbles ) {
 
                if ( data != null || val ) {
                        e.type = "change";
+                       e.liveFired = undefined;
                        return jQuery.event.trigger( e, arguments[1], elem );
                }
        };
@@ -772,7 +814,7 @@ if ( !jQuery.support.changeBubbles ) {
 
                        // 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
+                       // information
                        beforeactivate: function( e ) {
                                var elem = e.target;
                                jQuery.data( elem, "_change_data", getVal(elem) );
@@ -799,6 +841,9 @@ if ( !jQuery.support.changeBubbles ) {
        };
 
        changeFilters = jQuery.event.special.change.filters;
+
+       // Handle when the input is .focus()'d
+       changeFilters.focus = changeFilters.beforeactivate;
 }
 
 function trigger( type, elem, args ) {
@@ -811,17 +856,21 @@ if ( document.addEventListener ) {
        jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
                jQuery.event.special[ fix ] = {
                        setup: function() {
-                               this.addEventListener( orig, handler, true );
+                               if ( focusCounts[fix]++ === 0 ) {
+                                       document.addEventListener( orig, handler, true );
+                               }
                        }, 
                        teardown: function() { 
-                               this.removeEventListener( orig, handler, true );
+                               if ( --focusCounts[fix] === 0 ) {
+                                       document.removeEventListener( orig, handler, true );
+                               }
                        }
                };
 
                function handler( e ) { 
                        e = jQuery.event.fix( e );
                        e.type = fix;
-                       return jQuery.event.handle.call( this, e );
+                       return jQuery.event.trigger( e, null, e.target );
                }
        });
 }
@@ -1004,7 +1053,11 @@ jQuery.each(["live", "die"], function( i, name ) {
 function liveHandler( event ) {
        var stop, maxLevel, elems = [], selectors = [],
                related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
-               events = jQuery.data( this, "events" );
+               events = jQuery.data( this, this.nodeType ? "events" : "__events__" );
+
+       if ( typeof events === "function" ) {
+               events = events.events;
+       }
 
        // Make sure we avoid non-left-click bubbling in Firefox (#3861)
        if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) {
@@ -1110,7 +1163,7 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl
 // More info:
 //  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
 if ( window.attachEvent && !window.addEventListener ) {
-       window.attachEvent("onunload", function() {
+       jQuery(window).bind("unload", function() {
                for ( var id in jQuery.cache ) {
                        if ( jQuery.cache[ id ].handle ) {
                                // Try/Catch is to handle iframes being unloaded, see #4280