Style in jQuery Plugins and Why it Matters

"Cowboy" Ben Alman | May 14, 2010

 

Most plugin authors are web designers or developers who have created some cool functionality that they want to share with others. Unfortunately, many plugin authors haven't spent enough time examining other people's code to see what really works and what doesn't.

While the plugin authoring choices you make are ultimately up to you, hopefully you will find these suggestions useful when it comes to developing your personal style and creating plugins, as they are based on real code used in a number of popular jQuery plugins.

This article is split into these main sections:

  1. A few "pro tips"
  2. Playing nice with others
  3. Elements of style
  4. In conclusion

1. A few "pro tips"

When you start creating your plugin, the choices you make will greatly influence not just how well your code performs, but also how maintainable your code is. Remember that as better performing plugin code leads to better overall website or application performance, well thought out, readable code will help ensure that future efforts to maintain or update your plugin will not end in frustration.

Being able to find a balance between these concerns might seem daunting, but as long as you are mindful of your options, you should be able to find a comfortable middle ground.

DRY = Don't repeat yourself

Unfortunately, developers often learn this the hard way. Basically, after spending enough time and effort having to maintain enough poorly-organized code, eventually you'll learn that it's ultimately easiest when you don't repeat yourself. So, in the interest of saving you a lot of headaches later on, I'm mentioning this now.

If you're using the same complex expression in more than one place, assign its value to a variable and use that variable. If you're using the same block of code in more than one place, extrapolate that code into a function that can be called with arguments as needed. The fewer versions of same- or similarly-functioning code you have in your plugin, the easier that plugin is to test and maintain.

Also, because you might not realize that you've repeated yourself until you step back and look at your code, be sure to do just that periodically. Step back, look at your code, and DRY things up a bit!

// Not great: that's a lot of repeated, not-at-all-DRY code.

$('body')
  .bind( 'click', function(e){ console.log( 'click: ', e.target ); })
  .bind( 'dblclick', function(e){ console.log( 'dblclick: ', e.target ); })
  .bind( 'keydown', function(e){ console.log( 'keydown: ', e.target ); })
  .bind( 'keypress', function(e){ console.log( 'keypress: ', e.target ); })
  .bind( 'keyup', function(e){ console.log( 'keyup: ', e.target ); });


// Good: code is more DRY, which is a huge improvement, but it's not as
// obvious what is being done at first glance.

function myBind( name ) {
  $('body').bind( name, function(e){ console.log( name + ': ', e.target ); })
};

myBind( 'click' );
myBind( 'dblclick' );
myBind( 'keydown' );
myBind( 'keypress' );
myBind( 'keyup' );


// Better: the handler has been generalized to use the event.type property,
// and it's totally obvious what is being done, even at first glance.

function myHandler( e ) {
  console.log( e.type + ': ', e.target );
};

$('body')
  .bind( 'click', myHandler )
  .bind( 'dblclick', myHandler )
  .bind( 'keydown', myHandler )
  .bind( 'keypress', myHandler )
  .bind( 'keyup', myHandler );


// Best: really knowing how the jQuery API works can reduce your
// code's complexity and make it even more readable.

$('body').bind( 'click dblclick keydown keypress keyup', function(e){
  console.log( e.type + ': ', e.target );
});

Use the jQuery API

As you can see from the example above, there's no substitute for knowing how to best use the built-in jQuery methods. Read through the API documentation and examples, and by all means, examine the jQuery source as well as the source of other plugins.

The better you know and use the jQuery API, the cleaner and more readable your code will be. In addition, by utilizing the built-in jQuery methods, you will often be able to eliminate portions of your own code, resulting in less for you to have to maintain.

Avoid premature optimization

While optimization can be very important, it's not as important as just getting your code to work, period. The biggest issue with optimization is often that optimized code is often not as easy to read and understand as the pre-optimized code.

The first rule of optimization is only optimize things that need to be optimized. If everything works great, and there are no performance or file size problems, you probably don't need to refactor that code to be faster or smaller and completely unreadable, thus unmaintainable. Spend more of your time working writing code that works.

