Unobtrusive show/hide behavior reloaded

In the past two years the JavaScript community has done a lot of effort in redefining how JavaScript should be applied, in order for it to co-exist with other web standards like (X)HTML and CSS.

Modern JavaScript should be unobtrusive, which is short for accessible, usable and easy applicable. To accomplish this, behavior and structure should be separated and JavaScript should be used as an enhancement of structural markup, rather than core functionality that causes a page not to fully function when JavaScript is unsupported or disabled.

All this has resulted in a series of new articles, of which a few describe solutions to show/hide or collapse/expand page elements. Examples are the following articles written by Stuart Langridge, Simon Willison and Christian Heilmann.

In this article I will build on the good work done by these fellow colleagues. In the first half of the article I will discuss a common display problem for all these show/hide techniques, and offer a solution how to overcome it. In the second half I will extend the concept, by offering an alternative technique to show and hide elements. The added value of this method is that it offers an increased flexibility for additional functionality, like the categorization of content.

Deconstructing unobtrusive show/hide techniques

Let us first take a look at how show/hide solutions are constructed in general. All of the techniques above are based on the same principles and contain the following elements:

  1. Semantic (X)HTML without any inline JavaScript.
  2. The page contains a set of links to toggle the display of elements and elements to be shown or hidden. A href attribute of a toggle link refers to the id attribute of a toggleable section, e.g. <a href="#cow">...</a> maps to <div id="cow">...</div>.
  3. An external JavaScript file is linked in the header of the page. It contains a window.onload event that calls a main initialization function as soon as it gets fired.
  4. The main initialization function hides all toggleable elements and adds an onclick handler to the toggle links. If this event is fired, a function is called to show or hide a certain toggleable element.
  5. This show/hide function uses document.getElementById(id).style.display = 'none'; to hide elements and document.getElementById(id).style.display = 'block'; to show them.

Now let's step to the display problem. If you examine the examples above, you will notice that there is a brief moment where all toggleable elements are visible, before they get hidden. Note that the examples are low on content, so it may be that you will not encounter this flickering effect. For this I have created example 1a, which is based on Christian Heilmann's example and contains some additional content and images. You can view the linked JavaScript file here. Note that if you reload the page it may be cached, so you may not experience the effect again until you clear your browser's cache or use a 'hard reload'.

I took this solution to two real world clients and both times this 'flash of visible content' did not pass their user acceptance tests. It didn't matter how nice the philosophy behind the code was, my clients just did not accept it as it was. So I had to look for a solution.

The silent tears of the onload event

It is a public secret that the window.onload event lies at the core of this problem. It will only execute after the whole document, including all page assets like images and objects, is loaded. So if you work with a lot of content, a series of images or a slow server, you will always experience this negative side-effect.

When you apply unobtrusive behavior, the window.onload event by itself is a poor tool. What we ideally would like to use is an event handler that executes as soon as a certain element is loaded, or one that fires after the document structure is read and before any assets are loaded, like Peter-Paul Koch suggested. Unfortunately these events do not exist yet. It seems that developers around the world will keep on hitting this same brick wall, until a set of additional load events are added to the JavaScript specifications.

A band-aid while awaiting the real cure

To solve this display issue, we could add the IDs or classes of our toggleable elements to our CSS file and give it a default display: none; declaration. However by doing this we would breach the accessibility of our document, because when JavaScript is unsupported or disabled, all toggleable elements will be hidden.

The solution is very close. If we would put these style declarations in a separate external CSS file and would call this file from our linked JavaScript file, this would solve both our problems. First the styles that hide the toggleable sections will be loaded as soon as our external JavaScript file is included and executed in the head of our document. Second, this CSS file will only be loaded when JavaScript is available.

So a document.write('<link rel="stylesheet" type="text/css" href="js_hide.css" />'); would do the trick. We should only load this style sheet when there is enough W3C DOM support to add the actual show/hide behavior.

Please test our updated example 1b and have a look at its JavaScript and CSS file. For a code walkthrough, please read Christian Heilmann's article and the additional comments within the source code. Note that we still hide all toggleable sections with JavaScript for the highly improbable case that CSS is disabled. Only in this case the 'flash of visible content' will still occur. Next we don't have the amount of flexibility compared with a fully scripted solution. In this case it is impossible to hide elements with dynamic IDs or classes, at least not without a workaround.

UPDATE (28 September 05): Please read Using dynamic CSS to hide content before page load for an updated solution.

The concept of cloning hidden content

If we look at the five general elements of current show/hide scripts, we could replace the last bullet with a different technique to show and hide elements. Instead of relying on document.getElementById(id).style.display, we could use our node traversal and creation/removal tools to dynamically create, clone, remove or swap content.

Here is how that would work. As usual we hide all our toggleable elements, but this time we keep them hidden and use them as a 'backstage content pool'. Instead of toggling the display of these elements to show and hide them, we now clone them and add them dynamically as new elements to our page. If we wrap these new elements in a container and give this generated container a certain id, we can easily delete or swap its contents.

Please take a look at example 2 and its JavaScript and CSS file. Note that the only thing that has changed compared to the previous example is that the show and hide functions in our JavaScript file are replaced by a brand new toggle function:

