Article
Deck the Halls with Unobtrusive JavaScript
Sneaking the Presents in Through the Back Door
The second -- and ideal -- way to load JavaScript into an HTML document is to put all of our <script> tags at the very end of the document, right before the closing </body> tag. This allows us to be sure that the DOM is ready to be acted upon, since the code is being loaded after all of the <body>'s HTML is loaded into the DOM. Doing this eliminates the need for the window object's onload event handler. It also greatly reduces the wait between the page's rendering and the execution of our JavaScript, because its execution doesn't depend on an event that's fired only upon the completion of the download of all of the document's dependencies. In this scenario, the code for the popup link would execute a lot sooner, and would probably already be in place before the visitor even considers clicking on the "Sign in" link.
Here's what a back-loaded unobtrusive script looks like:
back-load.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
</head>
<body>
<p class="xmas">
<a href="/signin/" id="signin">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
<script>
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
</script>
</body>
</html>
Note that there's about seven hundred kilobytes of media between our link and our code, but that doesn't matter, because the browser doesn't load media sequentially like it does JavaScript. So it will fire off a handful of requests for media, but it will execute the JavaScript even while that operation is underway.
That said, there may still be problems, even with back loading.
Hide the Presents Till it's Time to Give Them Out
It may happen that your page has a lot of JavaScript to process, or that the server hosting your scripts is experiencing a momentary lag. Even if you're back loading your scripts, situations such as these may keep them from kicking in right away. This might result in strange behavior, such as the aforementioned links not being overridden in time, or even shifting layout problems. The latter issue occurs when you're modifying the DOM via scripting -- for example if you're adding class names that will cause CSS rules to be applied, inserting elements into the DOM, or adjusting the position or dimensions of an existing element. If the JavaScript code runs even a little late, and the changes to the DOM occur after the initial rendering, the result will be that elements will shift on the page, or worse, text will appear briefly before being hidden by the tardy execution of a function.
A technique for dealing with the inevitability of this scenario is to hide the affected content prior to its being rendered. This would mean writing a CSS rule along the following lines:
.modal {
visibility: hidden;
}
We'd give the class name modal to all the anchors in the page that are supposed to trigger a modal popup. We'd then write within our function a line of code that overrides the anchors' default behavior so that once it's done its work, it sets the anchor's visibility to visible, like so:
el.style.visibility = "visible";
We need to be careful, though, not to create new problems while solving other ones. By setting the visibility of all of the links with the modal class name in the page to hidden, we risk locking out anyone who doesn't have JavaScript available. That risk exists because the mechanism responsible for hiding the links is CSS, and the mechanism responsible for making them visible is JavaScript. By spanning two of the layers of separation, we're assuming that "everyone who has CSS also has JavaScript," which isn't always the case. So, what we need to do is create the modal style rule using JavaScript. That way, if JavaScript isn't available, the rule is never created and the links are never hidden. This is a situation where back loading is a bad idea because we want that rule to be available as soon as possible. Here's what our "Sign in" example page would look like if we used this technique:
hide-content.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
<script>
document.write('<style type="text\/css">.modal {visibility: hidden;}<\/style>');
</script>
</head>
<body>
<p class="xmas">
<a href="/signin/" id="signin" class="modal">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
<script>
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
signin.style.visibility = "visible";
</script>
</body>
</html>
You'll note that I've used document.write in order to create the modal style rule. Though I never advocate the use of document.write, this is the one place where I'm prepared to make an exception. This example uses a <style> block, but what I'd normally use on a real site would be an external CSS document that would contain all of the rules that can't be undone without JavaScript -- such as visibility: hidden. Writing the <link> tag that calls that CSS document with document.write is a simple, one-line solution to making sure that the browser calls that file while it's still processing the contents of the <head> (if JavaScript is available).
You'll also note that I've added a line that resets the anchor's visibility right after I've assigned a function to its onclick event handler. In other words, now that I'm sure that the anchor will behave as I want it to, I can turn it back on.
There are many ways to show and hide content, and each is valid in particular contexts. In this situation, I've opted to use visibility: hidden because it preserves the element's dimensions while hiding it. Were I to use display: none, for example, the space that the anchor normally occupies would collapse, and turning it on would cause the layout of the document to shift slightly. Another technique for hiding and showing content is to set an element's position to absolute and its left value to -3000px, sending it off the left edge of the screen. Bringing it back is as easy as either setting its position to relative or static, or giving it a left value that will bring it back into the viewable area of the page.
Wrapping the Presents
So we've back loaded our JavaScript and hidden the content that our code affects, but just popping it onto the screen isn't very graceful, and it gives the visitor absolutely no indication that there's some content on its way. It's kind of like Christmas presents: you don't keep them unwrapped and in your closet until it's time to hand them out. You wrap them up and leave them out so that people know that they've got something coming their way. The same applies to content that you're processing but keeping hidden. The most common way to indicate that something's coming is to use an animated graphic as a visual cue.
Let's add a loader to our "Sign in" anchor:
loader.css
.modal {
background: url(loading.gif) no-repeat center left;
}
.modal a {
visibility: hidden;
}
loader.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
<script>
document.write('<link rel="stylesheet" type="text/css" href="loader.css">');
</script>
</head>
<body>
<div class="xmas">
<p class="modal">
<a href="/signin/" id="signin">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
<script src="loader.js"></script>
</div>
</body>
</html>
loader.js
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
signin.style.visibility = "visible";
signin.parentNode.style.background = "none";
The first thing that I've done here is to create separate CSS and JS files, because our example has grown, and it's always better to keep CSS and JavaScript in separate files. I've added a new CSS background rule that adds a loader graphic to the parent element of our "Sign in" anchor. So while the anchor is hidden, its parent element displays a rotating graphic that indicates that something will be occupying this space momentarily. I've also moved the modal class name up to the anchor's parent element, since we need it to hold our loading graphic. Lastly, I've added one more instruction to our code block; it removes the loader graphic once our onclick assignment operation is complete.
Since this example is so small, you're unlikely to ever experience a delay that's long enough to allow you to see the loader graphic. For this reason, I've put together an example that simulates a two-second delay so that you can see the loader in action.