jQuery: The Write Less, Do More JavaScript Library

Changeset 3159

Show
Ignore:
Timestamp:
09/08/07 23:31:23 (1 year ago)
Author:
jeresig
Message:

Landing the new expando management code. Completely overhauls how data is associated with elements.

Plugins will be most interested in:
- jQuery.data(elem) -> Unique ID for the element
- jQuery.data(elem, name) -> Named data store for the element
- jQuery.data(elem, name, value) -> Saves a value to the named data store
- jQuery.removeData(elem) -> Remove the expando and the complete data store
- jQuery.removeData(elem, name) -> Removes just this one named data store

jQuery's .remove() and .empty() automatically clean up after themselves. Once an element leaves a DOM document their events are no longer intact. Thus, statements like so:

  $("#foo").remove().appendTo("#bar");

should be written like so:

  $("#foo").appendTo("#bar");

in order to avoid losing the bound events.

Location:
trunk/jquery
Files:
5 modified

Legend:

Unmodified
Added
Removed
  • trunk/jquery/src/core.js

    r3149 r3159  
    239239 
    240240            this.find("*").andSelf().each(function(i) { 
    241                 var events = this.$events; 
     241                var events = jQuery.data(this, "events"); 
    242242                for ( var type in events ) 
    243243                    for ( var handler in events[type] ) 
     
    425425}; 
    426426 
     427var expando = "jQuery" + (new Date()).getTime(), uuid = 0; 
     428 
    427429jQuery.extend({ 
    428430    noConflict: function(deep) { 
     
    463465    nodeName: function( elem, name ) { 
    464466        return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); 
     467    }, 
     468     
     469    cache: {}, 
     470     
     471    data: function( elem, name, data ) { 
     472        var id = elem[ expando ]; 
     473 
     474        // Compute a unique ID for the element 
     475        if ( !id )  
     476            id = elem[ expando ] = ++uuid; 
     477 
     478        // Only generate the data cache if we're 
     479        // trying to access or manipulate it 
     480        if ( name && !jQuery.cache[ id ] ) 
     481            jQuery.cache[ id ] = {}; 
     482         
     483        // Prevent overriding the named cache with undefined values 
     484        if ( data != undefined ) 
     485            jQuery.cache[ id ][ name ] = data; 
     486         
     487        // Return the named cache data, or the ID for the element    
     488        return name ? jQuery.cache[ id ][ name ] : id; 
     489    }, 
     490     
     491    removeData: function( elem, name ) { 
     492        var id = elem[ expando ]; 
     493 
     494        // If we want to remove a specific section of the element's data 
     495        if ( name ) { 
     496            // Remove the section of cache data 
     497            delete jQuery.cache[ id ][ name ]; 
     498 
     499            // If we've removed all the data, remove the element's cache 
     500            name = ""; 
     501            for ( name in jQuery.cache[ id ] ) break; 
     502            if ( !name ) 
     503                jQuery.removeData( elem ); 
     504 
     505        // Otherwise, we want to remove all of the element's data 
     506        } else { 
     507            // Clean up the element expando 
     508            try { 
     509                delete elem[ expando ]; 
     510            } catch(e){ 
     511                // IE has trouble directly removing the expando 
     512                // but it's ok with using removeAttribute 
     513                elem.removeAttribute( expando ); 
     514            } 
     515 
     516            // Completely remove the data cache 
     517            delete jQuery.cache[ id ]; 
     518        } 
    465519    }, 
    466520 
     
    850904 
    851905    unique: function(first) { 
    852         var r = [], num = jQuery.mergeNum++; 
     906        var r = [], done = {}; 
    853907 
    854908        try { 
    855             for ( var i = 0, fl = first.length; i < fl; i++ ) 
    856                 if ( num != first[i].mergeNum ) { 
    857                     first[i].mergeNum = num; 
     909            for ( var i = 0, fl = first.length; i < fl; i++ ) { 
     910                var id = jQuery.data(first[i]); 
     911                if ( !done[id] ) { 
     912                    done[id] = true; 
    858913                    r.push(first[i]); 
    859914                } 
     915            } 
    860916        } catch(e) { 
    861917            r = first; 
     
    864920        return r; 
    865921    }, 
    866  
    867     mergeNum: 0, 
    868922 
    869923    grep: function(elems, fn, inv) { 
     
    9911045    }, 
    9921046    remove: function(a){ 
    993         if ( !a || jQuery.filter( a, [this] ).r.length ) 
     1047        if ( !a || jQuery.filter( a, [this] ).r.length ) { 
     1048            jQuery.removeData( this ); 
    9941049            this.parentNode.removeChild( this ); 
     1050        } 
    9951051    }, 
    9961052    empty: function() { 
     1053        // Clean up the cache 
     1054        jQuery("*", this).each(function(){ jQuery.removeData(this); }); 
     1055 
    9971056        while ( this.firstChild ) 
    9981057            this.removeChild( this.firstChild ); 
  • trunk/jquery/src/event.js

    r3138 r3159  
    4242 
    4343        // Init the element's event structure 
    44         if (!element.$events) 
    45             element.$events = {}; 
    46          
    47         if (!element.$handle) 
    48             element.$handle = function() { 
    49                 // returned undefined or false 
    50                 var val; 
    51  
    52                 // Handle the second event of a trigger and when 
    53                 // an event is called after a page has unloaded 
    54                 if ( typeof jQuery == "undefined" || jQuery.event.triggered ) 
    55                   return val; 
    56                  
    57                 val = jQuery.event.handle.apply(element, arguments); 
    58                  
     44        var events = jQuery.data(element, "events") || jQuery.data(element, "events", {}); 
     45         
     46        var handle = jQuery.data(element, "handle", function(){ 
     47            // returned undefined or false 
     48            var val; 
     49 
     50            // Handle the second event of a trigger and when 
     51            // an event is called after a page has unloaded 
     52            if ( typeof jQuery == "undefined" || jQuery.event.triggered ) 
    5953                return val; 
    60             }; 
     54             
     55            val = jQuery.event.handle.apply(element, arguments); 
     56             
     57            return val; 
     58        }); 
    6159 
    6260        // Get the current list of functions bound to this event 
    63         var handlers = element.$events[type]; 
     61        var handlers = events[type]; 
    6462 
    6563        // Init the event handler queue 
    6664        if (!handlers) { 
    67             handlers = element.$events[type] = {};   
     65            handlers = events[type] = {};    
    6866             
    6967            // And bind the global event handler to the element 
    7068            if (element.addEventListener) 
    71                 element.addEventListener(type, element.$handle, false); 
     69                element.addEventListener(type, handle, false); 
    7270            else 
    73                 element.attachEvent("on" + type, element.$handle); 
     71                element.attachEvent("on" + type, handle); 
    7472        } 
    7573 
     
    8684    // Detach an event or set of events from an element 
    8785    remove: function(element, type, handler) { 
    88         var events = element.$events, ret, index; 
     86        var events = jQuery.data(element, "events"), ret, index; 
    8987 
    9088        // Namespaced event handlers 
     
    112110                // remove all handlers for the given type 
    113111                else 
    114                     for ( handler in element.$events[type] ) 
     112                    for ( handler in events[type] ) 
    115113                        // Handle the removal of namespaced events 
    116114                        if ( !parts[1] || events[type][handler].type == parts[1] ) 
     
    121119                if ( !ret ) { 
    122120                    if (element.removeEventListener) 
    123                         element.removeEventListener(type, element.$handle, false); 
     121                        element.removeEventListener(type, jQuery.data(element, "handle"), false); 
    124122                    else 
    125                         element.detachEvent("on" + type, element.$handle); 
     123                        element.detachEvent("on" + type, jQuery.data(element, "handle")); 
    126124                    ret = null; 
    127125                    delete events[type]; 
     
    131129            // Remove the expando if it's no longer used 
    132130            for ( ret in events ) break; 
    133             if ( !ret ) 
    134                 element.$handle = element.$events = null; 
     131            if ( !ret ) { 
     132                jQuery.removeData( element, "events" ); 
     133                jQuery.removeData( element, "handle" ); 
     134            } 
    135135        } 
    136136    }, 
     
    157157 
    158158            // Trigger the event 
    159             if ( jQuery.isFunction( element.$handle ) ) 
    160                 val = element.$handle.apply( element, data ); 
     159            if ( jQuery.isFunction( jQuery.data(element, "handle") ) ) 
     160                val = jQuery.data(element, "handle").apply( element, data ); 
    161161 
    162162            // Handle triggering native .onfoo handlers 
     
    195195        event.type = parts[0]; 
    196196 
    197         var c = this.$events && this.$events[event.type], args = Array.prototype.slice.call( arguments, 1 ); 
     197        var c = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 ); 
    198198        args.unshift( event ); 
    199199 
  • trunk/jquery/src/selector.js

    r3138 r3159  
    141141                    r = []; 
    142142 
    143                     var nodeName = m[2], mergeNum = jQuery.mergeNum++; 
     143                    var nodeName = m[2], merge = {}; 
    144144                    m = m[1]; 
    145145 
     
    148148                        for ( ; n; n = n.nextSibling ) 
    149149                            if ( n.nodeType == 1 ) { 
    150                                 if ( m == "~" && n.mergeNum == mergeNum ) break; 
     150                                var id = jQuery.data(n); 
     151 
     152                                if ( m == "~" && merge[id] ) break; 
    151153                                 
    152154                                if (!nodeName || n.nodeName.toUpperCase() == nodeName.toUpperCase() ) { 
    153                                     if ( m == "~" ) n.mergeNum = mergeNum; 
     155                                    if ( m == "~" ) merge[id] = true; 
    154156                                    r.push( n ); 
    155157                                } 
     
    347349            // We can get a speed boost by handling nth-child here 
    348350            } else if ( m[1] == ":" && m[2] == "nth-child" ) { 
    349                 var num = jQuery.mergeNum++, tmp = [], 
     351                var merge = {}, tmp = [], 
    350352                    test = /(\d*)n\+?(\d*)/.exec( 
    351353                        m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" || 
     
    354356 
    355357                for ( var i = 0, rl = r.length; i < rl; i++ ) { 
    356                     var node = r[i], parentNode = node.parentNode; 
    357  
    358                     if ( num != parentNode.mergeNum ) { 
     358                    var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode); 
     359 
     360                    if ( !merge[id] ) { 
    359361                        var c = 1; 
    360362 
     
    363365                                n.nodeIndex = c++; 
    364366 
    365                         parentNode.mergeNum = num; 
     367                        merge[id] = true; 
    366368                    } 
    367369 
  • trunk/jquery/test/data/testrunner.js

    r3022 r3159  
    162162 */ 
    163163function reset() { 
    164     document.getElementById('main').innerHTML = _config.fixture; 
     164    $("#main").html( _config.fixture ); 
    165165} 
    166166 
  • trunk/jquery/test/unit/event.js

    r3139 r3159  
    99    }; 
    1010    $("#firstp").bind("click", {foo: "bar"}, handler).click().unbind("click", handler); 
    11      
    12     ok( !$("#firstp").get(0).$events, "Event handler unbound when using data." ); 
     11 
     12    ok( !jQuery.data($("#firstp")[0], "events"), "Event handler unbound when using data." ); 
    1313     
    1414    reset(); 
     
    109109    el.click(function() { return; }); 
    110110    el.unbind('change',function(){ return; }); 
    111     for (var ret in el[0].$events['click']) break; 
     111    for (var ret in jQuery.data(el[0], "events")['click']) break; 
    112112    ok( ret, "Extra handlers weren't accidentally removed." ); 
    113113 
    114114    el.unbind('click'); 
    115     ok( !el[0].$events, "Removed the events expando after all handlers are unbound." ); 
     115    ok( !jQuery.data(el[0], "events"), "Removed the events expando after all handlers are unbound." ); 
    116116}); 
    117117