Readable DOM Ready Event Handlers

Monday, March 26, 2012

One reason some people don't like JavaScript, I think, is because idiomatic JavaScript favors fewer keystrokes and CPU cycles over readability. There is a large amount of script code that is hard to read, and even harder to maintain and change.

Take, for example, the following snippet. I think this represents a typical DOM ready event handler with jQuery.

$(function () {
    $("#warning").dialog({ autoOpen: true });
    $("#openDialog").click(function () {
        $("#warning").dialog("open");
    });
    $("#submit").click(function () {
        $("#warning").dialog("close");
    });
    $("#names").autocomplete({
        source: ["Foo", "Bar", "Baz"] 
    });
});

It's a wall of text written in the get in, get done, and get out style. The code requires some concentration to see what is happening inside.

I think the code improves after lifting out the nested, anonymous functions.

$(function () {
    var warning = $("#warning");

    var openDialog = function () {
        warning.dialog("open");
    };

    var closeDialog = function () {
        warning.dialog("close");
    };

    warning.dialog({ autoOpen: false });   
    $("#openDialog").click(openDialog);    
    $("#submit").click(closeDialog);       
    $("#names").autocomplete({
        source: ["Foo", "Bar", "Baz"] 
    });            
});

And to take the idea one step further, here is a pattern I'm going to try and follow over the next few weeks: initialize, implement, act

$(function () {

    var warningDialog = $("#warning");
    var openButton = $("#openDialog");
    var submitButton = $("#submit");
    var nameInput = $("#names");
    var inputValues = ["Foo", "Bar", "Baz"];

    var openDialog = function () {
        warningDialog.dialog("open");
    };

    var closeDialog = function () {
        warningDialog.dialog("close");
    };

    warningDialog.dialog({
        autoOpen: false
    });
    nameInput.autocomplete({
        source: inputValues
    });
    openButton.click(openDialog);
    submitButton.click(closeDialog);
    
});

 

The initialize section initializes variables, and tries to lift as many selectors as possible out of the code that follows. The implement section contains functions and other implementation details for the behavior of a page. The act section is the place for wiring up event handlers, creating widgets, and kickoff any data binding for the page.

The end result is verbose, but I've already found it easier to read and change as the requirements shift.

Thoughts?


Comments
BZ Monday, March 26, 2012
I'd rather see the related code close in proximity rather than spread out into Initialize, Implement, Act. e.g:

var warningDialog = $("#warning");

var openDialog = function () {
warningDialog.dialog("open");
};

var closeDialog = function () {
warningDialog.dialog("close");
};

warningDialog.dialog({
autoOpen: false
});

var nameInput = $("#names");
var inputValues = ["Foo", "Bar", "Baz"];

nameInput.autocomplete({
source: inputValues
});

var openButton = $("#openDialog");
openButton.click(openDialog);

var submitButton = $("#submit");
submitButton.click(closeDialog);
gravatar Tamizhvendan S Monday, March 26, 2012
Hi Scott,

Thanks for bringing this topic into discussion.

My two suggestions to improved the readability more
1. We can get rid of the redundant usage of "var" keyword in declaration by grouping all the declaration in a single "var" keyword.
2. Also I would prefer to use '$' as prefix for variables that stores the jQuery values. E.g $warningDialog, $openButton. The reader will get it in the first attempt without peeking at the declaration.

I've modified the changes mentioned above and the gist is available here

https://gist.github.com/2211436
gravatar Benny Halperin Tuesday, March 27, 2012
Your approach is definitely correct IMHO.

It becomes more acute when you design classes/widgets that can appear multiple times on the same page.

In that case I would pass an options object argument to the widget constructor with selectors that map to the individual elements. Much like the plugin model.

Works beautifully with the revealing prototype design pattern.

A very simple example:

var myWidget = function(options)
{
this.warning = $(options.warningSelector);
}
gravatar Doeke Tuesday, March 27, 2012
I want to start with the observation that I also think that onload-handlers (that's what I call them) are often very unstructured so say the least.

I'm not sure I find the final code better to read. Now I need to remember that the variable "warningDialog" stands for an HTML Element with the ID of "warning". Also, you know you implemented the "initialize, implement, act" pattern, but you need to know this (I would mention the steps in comments to make this more clear).

A good way to structure stuff like this is to use the JSB (JavaScript Binding/Behavior) pattern (see http://dean.edwards.name/jsb/ ). Although I've worked on base2 (which is the ground layer of JSB), I probably not going to use it, because it hasn't gained much popularity. But it's a good starting point as of how to structure stuff.
gravatar Marius Schulz Wednesday, March 28, 2012
I like the idea of splitting up the code into different sections.

You could name the "Act" section "Interact" and thus coin the JavaScript III (Initialize, Implement, Interact) pattern, analogous to the AAA (Arrange, Act, Assert= Unit Testing pattern ;-).
gravatar pita bread Thursday, March 29, 2012
I prefer to write something from scratch, rather than to analyze the code.
gravatar Robert Phillips Thursday, March 29, 2012
Code readability is an overlooked aspect of development, and I really appreciate you bringing this to light. Not everyone is an awesome/hacker developer. Not everyone lives, breathes, and eats code. Most people read English (or the native language of their choice) more easily than program code.

By making code explicit, and arranging it as you would paragraphs in a story, you have something that is quicker and easier to read and maintain.

Yes, the code is more verbose. But it is also easier to understand and maintain by developers at all levels. And performance is usually unaffected.

@pita_bread - that was a joke, right?
gravatar matt Thursday, March 29, 2012
Completely agree with everything except for small personal style things (like only one var and only semi colons when they are needed)

I would add that what you have there is around the upper bound of what I will write before starting to either bust out jquery plugins or objects.
gravatar matt Thursday, March 29, 2012
@BZ: that is fine, as long as you keep hoisting in mind. Even though you are declaring all your variables close to their usage, they actually end up getting hoisted to the top of the function. So what actually happens is something more like this

var openDialog, closeDialog, nameInput, openButton, submitButton,
warningDialog = $("#warning");

openDialog = function () {
warningDialog.dialog("open");
};

closeDialog = function () {
warningDialog.dialog("close");
};

warningDialog.dialog({
autoOpen: false
});

nameInput = $("#names");
inputValues = ["Foo", "Bar", "Baz"];

nameInput.autocomplete({
source: inputValues
});

openButton = $("#openDialog");
openButton.click(openDialog);

submitButton = $("#submit");
submitButton.click(closeDialog);

Which completely doesn't matter 99% of the time, but if you don't realize the way it works and do a "=== undefined" check, it can cause some subtle bugs
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!