Unique Element IDs With jQuery

Posted on 01/11/2009 @ 04:55AM

When optimizing DOM-heavy Javascript I'll often find the need to cache a bunch of elements or element collections. For example, take the following rollover code taken from my online resume (uses jQuery):

    // Delegated rollovers
    $('#experience').mouseover(function(e)
    {
        return resume.experienceOver(e);
    });
    
    // ... later in the code ...
    
    // Show/hide the arrow
    experienceOver: function(e)
    {
        var target = $(e.target), parents = target.parents('li.company'), pos;
        
        if (parents.length < 1) { return; }
        
        target = parents.eq(0);
        pos    = target.position();
        this.arrow.slideTo(-20, pos.top);
    }

Event delegation is used so that only one mouseover event is applied. When one of the titles is rolled over, experienceOver() makes sure the element we're over is a child of one of the <li class="company"> tags (otherwise we don't care), then applies the rollover effect (sliding the arrow to point to the correct element).

But the jQuery parents() method can potentially involve a lot of traversal, and we know that traversal can be slow at times. So we need a way to save the result of the parents() method into an object so that it's called at most one time per element.

Using the jQuery UUID Extension with an element wrapper I created, we can attach a unique ID to each element, then use that ID as an index to the cache object:

    jQuery._uuid_default_prefix = '';
    jQuery._uuidlet = function () {
        return(((1+Math.random())*0x10000)|0).toString(16).substring(1);
    };
    /*
    Generates random uuid
    */
    jQuery.uuid = function (p)
    {
        if (typeof(p) == 'object' && typeof(p.prefix) == 'string')
        {
            jQuery._uuid_default_prefix = p.prefix;
        }
        else
        {
            p = p || jQuery._uuid_default_prefix || '';
            return(p+jQuery._uuidlet()+jQuery._uuidlet()+"-"+jQuery._uuidlet()+"-"+jQuery._uuidlet()+"-"+jQuery._uuidlet()+"-"+jQuery._uuidlet()+jQuery._uuidlet()+jQuery._uuidlet());
        };
    };
   
    jQuery.fn.uuid = function()
    {
           if (!this[0].uuid)
           {
               this[0].uuid = jQuery.uuid(this);
           }
          
           return this[0].uuid;
    }

    // ... updated experienceOver() method ...
    
    // Show/hide the arrow
    experienceOver: function(e)
    {
       
        var target = $(e.target),
            parents,
            pos,
            uuid = target.uuid();
           
        // Cache parent elements for better performance
        if ( typeof this.parentCache === 'undefined' )
        {
            this.parentCache = {};
        }
        if ( !this.parentCache[ uuid ] )
        {
            this.parentCache[ uuid ] = target.parents('li.company:first');
        }
       
        parents = this.parentCache[ uuid ];
       
        if (parents.length < 1)
        {
            return;
        }
       
        target = parents.eq(0);
       
        pos = target.position();
       
        this.arrow.slideTo(-20, pos.top);
    }

And now the parents() method is called at most once for each element rolled over.