Here's some code that I progressively optimized for size, resulting in something that minifies quite small, but is pretty much incomprehensible. How maintainable is that? Not very. Either way, I didn't even consider optimizing it until I had it completely working.

The second rule of optimization is “don't optimize too early”. If you're still fleshing out your API, don't go complicating things by reducing very easy-to-read but slightly inefficient logic to a point where you're not even sure what it's doing any more. Save that for the end, after you've written your unit tests. Then you can actually refactor your code while checking for regressions.

Avoid over-avoiding premature optimization

It's unacceptable to use the aforementioned "don't optimize prematurely" mantra as an excuse to write bad code, which brings me to this point. While your code should be as readable as possible, it shouldn't be blatantly un-optimized.

For example, by caching jQuery objects and/or chaining methods, you can see a huge performance boost in your code. A lot of people have said a lot of things about this topic, and while there are many more performance anti-patterns that you should be aware of, I'll simply illustrate this with one example and leave it up to you to do some more performance "best practice" research on your own.

// Bad: very slow, and not even remotely DRY.

$('#foo').appendTo( 'body' );
$('#foo').addClass( 'test' );
$('#foo').show();

// Good: the jQuery object reference is "cached" in elem.

var elem = $('#foo')

elem.appendTo( 'body' );
elem.addClass( 'test' );
elem.show();

// Even better: jQuery methods are chained.

$('#foo')
  .appendTo( 'body' )
  .addClass( 'test' )
  .show();

// And you can even combine caching with chaining, which can be especially
// useful in conditionals.

var elem = $('#foo').appendTo( 'body' );

if ( some_condition ) {
  elem.addClass( 'test' );
} else {
  elem.show();
}

2. Playing nice with others

If you want people to use your plugin, it has to not only provide the functionality that they're expecting, but it has to coexist peacefully with the other code they're using. If people try to use your plugin, and it messes around with other parts of their code, they will stop using your plugin.

People want to use plugins that behave themselves, and if your plugin doesn't behave, they'll find one that will.

Don't modify objects you don't own

I'm not going to get too in-depth on this topic, because Nicholas Zakas already has, so read his article on not modifying objects you don't own. This article directly addresses "if people try to use your plugin, and it messes around with other parts of their code, they won't use your plugin anymore."

Declare your variables

Always declare your variables before you use them, using the varkeyword. Instead of undeclared variables being local, like you might expect, they are actually created in the global scope. These "implicit globals" can conflict with other code, which is bad--see the previous point.

In addition, when trying to maintain code that contains implicit globals, it's often very difficult to know where else those variables are being used, making future maintenance much more complicated and time-consuming.

Use a closure

To that end, by putting your code inside a closure (aka. function), your plugin can have private properties and methods that won't "leak" out into the global namespace. In addition, by keeping this function anonymous and executing it immediately, even this function won't clutter up the global namespace with a leftover reference.

By surrounding your closure function with (...)(); you invoke it immediately, and you can pass in any variables you'd like. In the following example, the top-level plugin function is executed immediately, passing in a reference to jQuery that will be used internally as $. The benefit of this approach is that even if jQuery is operating in noConflict mode, you can still use $in your code, keeping your code very readable.

// Ultra-basic jQuery plugin pattern.
(function($){
  
  var myPrivateProperty = 1;
  
  // Call this public method like $.myMethod();
  $.myMethod = function(){
    // Your non-element-specific jQuery method code here.
  };
  
  // Call this public method like $(elem).myMethod();
  $.fn.myMethod = function(){
    return this.each(function(){
      // Your chainable "jQuery object" method code here.
    });
  };
  
  function myPrivateMethod(){
    // More code.
  };
  
})(jQuery);

Use namespaces when binding event handlers

When binding and unbinding event handlers, use a namespace or function reference for easy and reliable unbinding, without fear of conflicting with other code.

// Bad: this method will unbind every other plugin's 'body' click handlers!
$('body').bind( 'click', handler ); // Bind.
$('body').unbind( 'click' );        // Unbind.

// Good: only the 'body' click handlers bound with the 'yourNamespace'
// namespace are unbound!
$('body').bind( 'click.yourNamespace', handler ); // Bind.
$('body').unbind( 'click.yourNamespace' );        // Unbind.

