As the use of JavaScript continues to grow in its involvement with developing cutting edge web applications, so too will grow the amount of JavaScript developers will be asked to write. In the pasts few years many JavaScript framework have emerged to help ease the burden of writing large amounts of JavaScript by encapsulating common operations and providing abstraction against the variety of JavaScript Engine implementations among the popular browsers. However, even with these frameworks in place JavaScript can still a pain in the butt to code and maintain. Below I will talk about some tips that can be used to improve the code your write as well as increase the productivity of writing the code.
At the end of this article I have posted the code for a very simple ASP .NET application that employ’s these strategies in its design.
Unobtrusive JavaScript
We often talk about the graceful degradation with respect to developing computer applications. How to handle a condition where the program cannot achieve a desired result become circumstances disallow it; the same is true when developing a JavaScript driven application. In its purest form developing an Unobtrusive JavaScript application involves an application that functions with or without JavaScript. The principle idea is that JavaScript should NOT drive a page but rather should AUGMENT it.
While this idea is very good in practice, it is often never done to the purest form due to budget or time considerations. However, the central point of Unobtrusive JavaScript is the user should be alerted that this application can work better with JavaScript. The user should not click on features with the expectation that they are available and have the feature be “dead” due to lack of JavaScript.
The main practice with Unobtrusive JavaScript is to separate all JavaScript code out from the presentation, that is the HTML. This means no defining of event handlers in the tag’s themselves, but rather using an external JavaScript file to augment the page. This has many advantages, for one it allows you to more easily take advantage of certain syntaxes such as closure available to you from JavaScript. In addition, it also allows the browser to cache the JS file, therefore lightening the load when the document is being downloaded. This is the same practice we employ with CSS.
You can almost consider there to be a fourth layer to the application design: Models, Controllers, View, and Interaction. The additional layer interaction, is the layer that allows the view to manipulate it self as well as talk to the controllers; basically provide interactivity to the view.
Closure
Closure is a syntax advantage that can be leveraged to help obfuscate your code and provide decreased visibility to sensitive values. It can also help to circumvent undesirable parsing of values in certain situations. When developing JavaScript intensive application you always need to consider malformed data as a potential security risk. Remember that using a tool such as Firebug users can have free reign with your pages and even wire up their own JavaScript events to call your services. The code is all right there in front of them and the security of these services is something you must be constantly aware of.
By using closure you can effectively store your values in the JavaScript runtime, I have an example of this in my sample application available for download. But JQuery takes this a step further by providing the JQuery data cache which allows you to store data on a per node basis using a hashtable; consider the following code:
var totalAge = 15; $("#totalAge").text(totalAge).data("total", totalAge);
Again we can put whatever data we want in the data cache and identify it with a key. As you probably have guessed this is not as secure as the closure idea since our user could easily wire up a JavaScript event to alert back the values used here. What I tend to use this for is to store data that I can use in client side operations so that I dont have to parse numbers, but rather can read the value in native JavaScript format.
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Prototype & JQuery Plugin Architecture
If you have ever written a .NET extension method then you already understand the basics of JavaScript prototyping. JavaScript contains several standard types: Number, Date, String, Integer, Array, Boolean, as well as others. All of these types contain a property named prototype that can be used to add custom methods onto the type. However, much like the namespace containing the extension method must be available in .NET, you must have the prototyped method definition in scope on the page the use it. You define a prototype method like this:
1: String.prototype.parseToNumber = function() {
2: // convert to a number
3: var number = parseInt(this);
4: if (!isNaN(number)) {
5: return number;
6: }
7:
8: return 0;
9: }
Here we are creating a new function for a String that will attempt to parse the string value to a Number, notice that we return zero if we are unable to do this. So we can see its usage here:
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
var totalAge = $("#listing :text").SumContainer(function($obj) { return $obj.val().parseToNumber(); });
By using prototype functions we can promote cleaner more reliable code the emphasis reuse and centralizes functionality to make maintenance easier. Also included in this sample is a custom JQuery plugin I wrote called SumContainer. Remember that JQuery is essentially a query language built on top of JQuery. We make queries into the DOM and get results, we then call functions to act on the results. Here is the code for the SumContainer plugin:
1: jQuery.fn.SumContainer = function(fn) {
2: var total = 0;
3: this.each(function() {
4: value = fn(jQuery(this));
5: total += value;
6: });
7:
8: return total;
9: };
What this does is, by using fn we effectively prototype JQuery with a new function called SumContainer, which takes as a parameter a function that denotes HOW to get the value to sum. As you will notice above when we call SumContainer we pass it an anonymous function that is passed a JQuery reference to the current element within the plugin.
While this technique is, to my knowledge, contained to just the JQuery world, you can understand that both of these techniques are doing roughly the same thing: centralizing code and make it easier to create libraries and frameworks to support your code; in fact the vast majority of frameworks and libraries and built on these two concepts.
To further demonstrate these techniques and tips I invite you to download my code and the slides from this presentation. I have done the best I can commenting the source code to help you understand what is going, but please feel free to leave a comment with questions.
http://cid-18f9d8896416d2fb.skydrive.live.com/embedgrid.aspx/BlogFiles/JavaScript.DesignTips.zip
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }