Steve Wellens

Programming in the .Net environment

Sponsors

Links

jQuery Code Does not have to be Ugly

I never pass up a chance to look at jQuery code.  It’s amazing so much power can come from such a tiny package. 

An appealing feature of jQuery is the browser independence it provides.  Every JavaScript book I’ve read, includes code for checking which version, of which browser, the code is running in.  There are a lot of:  if else—if else—if else….statements.  What a nightmare.  No thanks.  jQuery to the rescue!

However, most of the jQuery code I’ve seen uses anonymous functions even when they aren’t needed which makes the code harder to read, buggier and less maintainable.  What a nightmare.  No thanks.  Note: I’m not anti-anonymous-functions; As a matter of fact, I use them in the code below (where appropriate).

Here’s a jQuery sample:

$(document).ready(function(){ 
    $("Input.DataEntry").each(function(){
        if (this.value == "")
            this.style.backgroundColor = "yellow";
        else
            this.style.backgroundColor = "White";
    });             
});

Putting that sample together took more than a few tries.  While it looks trivial, getting the curly braces and parenthesis to match up took some iterative trial and error.  Also, the debugger does not allow you to step into the code as it is written:  Instead, the debugger steps into the jQuery source code.  Yikes! 

Here’s a cleaner way to connect a function to a jQuery event (Hint: don’t use an anonymous function):

$(document).ready(DocReady);
 
function DocReady()
{       
       ColorCodeTextBoxes();
}

It is obvious what is being done in the code above…no problems with nesting curly braces and parenthesis.  When the document is ready, the DocReady function is called.  Let’s add a bit more code to show the advantages of doing it this way:

// ---- Main Doc Ready function -----------------
 
$(document).ready(DocReady);
 
function DocReady()
{       
    AssignClickToToggleButtons();
    ColorCodeTextBoxes();
}
 
// ---- ColorCodeTextBoxes -----------------------
//
// If a DataEntry textbox is empty, color it yellow
// otherwise color it white.
// To Do: use CSS classes instead of hard-coded color
 
function ColorCodeTextBoxes()
{
    var TextBoxes = $(":text.DataEntry");
 
    TextBoxes.each(function()
    {
        if (this.value == "")
            this.style.backgroundColor = "yellow";
        else
            this.style.backgroundColor = "White";
    });
}
// ---- AssignClickToToggleButtons -----------------------
//
// add function to Toggle buttons click event.
// the function toggles the button's color with each click
 
function AssignClickToToggleButtons()
{
    $(".ToggleButton").click (function()
    {
        $(this).toggleClass('RedBackground').toggleClass('GrayBackground');
    })
}

Hooking up a standalone function to an event is better than jamming the contents of the function into the event as an anonymous function.  Here are some reasons why:

  • The function can be assigned to other events*.
  • The function can be debugged as a standalone object.
  • The function can be better commented.
  • The function can easily be removed, i.e.:  // ColorCodeTextBoxs();

*In our example, you would want to Color Code the textboxes when the form loads and when it fails to submit to indicate which fields have been left blank.

Some may point out that by chaining results and nesting functions you avoid intermediate variables.   To those people I give the Programmer’s Curse:  “Someday, I hope you have to maintain your own code.”

Note that intermediate variables are absolutely mandatory when you are debugging: If you want to watch an object, you have to have something to watch.

I do concede that intermediate variables and non-nested functions may make the code run a few nanoseconds slower.

However, let me point out that just as real-estate agents have the phrase, “Location, Location, Location,” developers should adopt the phrase, “Maintenance, Maintenance, Maintenance.”

I hope someone finds this useful.

Steve Wellens

Posted: Jan 25 2010, 01:45 PM by SGWellens | with 29 comment(s)
Filed under: ,

Comments

Brock said:

One of the beautiful things about jQuery is that is does promote the use of anonymous functions such that you don't pollute the global namespace with your own functions.

If you don't like anonymous functions and to prevent littering the global namespace, use a closure, define a local for your function and then register the local variable for your event handlers.

