Thursday, March 19, 2015

Why you (as an HTML developer) should start to think of the DOM as a travelling salesman problem

image
I stumbled upon the following jQuery code by another team member (not a Pzl guys as we know better). I challenged him to figure out what was “wrong” with it and he did sort of clue himself in so I’m not outing him here :)

function showMoreButton(className) {
   var alteringDiv = $(className);
   if (!$.isEmptyObject(alteringDiv)) {
       //DOM changes to Search Navigation
       alteringDiv.prepend('<h2 class="ms-displayInline moreButton"></h2>');
       $('.ms-srchnav-overflow>a').detach().appendTo('.moreButton');
       $('.moreButton>a').text('Other');
       $('.moreButton>a').attr('id', 'moreSearchCategories').addClass('collapsed');
       alteringDiv.removeClass('ms-srchnav-overflow');
       $('.moreButton>a').removeClass('ms-srchnav-more-glyph');
   }
}


The code calling the function was formatted as follows:

showMoreButton(".ms-srchnav-overflow")

If you look at the logic it’s pretty simple and works just fine. It retrieves the a DIV with the class ms-srchnav-overflow, adds an element to it, and moves some A tags from another element over to this new element, as well as changing some text, adding an id and fixing up some class names.

So what’s my beef here? Well, for starters the class being sent in is manually used in the detach selector. But the biggest issue is the multiple use of the .moreButton>a selector. For a whopping three times the DOM is searched from the top BODY tag to find a class named moreButton. For this page in particular, that’s a lot of iterations to be made before the tag is found.

My optimized solution merely creates and stores the jQuery objects used, no avoid unneeded selectors and also select only on the smaller scopes needed.

function showMoreButton(className) {
    var alteringDiv = $(className);
    if (!$.isEmptyObject(alteringDiv)) {
       //DOM changes to Search Navigation
       var buttonNode = $('<h2 class="ms-displayInline moreButton"></h2>');
       alteringDiv.prepend(buttonNode);
       var aTag = alteringDiv.children("a");
       aTag.detach().appendTo(buttonNode);
       aTag.text('Other');
       aTag.attr('id', 'moreSearchCategories').addClass('collapsed');
       alteringDiv.removeClass('ms-srchnav-overflow');
       aTag.removeClass('ms-srchnav-more-glyph');
    }
}

And to top it off the selector coming in could be changed to include the closest tag with an id to speed things up even further.

showMoreButton("#globalTopNav .ms-srchnav-overflow")

Back to the travelling salesman problem. The idea is that a salesman should find the shortest path from A to B (and C, D etc.)

A lookup in the DOM on id is approximate to an O(1) operation, while traversing is O(n). What this means is that O(1) with id lookup is like a teleport to the area, and then you walk a short distance to find the right door (class selector). Once the door is found, you don’t move your feet between each knock. The original code would per knock on the door first find the door, then knock once, skip home, and do it all again.

If you have to walk the long path to the door the first time, at least remember where it is. As developers we’re not guppies are we? You might think it’s a small optimization, but not everyone is sitting on quad CPU devices (think Citrix), and raw power is no excuse to not keep a tidy house imo.

Happy selecting!