Many times we want several items to simultaneously disappear from a webpage. Usually this disappearance is triggered when we click something on the page. For instance, last week I was asked to fix an issue with an image map which, when clicked, caused other information to appear . In the middle of the page there was an image of some plumbing and as you clicked on different portions of the plumbing, DIVs full of information were suppose to appear on the right side of the page. The problem, if you clicked the image map more than once, was that the previous DIV failed to disappear, and the new one jumbled up the page. If you clicked 5 times, you ended up with 5 DIVs all visible at once, a real mess for our design. There were 12 boxes of information in all, and 11 of them were suppose to be invisible at all times.
To fix the problem, I pulled together some of my favorite scripts to create some Javascript code that allows all items with the CSS class of causeDisappearance to cause any item with the CSS class of disappearOnClick to disappear.
Note to the graphic designers: this script is easy to use. You just have to include it on the page. Then assign the CSS class disappearOnClick to items you want to have disappear, and assign the CSS class causeDisappearance to any item that, when clicked, should cause those other items to disappear.
A few years ago this kind of code might have been implemented with inline calls to Javascript, like this:
<img src="myImage" onclick="makeTheseItemsDisappear('disappearOnClick'); makeThisItemAppear('infoBox3');" />
In the above we assume we’ve a Javascript function called makeTheseItemsDisappear() which takes the name of a CSS class as a parameter and then finds all the items with that class and sets their display property to “none”. However, inline handlers of that sort go against the principles of unobtrusive Javascript. Ideally, Javascript should be written in a way such that scripts written by different authors can be mixed on a page by a designer, with no collisions or conflicts. Inline handlers are not unobtrusive because several scripts might want to have access to the onclick event handler of a given item.
Few people have done as much to advance the cause of unobtrusive Javascript as Simon Willison. He wrote a number of scripts that needed to be triggered by the onload event handler, and he thought long and hard about how to use the onload event handler without getting into conflict with other scripts that might also want to use the onload event handler. What he came up with is a masterpiece of simplicity:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
In this function, Willison first gets whatever functions might already be stored in the onload event handler of the page. He then combines his code with the old code and puts the combination back into the onload event handler as a new function.
If we want to make all items with a certain CSS class disappear after some event, then we need a function that gets all items of a certain CSS class. One look at Google and we can see that this is a function that many talented programmers have worked on, and there are many different versions. Some of these functions look for all items of a certain class that are within a certain node, and others scan the whole page for items of a certain class. Since any item can have multiple CSS classes, each of these functions attempts to handle the word boundaries that may separate one class name from another. Daniel Glazman has a round up of 4 different functions that all call themselves getElementsByClassName, written by different programmers.
My favorite variation on getElementsByClassName is by the talented Snook. However, I’ve changed his code somewhat. He makes it mandatory that you give this function a node to search inside of. I want the node to be optional, so I’ve added a line that sets the node to the body of the document, if no node was given to the function. So here is my variation on Snook’s code:
function getElementsByClassName(classname, node) {
if (node == "" || node == undefined) {
// this next line is basically a check to see if the page is done loading.
// document.body is undefined till the page has fully loaded.
if (document.body) {
node = document.body;
}
}
var a = [];
// the regular expression tests against word boundaries, in case
// this item has several CSS classes assigned to it.
var re = new RegExp('\\b' + classname + '\\b');
var els = node.getElementsByTagName("*");
for(var i=0,j=els.length; i
return a;
}
Once we have these two functions, above, we can use getElementsByClassName to find every item with the CSS class of causeDisappearance and we can alter those items onclick event handler so that clicking these items causes all items with the CSS class of disappearOnClick to disappear, and we can wrap all this code inside a function that’s being given to addLoadEvent, so that the changing of the onclick event handlers happens onload.
Here we create an anonymous function that gets all the items with a CSS class of disappearOnClick and sets their display property to “none”. We store this anonymous function in a variable called referenceToFunctionToDisappearElements.
var referenceToFunctionToDisappearElements = function() {
var arrayOfAllClickElements = getElementsByClassName("disappearOnClick");
for (var i=0; i < arrayOfAllClickElements.length; i++) {
var referenceToOneElement = arrayOfAllClickElements[i];
referenceToOneElement.style.display = "none";
}
}
Then we use addLoadEvent to add an anonymous function to the onload event handler of this page, so that onload every item with a CSS class of causeDisappearance will have its onclick event handler altered so as to execute the function in referenceToFunctionToDisappearElements when clicked. We are imitating Simon Willison’s code so that if multiple scripts are trying to add to the onclick events of these items, our code will not interfere with those other scripts. At least, not if our code loads after theirs (if their code loads after ours then it may overwrite ours, if their code has not been written in an unobtrusive way).
addLoadEvent(function() {
var arrayOfAllClickElements = getElementsByClassName("causeDisappearance");
var howManyElements = arrayOfAllClickElements.length;
for (var i=0; i < arrayOfAllClickElements.length; i++) {
var referenceToOneElement = arrayOfAllClickElements[i];
var oldClick = referenceToOneElement.onclick;
if (typeof referenceToOneElement.onclick != 'function') {
referenceToOneElement.onclick = referenceToFunctionToDisappearElements;
} else {
referenceToOneElement.onclick = function() {
if (oldClick) {
oldClick();
}
referenceToFunctionToDisappearElements();
}
}
}
});
I should add, when I first put this code on the page, it did not work at all. In fact, the addLoadEvent didn’t even seem to fire. I put in an alert(’hi’); as the first line, and nothing happened. As I poked around the page, I realized this was a very old page written by Dreamweaver a long time ago, and a bunch of Dreamweaver functions had been written into the body tag on the page, using the onload event. So it was completely overwriting what I tried to do here with addLoadEvent. So I took the code out of the body tag and moved it to the external file and wrapped in the addLoadEvent, like this:
addLoadEvent(function() { MM_preloadImages('images/sub/features02_easy.gif', 'images/sub/features03_turn.gif', 'images/sub/features03_rubbersleeve.gif', 'images/sub/features03_kwikfit.gif', 'images/sub/features03_nylontip.gif');
});
Then everything worked fine.
The greatest benefit of this style of unobtrusive Javascript is that it separates the concerns of the graphic designers from the concerns of the programmers. If you use inline Javascript code, then the designers have to be careful not to disturb the programmers code and, more so, the programmers and designers need to work on the same files, and must be careful not to overwrite each other’s work. But using something like addLoadEvent, the scripts are like magic tricks - they just work. With this script, all the designers have to do is include it on the page and assign the CSS classes disappearOnClick and causeDisappearance. Empowering the designers by making it easy for them to use scripts is a step forward in terms of productivity. Simple to use, unobtrusive scripts bring some sanity to a part of web development that has been chaotic in the past.
I like jQuery for this sort of task, since I found it about a month ago. When the page loads, jQuery builds a dot-concatenated xquery-like path to all the elements on the page. I’m amazed at what a simple little javascript class can do (I think it’s like 2200 lines of code).
Sample piece of code:
$("p.surprise").addClass("ohmy").show("slow");