Another comment: the common patten in JavaScript is to only use PacsalCasing if your function is a constructor and use camelCasing for functions and methods.

# January 25, 2010 3:37 PM

paul.vencill said:

I would comment that the one problem w/ the way you chose to approach the problem is that (generally) you should not use global functions where they can be avoided.  Namespace pollution and all that.

What many developers do instead is have an object that holds their functions.  

If you're just supporting your own page, the global functions are probably less of a big deal, of course, but as a habit, I typically will create an object based on either my domain or the customer's domain (if it's custom code for them) to hold the functions.

Well-thought names as you suggest are, of course, important as well.

# January 25, 2010 3:53 PM

koolraagaa said:

Nice writeup.

It is very necessary to have a good nomenclature for the whole software products.

# January 26, 2010 10:26 AM

KT said:

You've given me a good rule for my jQuery - if I can simply debug it without re-writing my functions and variables, it's probably at least on the path to maintainable.

# January 27, 2010 8:08 AM

Bryant said:

good article, but to take it one step further... I usually have an object that I instantiate after the .ready(); is fired. Its usually called main, and looks like this...

$(document).ready(function(){

       mainObj = new main();

});

Then I have prototyped functions onto that main object for handling all the Events and various other JS objects.

... Also one other simple convention I started following is after I do a jQuery selector, like in your code -- var TextBoxes = $(":text.DataEntry"); --, I will append a "$" onto the front of the variable name, so that I know that the data is a jQuery object and not just a DOM element.... so in your example it would look like...

var $TextBoxes = $(":text.DataEntry");

great post though!

# January 31, 2010 11:21 AM

Timmy said:

Paul and Brock touched on what I would say, but you made some excellent points.

# January 31, 2010 12:56 PM

Irfaan said:

Hi,

It is a great post. I would like to add another reason that The function can be parametrized.

# February 1, 2010 9:59 AM

SGWellens said:

>>... The function can be parametrized.

Nested functions can also use parameters.  However, it just adds to the messy nature of things when using nested anonymous functions.

For some reason,I'm reminded of my nephew fishing.  He spends more time untangling a bird's nest of fishing-line in his lap than he does fishing.

# February 1, 2010 10:23 AM

Irfaan said:

I agree on nested functions. What I was talking about was an attempt to generalizing a piece of code by encapsulating it in a method and passing parameters to it. Whether it be JQuery code or ordinary JavaScript. Though, parameters can not be very useful if there purpose is just to select DOM or JQuery objects as JQuery selectors are there for this purpose, but I may not want to rely on styles every time.

# February 1, 2010 4:30 PM

Charles Boyung said:

I've always felt that those JavaScript developers that rely on anonymous functions do so more because they are more concerned with keeping their jobs (because they are the only ones that can figure out their incredibly messy code) than they are with actually writing high quality code.  If you don't want global functions, then fine, use an object or something like that (as others have mentioned), but to say that anonymous functions are "one of the beautiful things about jQuery" is a sign to me of a true lack of understanding of maintainability.

# February 1, 2010 5:32 PM

SGWellens said:

I hear ya brother.

By the way, if a JavaScript function is declared in a web page, it's just 'global' to the page.  If you think of the page as an object, it's not global at all.

# February 1, 2010 6:22 PM

Zeeshan Umar said:

It is really a big problem for me , there are times when I see my JQuery code after some time and unable to understand that code :)

Hopefully, this will not happen in future and I am looking forward for some more ways I can improve the writing style.

# February 11, 2010 11:37 AM

gurjeet said:

thats waz cool mate!

# February 14, 2010 12:44 PM

malcolms said:

Another side point is you should === instead of == when comparing the values.

Nice article.

# March 21, 2010 11:01 PM

Luiz Esmiralha said:

It's amazing how a tiny thing like jQuery can pack so much obfuscation! Terrible code that reads like an abnormal singing an Elton John song in Aramaic.

# April 22, 2010 5:24 PM