2009
Volume 24 Number 0
Extreme ASP.NET Makeover - JavaScript Improvements
By James Kovacs | 2009
Welcome back for the fourth installment of the article series "Extreme ASP.NET Makeover." In Part 3 , we examined how XHTML and Cascading Style Sheets (CSS) can improve the layout and visual appeal of Web sites in general, and the ScrewTurn Wiki site in particular. This article will focus on refactoring ScrewTurn Wiki’s JavaScript using jQuery, and testing it with QUnit.
Lingua Franca of the Programmable Web
JavaScript is the lingua franca of Web programming languages. It doesn’t matter whether you’re programming in .NET, Ruby, Python, PHP or a myriad of other server-side languages, the programming language of the browser is JavaScript. The official standard is more properly called ECMAScript and is specified in ECMA-262. JavaScript, JScript, ActionScript and others are all dialects of ECMAScript, but written by different vendors. Current browsers implement ECMA-262 Edition 3, which has been a standard since 1999. (Work is ongoing on ECMA-262 Edition 5. Edition 4 was abandoned due to irreconcilable differences within the standards committee.) The JavaScript dialect is managed by the Mozilla Foundation and is implemented by Firefox, Google Chrome, Safari and other browsers. JScript is Microsoft’s dialect and is implemented in Internet Explorer. The dialects are compliant with ECMA-262, but offer vendor-specific extensions. Throughout this article, I will use the common practice of referring to the language as JavaScript, though more properly, it should be called ECMAScript.
JavaScript Heritage
JavaScript has a reputation as a toy language. Many developers have the mistaken impression that JavaScript is a dumbed-down version of Java without strong typing. The reality couldn’t be further from the truth. When designing a new language for the Netscape browser, Brendan Eich, JavaScript’s creator, took inspiration from Self (a prototype-based object-oriented language) and Scheme (a functional language). The new language was named Mocha and later renamed LiveScript. With the surge in interest in Java in the late '90s, Mocha/LiveScript was rebranded JavaScript and modified to look more like Java, with curly braces and semicolons. In reality, JavaScript is more closely tied to its functional roots, where functions are treated as first-class citizens of the language. Although many developers still use JavaScript in a very procedural fashion, it is in fact a very powerful functional language that is running inside millions of Web browsers worldwide.
JavaScript Renaissance
A renewed interest in JavaScript has been sparked by recent JavaScript frameworks, such as jQuery, Prototype, YUI, Dojo and MooTools. jQuery has received a lot of attention recently, due in part to its power, simplicity, and ease of extensibility. It was first released by John Resig in 2006, and has since gathered an active community of contributors, plug-in authors and users. According to the jQuery Web site:
“jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.”
jQuery received an additional boost on Sept. 28, 2008, when Scott Guthrie, corporate vice president of the Microsoft Developer Division, announced that Microsoft had partnered with the jQuery development team and would be shipping jQuery with Visual Studio going forward. This was a historic moment because jQuery is released under the MIT open source license. Not only is Microsoft shipping an open source library with Visual Studio, it has committed to shipping the library unmodified and to contributing patches in the same way as any other developer or team.
The latest release, as of this writing, is jQuery 1.3.2, and it supports the major browsers in use today, including Internet Explorer 6.0+, Firefox 2.0+, Safari 3.0+, Opera 9.0+ and Chrome. Web development and JavaScript have garnered a bad reputation in development circles, in large part due to browser-specific inconsistencies, bugs and quirks. To achieve cross-browser support, jQuery provides a consistent API for manipulating the HTML DOM, attaching events, animating elements and AJAX interactions, by internally handling differences between browsers.
Testing JavaScript
Before diving headlong into replacing ScrewTurn Wiki’s classic-style JavaScript with jQuery-style JavaScript, we have to remember the purpose of refactoring – to change the internal implementation of code without changing its behavior. We could change the code and manually verify that the changes did not break anything, but we should be applying the same care with our JavaScript as we do with our server-side code. If this were C#, I would write a test to verify the current behavior, refactor the code and re-run the test to ensure that everything continued to behave as before. Why should I take any less care with client-side JavaScript? The question though is, how do I test JavaScript? The answer comes from jQuery itself.
When developing jQuery, the jQuery team had much the same challenge – to verify that jQuery continued to work as expected, as optimizations and fixes were applied. Its solution was to create a lightweight JavaScript test framework called QUnit. There is nothing jQuery-specific in QUnit, and you can use it to test any JavaScript code. A simple QUnit test might look like this:
test('Concatenated hello should compare equal to hello', function() {
var actual = 'h' + 'e' + 'l' + 'l' + 'o';
var expected = 'hello';
equals(actual, expected);
});
This test is embedded in a Web page test harness, and the results can be viewed in a browser, as shown in Figure 1.
Figure 1 QUnit Tests for ScrewTurn Wiki
The following video takes a more in-depth look at QUnit and how tests integrate into a HTML page test harness.
Refactoring ScrewTurn Wiki’s JavaScript
The first step in refactoring ScrewTurn Wiki’s JavaScript is to extract it into separate JavaScript files. Not only will this allow us to test the JavaScript, but it also will improve page performance. This is due to decreased page size, as the browser can cache the JavaScript files rather than downloading them with every dynamic page. This is mostly a simple operation of moving the JavaScript from MasterPage.master and Default.aspx to ~/JS/ScrewTurnWiki.js, and including a reference to ScrewTurnWiki.js in MasterPage.master. (Moving the JavaScript from other pages to ScrewTurnWiki.js is left as an exercise for the reader.) ScrewTurn Wiki’s MasterPage.master calls Tools.GetIncludes() to automatically generate <script type=”text/javascript”/> tags for all JavaScript files in ~/JS/ and ~/Themes/<NAME>/, so we do not have to manually add ScrewTurnWiki.js.
After moving the scripts to ~/JS/ScrewTurnWiki.js, ScrewTurn Wiki’s main page, Figure 2, looks like this:
Figure 2 ScrewTurn Wiki Main Page
Notice the “Name Size” box that appears at the bottom of the page beneath the Warning? This is the PageAttachmentsDiv that is supposed to be hidden. Here is the code to hide that :
var __elem = document.getElementById("PageAttachmentsDiv");
if (document.getElementById("PageAttachmentsLink")) {
__RepositionDiv(document.getElementById("PageAttachmentsLink"), __elem);
}
__elem.style[:display"] = "none";
Before moving the code, the above JavaScript was placed after the <div id=”PageAttachementsDiv”/> . When the code executed, the <div/> existed and was hidden properly. When the code is moved into ScrewTurnWiki.js, it is executed before the <div/> is created and nothing happens. The <div/> is later created and remains visible. The solution is to simply move the above loose JavaScript into a doWindowLoad() function and execute that function when the window finishes loading:
window.onload = doWindowLoad;
function doWindowLoad() {
var __elem = document.getElementById("PageAttachmentsDiv");
if (document.getElementById("PageAttachmentsLink")) {
__RepositionDiv(document.getElementById("PageAttachmentsLink"), __elem);
}
__elem.style["display"] = "none";
// Additional code that needs to executed when the HTML DOM
// is loaded and ready for manipulation
}
ScrewTurn Wiki now looks and behaves the way it did before the JavaScript was relocated, as we see in Figure 3.
Figure 3 ScrewTurn Wiki Main Page Without the “Name Size” Box
Now that the JavaScript has been relocated into a separate file, we can use QUnit to test whether PageAttachmentsDiv is hidden when the page loads. In QUnitTests.html – our HTML page test harness – we create a dummy <div/> with the same ID:
<div id="PageAttachmentsDiv" style="width:100px;height:100px;"></div>
Then in our test, we explicitly call the doWindowLoad() function and verify that PageAttachmentsDiv is hidden:
test('PageAttachmentsDiv should be initially hidden', function() {
doWindowLoad();
var display = document.getElementById('PageAttachmentsDiv').style['display'];
equals(display, 'none');
});
Using dummy page elements in the test harness is easier than trying to test the actual page elements on the live page. To avoid polluting the test harness output with dummy elements, the containing <div/> can be marked as hidden (display:none;) or absolutely positioned outside of the visible content (position:absolute;left:-1000px;top:-1000px;). The choice of technique depends on what you are trying to test, as hiding the <div/> sometimes interferes with layout-related tests.
Getting Jiggy with jQuery
With our QUnit test harness in place, we are ready to refactor ScrewTurn Wiki’s JavaScript using jQuery. Let’s start by taking a look at hiding the PageAttachmentsDiv:
var element = document.getElementById("PageAttachmentsDiv");
element.style["display"] = "none";
The following jQuery code implements the same functionality:
$('#PageAttachmentsDiv').hide(); // $() is an alias for jquery()
(Note that you need to include a reference to the jQuery library in your page using a <script/> tag.) The jQuery version is terser, using the CSS-style “#” to indicate ID-based selection, and the more declarative “hide()” method. It might seem like a small savings until you realize the power disguised behind the apparent simplicity. What if we want to hide all menus on the page with the CSS class of “menu”?
$('.menu').hide();
There is no need to walk the HTML DOM or iterate for-loops. jQuery handles all of this transparently for you.
jQuery uses CSS-like selectors to operate on collections of HTML DOM elements. So we can play even more powerful tricks, such as hiding all menus, but only if their direct parent is a <div/> with CSS class “toplevel”:
$('div.toplevel > .menu').hide();
To see this code in action, check out:
jQuery is not only encouraging us to write less code, but also a more functional style of code. To make this clearer, consider hiding all menus using procedural-style code:
- Start with the document element.
- For each child element
- If it is a menu, change its style to display: none.
- Else, iterate through its children from Step 2.
A functional style says:
- Hide all menus.
Functional-style code says what to do rather than how to do it. This is a subtle distinction, but an important one.
ASIDE: jQuery IntelliSense
jQuery IntelliSense can be enabled in Visual Studio 2008 SP1 with a supported hotfix from Microsoft as detailed by Scott Guthrie’s blog . Watch the following video for tips, tricks, and gotchas when enabling jQuery IntelliSense in your projects.
Are You Ready?
I glossed over one important point in the jQuery code. jQuery is manipulating the HTML DOM and you can’t do that until the HTML DOM is loaded. Typically, you execute JavaScript code in response to the window.onload event as we did above. Unfortunately, window.onload only fires after the page is done loading, which includes all JavaScript files, images, banners and other resources on the page, which is often later than need be. jQuery provides a convenient, browser-agnostic hook that will execute code as soon as the HTML DOM is ready, but without waiting for all page resources to load:
$(document).ready(function() {
// Hookup event handlers and execute HTML DOM-related code
});
We can refactor our window.onload code to the following:
$(document).ready(function() {
$('#PageAttachmentsDiv).hide();
// Additional HTML DOM-related code
});
One truly nice feature is that hooking the ready event does not overwrite it, but instead adds the function to a stack of registered ready functions. So, unrelated pieces of code can each register for the ready event without interfering with one another.
Separating Your Concerns with jQuery
In your typical Web page, JavaScript is often found liberally sprinkled throughout the HTML. Little snippets of inline code in event handlers to catch clicks, mouse-overs, key presses and more. For example, ScrewTurn Wiki’s admin tools menu is toggled by clicking on the Admin link, which executes a call to the __ToggleAdminToolsMenu() function. (To see the Admin link, log in using the username/password of admin/password.)
<a id="AdminToolsLink" title="Administration Tools" href="#"
onclick="javascript:return __ToggleAdminToolsMenu();">Admin</a>
Rather than having the onclick event wired up in the HTML, we could use the following markup instead:
<a id="AdminToolsLink" title="Administration Tools" href="#">Admin</a>
And then apply the behavior in a script tag in the <head/> or preferably in a separate JavaScript file:
$(document).ready(function() {
$('#AdminToolsLink').click(function() {
return __ToggleAdminToolsMenu();
});
});
jQuery encourages the separation of behavioral code from HTML markup.
It doesn’t stop at moving function calls around. We can use jQuery to further refactor and simplify ScrewTurn Wiki’s JavaScript. Code related to showing/hiding page elements such as Admin Tools and Page Attachments is around 100 lines long, including comments and white space, but not including hooking up onclick, onhover and other events. That might not seem like a lot of code, but we can achieve the same effect for the Admin Tools with the following jQuery:
$(document).ready(function() {
$('#AdminToolsLink').click(function() {
$('#AdminToolsDiv').css({ 'z-index': '1',
'position': 'absolute',
'top': link.position().top + link.outerHeight() + 'px',
'left': (link.position().left
- (div.outerWidth() - link.outerWidth())) + "px"
});
$('#AdminToolsDiv').toggle();
}).click();
});
When AdminToolsLink is clicked, we use the built-in jQuery css() function to reposition the AdminToolsDiv correctly beneath the AdminToolsLink and then use the built-in toggle() function to toggle AdminToolsDiv between visible and hidden. Notice the parameterless click() function on the end. In jQuery, click(function() {}) hooks up an event handler while click() calls it. So we are setting up an event handler and immediately invoking it to toggle the menu to initially hidden. We can use the same technique for the Page Attachments, but we can take this a step further, as shown in Figure 4.
Figure 4 jQuery’s Toggle() Function
$(document).ready(function() {
$('.dropdownMenu').click(function() {
var link = $(this);
var div = link.next('div');
div.css({ 'z-index': '1',
'position': 'absolute',
'top': link.position().top + link.outerHeight() + 'px',
'left': (link.position().left
- (div.outerWidth() - link.outerWidth())) + "px"
});
div.toggle();
}).click();
});
We apply a CSS class of “dropdownMenu” to any page element that we want to use as a drop-down menu. The code above hooks the click event. When clicked, jQuery finds the next <div/> after the clicked element and toggles that <div/> between visible and hidden. We have moved from a procedural style of specifying how page elements are individually shown and hidden to a declarative style whereby applying a CSS class of “dropdownMenu” to an element makes its children toggle automatically. If we decide that we don’t like the toggle() effect, jQuery provides a variety of other visual effects, including fades, slides and custom animations.
You can do a lot more with jQuery, including alternating row styles on tables, mouse-over effects, background loading and display of AJAX data, and more. An advanced UI library has been built on top of jQuery, which is aptly named jQueryUI. It builds on jQuery’s core events and animations to provide rich client-side widgets, such as date pickers, custom dialogs, progress bars, sliders and tabs, as well as rich interactions such as making page elements draggable, resizable and sortable!
Conclusion
This article has focused on using jQuery and QUnit to refactor ScrewTurn Wiki’s JavaScript. By using jQuery, we were able to dramatically reduce the amount of JavaScript required. I have only scratched the surface of what can be accomplished with jQuery. You can learn more about jQuery from Getting Started with jQuery by Jörn Zaefferer, as well as Dino Esposito’s series of MSDN Magazine jQuery articles: “Explore Rich Client Scripting With jQuery, Part 1”, “Explore Rich Client Scripting With jQuery, Part 2”, and “Build Rich User Interfaces with jQuery”. Next time, we will look at incorporating some more advanced jQuery techniques to extend the functionality of the site using jQueryUI.