// Also good: only the 'body' click handlers that reference the 'handler'
// function are unbound (note that since this method requires a function
// reference, it will not work for events bound with an inline anonymous
// function)
$('body').bind( 'click', handler );   // Bind.
$('body').unbind( 'click', handler ); // Unbind.

Use unique data names

Just like with method names and event namespaces, when storing data on an element, use a name that's sufficiently unique. Using an overly generic name can cause conflicts.

// Bad: not a very unique name, may conflict with other code's data.
$('#foo').data( 'text', 'hello world' );

// Good: very unique name, highly unlikely that it will conflict with other
// code's data.
$('#foo').data( 'yourPluginName', 'hello world' );

Also, if you're going to be storing many values in element data, instead of using many individual data names, consider using a single object which will effectively provide a namespace for your properties.

function set_data() {
  var data = {
    text: 'hello world',
    awesome: false
  };
  
  // Store data object all-at-once.
  $('#foo').data( 'yourPluginName', data );
  
  // Updating data.xyz properties will update the data store as well.
  data.awesome = true;
  data.super_awesome = true;
};

function get_data() {
  var data = $('#foo').data( 'yourPluginName' );
  alert( data.super_awesome );
}

set_data();
get_data(); // Alerts true.

3. Elements of style

It can be argued that because coding style is fairly subjective, there's no right or wrong way to do it. The thing is, that argument gets thrown out the door as soon as other people start looking at your code, because they might need to be able to decipher said code in order to track down a bug or add a feature.

When you're coding, don't be afraid to step back for a moment and take a look at the code that you are producing. Is this code well organized? Does it make sense? Is it readable? If it isn’t now, it certainly won’t be in six months when you want to add a new feature.

The most important thing to remember here is that you need to be consistent in developing your own personal style. You don't have to follow these style guidelines, but if you decide to do things your own way, at least have a good reason for it.

Also see Douglas Crockford's article onCode Conventions for JavaScript, upon which many of these suggestions are based.

Line length

Believe it or not, some people still code in a terminal shell, which means that any line over 80 characters is going to wrap in an ugly way. Yes, these people can make their terminal window wider, but you should also ask yourself if any line of code really needs to extend past column 80. If it doesn't, since you can manually wrap your code onto another line better than someone's dumb text editor can, do it!

Suggesting line length limits may sound rather draconian to you, but let me put it into a slightly different context. You know all those web sites with code samples that are hundreds of lines long but also have a horizontal scrollbar, except you can't see the horizontal scrollbar because the code sample is taller than the window? So, you scroll down to the bottom of the code sample to move the scrollbar to the right, except the code you want to read has been scrolled up and out of the viewport, so now you have to scroll back up?

You probably get the idea. Horizontal scrolling in text editors and code samples is horrible. Kill two birds with one stone here, keep your line lengths reasonable, and everyone wins.

Tabs vs. spaces

The age old debate: which is better, tabs or spaces? While it can be argued that the tab character is a more semantically meaningful way to express indentation, and while many text editors allow changing the width of the tab character, not all do. Tabs render especially wide in browsers' source views (and often in article code examples as well), and tabs appearing at the end of a line of code can make quite a mess of end-of-line comments.

2- or 4-space indents, on the other hand, offer enough indentation to differentiate levels of nesting in your code, while leaving enough room on the line for lots of actual code. While many people advocate using 4-space indents, I personally recommend 2-space indents in order to minimize horizontal scrolling or wrapping.

Which of these is easier to read?

// Tabs (simulated with 8-space indents)
function inArray( elem, array ) {
         if ( array.indexOf ) {
                 return array.indexOf( elem );
         }
         
         for ( var i = 0, length = array.length; i < length; i++ ) {
                 if ( array[ i ] === elem ) {
                         return i;
                 }
         }
         
         return -1;
};

// 2-space indents
function inArray( elem, array ) {
   if ( array.indexOf ) {
     return array.indexOf( elem );
   }
   
   for ( var i = 0, length = array.length; i < length; i++ ) {
     if ( array[ i ] === elem ) {
       return i;
     }
   }
   
   return -1;
};

