20 Apr 2009

Reintroducing a little sanity in working with JavaScript

When I first started working with JavaScript (JS),

I came from a background working with type-safe languages like Java and C++.

Working with JavaScript wasn't as much fun as working with C++ and Java. JavaScript has a very poor image and there are several problems with the language which can lead to very bad coding.

One of the main problems I had with JavaScript was the lack of encapsulation of data, the API especially the DOM-API is rather horrible and the browser compatibility can be a mess. Jquery fixes both the problem with DOM-API and the problem with cross-browser development by wrapping is up in a much more developer-friendly API.

Some of the things I learned from working with the JS-library jQuery, was how to exploit JS's features to get the encapsulation required to make my code a lot cleaner.

Around the same time I started using jQuery ((http://jquery.com)),

I also started using jslint ((http://www.jslint.com/)) to increase the code quality and reduce stupid mistakes.

Jslint is a tool that looks for problems in JS source code. On their website you can copy & paste source code into a form to check the code or you can use a standalone version ((http://www.jslint.com/rhino/index.html or http://www.jslint.com/wsh/index.html))

The benefit from using a tool like jslint is that you find problems with the code, you might not find testing it in a browser or would that a long time to find. Jslint is mean and might hurt your feelings, but your code will be much better if you follow the advise it gives you.

In your source file, you can add options to the jslint parser. These are the options I normally use:

/*jslint undef: true, browser: true */
/*global jQuery, $*/

The first line is options for jslint undef: true will give a warning if a variable or function is used that hasn't been defined or is used before it's defined. browser: true, tells jslint that this script is meant to run in a web-browser and therefor allows variables the browser provides.

The second line tells jslint which global variable are available in this case jQuery and $ because I intend to use jQuery.

To encapsulate my code I use this structure:

/*jslint undef: true, browser: true */
/*global jQuery, $*/

( function( $ ) {
	// Private variables

	// Private functions

	// Public functions
} )( jQuery );

What happens here is that I create an anonymous function which is called immediate with a reference to the jQuery object. This function can only get called once.
Inside this function I've three sections:

  1. Private variables
  2. Private functions
  3. Public functions

You'll notice this is much like a class definition is Java or C++, which is my intension to mimic. ((If you want to make objects from this function: Name the function and remove the implicit call to it ( jQuery ). Then you can call it with new and create an instance of it. ))
In the first two sections I put variables and functions, that can only be reached from within the anonymous outer function.

I'll now write a very simple plug-in to jQuery using this structure.

/*jslint undef: true, browser: true */
/*global jQuery, $*/

( function( $ ) {
	// Private variables
	var enabled = false;

	// Private functions
	var switchPageBackgroundColor = function( ) {
		$( document.body ).css( {
			"background-color" : "#ABBEEC",
		} );
		enabled = true;
	};

	var switchOfPageBackgroundColor = function( ) {
		$( document.body ).css( {
			"background-color" : ""
		} );
		enabled = false;
	};

	// Public functions
	$.switchPageBackgroundColor = function( ) {
		if ( !enabled ) {
			switchPageBackgroundColor( );
		}
	};

	$.switchOfPageBackgroundColor = function( ) {
		if ( enabled ) {
			switchOfPageBackgroundColor( );
		}
	};
} )( jQuery );

This code adds two public functions to the jQuery object switchPageBackgroundColor and switchOfPageBackgroundColor. These two functions are little more then wrappers to the private functions, but it's impossible to call the private functions directly from outside of the anonymous function.

But wait!!! This code has a bug that breaks the code in IE but not in Firefox, can you spot this error?

If you run this code through jslint you should get this error:

Lint at line 11 character 44: Extra comma.
"background-color" : "#ABBEEC",

You might be able to find such an error on your own, but imagine you maintained a large website with 20k+ lines of JS code and you introduced such a bug. How would you find it?

When I worked for ScioSphere I created strict guidelines for the JS source code which I were responsible for. The first and most important rule in those guidelines were “Nothing gets committed or deployed without passing the jslint test”.

Code without the error:

/*jslint undef: true, browser: true */
/*global jQuery, $*/

( function( $ ) {
	// Private variables
	var enabled = false;

	// Private functions
	var switchPageBackgroundColor = function( ) {
		$( document.body ).css( {
			"background-color" : "#ABBEEC"
		} );
		enabled = true;
	};

	var switchOfPageBackgroundColor = function( ) {
		$( document.body ).css( {
			"background-color" : ""
		} );
		enabled = false;
	};

	// Public functions
	$.switchPageBackgroundColor = function( ) {
		if ( !enabled ) {
			switchPageBackgroundColor( );
		}
	};

	$.switchOfPageBackgroundColor = function( ) {
		if ( enabled ) {
			switchOfPageBackgroundColor( );
		}
	};
} )( jQuery );

Tags: