Javascript Tagging System
Aim
I needed a way for users to browse through the projects on my site using tags, but I didn't want the tagging system to be based server side. If it was server side you'd have excessive lag and added server stress, all for merely rearranging the same content. Thus, the only real option for client side programming was Javascript. All I wanted was to write up a list of comma separated tags and have the tagging system automatically handle the rest.
Written tags (python, graphics...) => HTML tags (<a href="#"...>python</a>...) => Show/Hide
Initial Implementation
The initial implementation was done in only a few minutes with fairly standard Javascript for handling the HTML tag creation and the show and hide functionality handled by Jquery (fadeIn and fadeOut respectively). This initial implementation seemed to work well, and had all I basically wanted - the issue was when my friend asked me how the system scaled - how many entries could it handle without dying?
Turns out it wasn't as much as one might have thought or liked... At ~40 we hit Matrix bullet time (ie, freeze frame)
"Generic uhoh" - this needs to be fixed. I could understand if it choked and died at 150 or 200 entries, but 40 entries is well within the realm of possibilities - and worse than that this is all on relatively modern and powerful computers! Even though Javascript isn't known as a fast language I'd seen it in the past handle some huge data sets - thus it fell to the fault of the programmer.
You may hate to admit it, but if it doesn't work, chances are it's your fault ^_^
Optimising the Beast (or why Jquery is so damned pretty...)
The original coding was similar to the following...
for (i=0; i < $(".entry").length; i++) {
var curr = $(".entry")[i];
if ($(".tags", curr ).text().indexOf(tag) == -1) $(".entry:eq("+i+")").fadeOut("slow");
else $(".entry:eq("+i+")").fadeIn("slow");
}
One of Jquery's greatest strengths is it's ability to use selectors and chain functions creatively. Using this, the code ended up as two lines -
$("div.entry:hidden > div.taglinks:contains('" + tag + "')").parent().fadeIn();
$("div.entry:visible > div.taglinks:not(:contains('" + tag + "'))").parent().fadeOut();
There are a few core ideas going on here -
- All the entries are split into two categories - currently visible and currently hidden. This is to avoid unnecessary show/hide operations on entries that are already in that state. As opposed to the simple for loop example earlier, it is faster for Jquery to only return the elements that are/n't hidden as opposed to placing an if statement to test the state and react accordingly.
- The type of DOM element is listed for every object - in many Jquery examples the code says the equivalent of
$(".entry")Whilst this is fine in most cases, it becomes a burden on intensive repeated lookups as Jquery has to query every single DOM element. By noting the element type like$("div.entry")you can cut down on the time nicely by only checking div nodes. - Use of the 'parent' function call means that the DOM doesn't need to be queried again to find the relevant element. In the earlier for loop this was necessary, and any request to DOM is somewhat vicious on efficiency.