Posts Tagged ‘optimisation’

My jQuery plugin writing tips

Wednesday, September 2nd, 2009

I’ve now written two jQuery plugins, and have a pretty good idea for a third. As you might expect, I’ve learned quite a few things along the way. When I started there was a lot I didn’t know about javascript and jQuery. There still is, but one thing that struck me is that there are a lot of great tutorials on writing jQuery plugins out there but none of them individually captures all the things that took my plugins from being nice ideas with mediocre implementations to being something I’m proud of.

So here is my definitive combination of them all, together with one or two of my own contributions (a bit scant on detail, but follow the links for more info).

1. Build a good test page

My test page for any given plugin has 3 instances of a type of element the plugin is suppose to effect. eg a list. They are marked up as follows:

<ul class="single" >...</ul>
<ul class="multiple" >...</ul>
<ul class="multiple" >...</ul>

And I run the following script in the document head

$(document).ready(function() {
  $('.single').myPlugin();
  $('.multiple').myPlugin();
});

This will make sure your plugin works when called more than once on a page, and when one instance affects more than one element. For testing your code for speed the same page with various bits of html copied lots of times does the trick.

2. Follow jQuery’s recommended tutorial

It really is very good, and covers most important points. The other items on the list are either little tweaks that I think make the code neater or, in just one or two cases (highlighted in red), things its missed out. Read the tutorial

3. Make sure your plugin won’t conflict with other javascript libraries

The most effective way to do this is to wrap your whole plugin in a function where $ is its parameter, and then call it on jQuery, like so:

(function($) {
  $.fn.pluginName = function(options) { ... };
})(jQuery);

More info

4. Make your plugin’s settings changeable, both when the plugin is called, and by tweaking the default settings

The way to do this is to set up your plugin’s defaults as a JSON which is a public property of the plugin object. So within your plugin add these lines (where options is the name of the JSON passed to the plugin as a parameter).

this.defaults = {parameter:'value', ...};
options = $.extend(defaults,options);

More info

5. Make sure your plugin keeps track of DOM elements effectively

Structure your code as follows in order to make sure your variables are accessible to the right instances of the plugin, but aren’t stored in memory longer than they need to be.

$.fn.pluginName = function(options) {
  var constructor1, constructor2, ...;//elements that are used only when the plugIn is constructing instances of itself at the start and play no role in handling later events;
  return this.each(function() {
     var tracker1, tracker2, ...;//elements that are important for events and other later changes. Also, if your plugin's constructor involves some AJAX it may be best to put all variables in here
     subFunction() {
	//this should always be able to call the variables it needs, in particular access the right DOM elements
     }
  });
};

6. Optimise, opimise, optimise

You never know how demanding the website using your plugin is going to be: how many times is it going to be called on one page; will the dom objects (eg sorting a list of items) it has to handle be a lot bigger than the ones you’ve tested it on. My crossSelect plugin was, I discovered, before version 4/5, really sluggish on lists larger than about 20 items.

So I found a great list of techniques to optimise your code, the most pertinent to writing plugins I have put below:

  • Make sure your optimisations are optimisations

    To keep track of whether your optimisations are having the desired effect, run your plugin on a page full of huge elements for it to process and then use firebug to measure the time it takes.

  • Don’t over-query the DOM

    jQuery is principallya tool for accessing and manipulating the DOM, but it doesn’t pay to do this too often as your code will be a lot quicker if you keep it to a minimum. Techniques for avoiding this include:

    • store DOM elements you know you will have to access a few times in variables (but be careful – see point 5)
    • Add new DOM elements as html, . So instead of
      $('selector').append('<ul>');
      for(i=0;i<limit;i++)
      {
        $('selector > ul').append('<li>'+i-th text+'<li>');
      }

      use

      string = '<ul>';
      for(i=0;i<limit;i++)
      {
        string+='<li>'+i-th text+'<li>';
      }
      $('selector').append(string);

      The improvement in speed of code is astounding.

    • Use contexts. A lot. It will both reduce code size (as shown below) and speed up the code (because jQuery only has to search a subset of the DOM). So instead of
      var theContainer = $('selector');
      $(theContainer).children('ul').children('li').aMethod();

      use

      var theContainer = $('selector');
      $('ul>li', theContainer).aMethod();

And that’s yer lot. Please let me know if you have any comments or suggested additions.

Optimize how?

Thursday, July 2nd, 2009

Yesterday and today I’ve been rewriting my jQuery crossSelect plugin (probably over half of the code has changed) to; a) Fix the serious bugs brought about by trying to bring my plugin closer in line with how it’s supposed to be done, without fully understanding the implications in advance; b) make the code more efficient, in part applying the ideas in this excellent article; and c) prepare the code for bringing in more functionality in later releases.

With regard to c), the main thing I needed to do was rewrite all my selection and removal functions so that moving many items into the selected column at once could just be the move one item function iterated a number of times. I’ve now ( I think) found a pretty efficient solution (each move many function is only 3 lines long), but along the way I came across an interesting dilemma.

My selectOne() function essentially moved a list item and then checks how many items are in each list before adjusting the buttons appropriately. Now, to do a selectAll() or a selectMany() the obvious thing to do is just to iterate that selectOne() function over all list items – just a handful of lines of code – … but this unfortunately leads to a less efficient (and probably slower) function. Writing a selectAll()/selectMany() function from scratch would enable me to only adjust the buttons once and, in the selectAll() case, not have to care about tracking which list item I’m dealing with as they all get moved over in the end… but this way would not only be less elegant, I feel, but also lead to more lines of code.

I’d always assumed that optimising code meant two things – faster and smaller – and I’d always thought that one more or less implies the other. Turns out I was wrong.

In the end, the escape from this trade off involved removing the button adjustment from selectOne() and putting it in selectNow(), a new function triggered by a click. But this required a feature of jQuery which I’ll talk about in some other post.