function toggle(s) {
	var id = s.href.match(/#(\w.+)/)[1];
	var clone = document.getElementById(id).cloneNode(true);
	var cloneDiv = document.getElementById('cloneDiv');
	if (cloneDiv) {
		cloneDiv.replaceChild(clone, cloneDiv.firstChild);	
	}
	else {
		var cloneDiv = document.createElement('div');
		cloneDiv.setAttribute('id', 'cloneDiv');
		cloneDiv.appendChild(clone);
		document.body.appendChild(cloneDiv);
	}
}

In line 2 we first retrieve the ID of the active toggleable section. Next we clone this element from the content pool (line 3). For convenience in making references, we wrap our cloned element in a generated container element. In line 4 we try to get a reference to an existing container element. If the container already exists (line 5 to 7), we simply replace the previously cloned toggleable element with the new one. If this container isn't generated yet (line 8 to 13), we create the container element, add an id attribute, append the cloned element, and append the resulting new container element to the document's body.

Some people will probably think: 'Nice trick, but less elegant than the normal method'. I agree that the other technique is a bit shorter and may be more transparent. But then again, does it really matter? The beauty of unobtrusive behavior is that you apply it to the same semantic (X)HTML document and that you simply link this JavaScript file that does the trick for you. In the end it is just different code that offers exactly the same functionality. Any way, let's move on to an example where it makes perfect sense to use content cloning.

The categorized links example

The real gain of this new approach is its flexibility. With the traditional show/hide method you can perfectly toggle the display of elements in an existing page structure. But what if you want to change this structure itself? In this case our node traversal and creation/removal tools put us back in the driver's seat, because they enable us to take any content from the content pool and display it in any order we like.

Say you want to categorize content on a true content level and show/hide multiple content elements from the same category. Let's apply this to a regular links page, one of a web designer stuffed with links about web design and development, organized in basic categories. A link to Jeffrey Zeldman in the XHTML section, one to Eric Meyer in the CSS section. Let's presume we start with example 3a (you can view its source code here).

At this moment all our content is segregated in a few main categories on a block level. If we would want to categorize it at a content level, like that Jeffrey Zeldman should also be in the CSS bucket, we could duplicate our content. However it would be a far more elegant and flexible solution if we could add some categorization meta data to this content.

If we would add a class attribute to the <li> elements that hold our links, it would enable us to add multiple space-separated category descriptions to our content. Our updated code would look like this. Add some updated show/hide behavior and we would get example 3b. The result is a regular links page enhanced with show/hide behavior that enables us to toggle between sections of content that are categorized on a true content level. You will have noticed that most websites belong to multiple categories now.

Let's take a closer look at our renewed toggle function:

function toggle(l) {
	var oldCloneUL = document.getElementById('cloneUL');
	if (oldCloneUL) document.body.removeChild(oldCloneUL);
	var cloneUL = document.createElement('ul');
	cloneUL.setAttribute('id', 'cloneUL');
	document.body.appendChild(cloneUL);
	var lis = document.getElementById('contentPool').getElementsByTagName('li');
	var cat_class = l.href.match(/#(\w.+)/)[1];
	for (var i = 0; i < lis.length; i++) {
		if (lis[i].className.indexOf(cat_class) != -1) {
			var clone = lis[i].cloneNode(true);
			cloneUL.appendChild(clone);
		}
	}
}

In line 2 we try to get a reference to an existing previously generated container element. In case this container element exists, we remove it (line 3). Next we create a new unordered list container element, add an id attribute and append it to the body element (lines 4 to 6). Next we get a reference to all the list item elements from the content pool (line 7), retrieve the class name of the active category (line 8), clone all the <li> elements from the content pool that have the same category class name assigned (line 11) and append them to the container element (line 12).

In this example our hidden content cloning concept enabled us to grab multiple <li> elements from different <ul> elements and build a new unordered list. It would take far more effort to achieve the same effect using the display values of the style attribute.

Evaluating the example

In the beginning of the article I stated that modern JavaScript should be accessible, easy applicable and usable. Well, let's see how our last example rates on these three scales.

If JavaScript is unsupported, disabled or is supported unsufficiently, the regular, unenhanced page is shown. If JavaScript is enabled, in most modern browsers we can use our keyboard next to our mouse to toggle the display of our content.

Our example can easily be applied to a links page which (X)HTML has the same signature. Should our JavaScript code be more generic? My own rule of thumb is that if I cannot reuse my scripts for similar types of functionality in different pages, I should consider refactoring my JavaScript functions. Please read executing JavaScript on page load if you want to add multiple onload handlers to a page and don't want over-ride any previously assigned callbacks from other scripts.

Can you enlarge the usability of a web page by hiding most of its content? I personally prefer a 'contextual click' over having to scroll through a large page full of content, especially when the toggleable content is displayed instantly.

However our design is not finished yet. We still have to make clear to the user that the sections are expandable and collapsible and what the active section is. We could for example use a tabular layout. What about a section that is shown by default? And what about an additional bit of JavaScript that appends the cloned elements on alphabet?

Well, let's borrow the tabular style and layout from example 9 of Douglas Bowman's Sliding Doors of CSS, Part II and add a few more lines of JavaScript to assign the active section, show the first section by default and show our elements on alphabet. Please view example 3c and the corresponding JavaScript file.

I hope that I gave you some food for thought. If you are still hungry and like to read some more about unobtrusive techniques, I can recommend Christian Heilmann's unobtrusive JavaScript tutorial.