Regardless of your preferred tab settings, the most important thing is that you indent consistently, lining up similarly-nested code blocks appropriately, for maximum readability. Still, wouldn't this code be a bit easier to follow if there was less horizontal scrolling?

Crowding arguments or code blocks

Sometimes less is more, but with whitespace, more is often more. Giving function arguments or code blocks a little "breathing room" often makes things a little more readable. Like anything else, whitespace can be taken to the extreme, resulting in less readable code. Since the ultimate goal is to create more maintainable code, you need to learn to use discretion here, but just remember: don't be overly frugal with whitespace!

Which of these is easier to read?

// Crowded.
function inArray(elem,array) {
   if (array.indexOf) {
     return array.indexOf(elem);
   }
   for (var i=0,length=array.length;i<length;i++) {
     if (array[i]===elem) {
       return i;
     }
   }
   return -1;
};

// Ahh.. a little bit of breathing room.
function inArray( elem, array ) {
   if ( array.indexOf ) {
     return array.indexOf( elem );
   }
   
   for ( var i = 0, length = array.length; i < length; i++ ) {
     if ( array[ i ] === elem ) {
       return i;
     }
   }
   
   return -1;
};

Comments

Write useful comments. You don’t have to write a book, and you should avoid commenting completely obvious code, but just think about who your target audience is going to be, and consider the possibility that they might not be familiar with the variable you happen to be referencing or the specific pattern you may be using.

The person looking at your source code is often someone who is troubleshooting a bug, wants to extend your plugin to make it more awesome, or just wants to learn from what you've done. You should do everything you can to facilitate comprehension of your code.

Also, more often than not, that person who wants to troubleshoot a bug or extend your plugin is you, except that just enough time has passed so that you don't remember why you coded that bit the way you did. Not only will your comments help other people, but they'll help you as well!

Curly braces

While it's technically valid to omit curly braces around blocks in certain scenarios, it can make code look slightly more ambiguous, so don't do it. Always use curly braces to avoid ambiguity where possible, and your code will be more readable.

Also, in JavaScript, because the location where an opening curly brace is placed can change how your code behaves, be sure to format your return statements properly by specifying any opening curly brace on the same line as the returnstatement.

And to be consistent (because consistency is Good), try to put all your opening curly braces on the same line as the statement they follow.

// Not great: this example is relatively obvious.
if ( a === 1 )
  b = 2;

// Bad: this example is somewhat ambiguous..
if ( a === 1 )
  b = 2;
  c = 3;

// Oops! This is how the previous example is actually interpreted!
if ( a === 1 ) {
  b = 2;
}
c = 3;

// Good: this example is completely obvious.
if ( a === 1 ) {
  b = 2;
}

// Good: this example is also completely obvious.
if ( a === 1 ) {
  b = 2;
  c = 3;
}

// Bad: see the above-mentioned link on how the location of opening curly
// braces can change how your code behaves.
function test()
{
  return
  {
    property: true
  };
};

// Good: not only does the function return the object as-expected, but a
// consistent { } style is being used.
function test() {
  return {
    property: true
  };
};

4. In conclusion

Ultimately, while there are many different plugin style considerations, the most important thing is for you to focus on developing your own personal style that balances efficiency with readability and maintainability. Your plugin not only needs to work well, but it needs to be easy to update and maintain, because you're going to be the person maintaining it!

Also, be sure to not only experiment with your own code, but also take time to examine the jQuery source as well as the source of other plugins. The more "other" code you see, the better equipped you will be to decide what works and what doesn't work, which will help you make more informed decisions in the end.

 

About the Author

Ben Alman currently works at Bocoup as Director of Training and Pluginization, where he is responsible for the development of beginner and advanced JavaScript, jQuery and HTML5 training curricula. In addition to his training and client work at Bocoup, Ben writes articles and gives presentations advocating JavaScript and jQuery code organization techniques and best practices.

Ben has created and maintains a number of very popular open source JavaScript projects and jQuery plugins and is a frequent contributor to the open source jQuery, jQuery Mobile and Modernizr projects. As an avid photographer and funk bass player, Ben loves spending time taking photos and jamming in the greater Boston area.

Find Ben on: