Re-div your page with DOM scripting
Many developers who have experimented with DOM-based Javascript will be familiar with the show/hide trick, featured in several web standards books including Zeldman’s Developing with Web Standards.
It involves creating a Javascript function which switches the value of an element’s CSS display property from block to none and vice versa, allowing you to show or hide sections of the page.
However, it has some inherent problems; it requires you to know which parts of the page you are going to want to show and hide and mark them up accordingly, and (if the default state of the element is to be hidden) it is not accessible if Javascript is disabled or not available.
In this article we are going to re-arrange the elements of the page, creating extra DIVs where necessary, using DOM-based Javascript.
An example project
For our example we’ll choose a format that is familiar to most of us – a blog. Let’s imagine that we are in the habit of writing lengthy articles, which attract multiple comments; to make browsing the site easier for the casual visitor we would like to only display the first few paragraphs of each article, and use the show/hide Javascript to reveal the remainder of the content, and the comments, if the visitor wishes to see them.
However, we don’t want to have to insert extra mark-up to do this – our base article mark-up will look like this:
<div id="article"> <p>Some article text.</p> </div> <div id="comments"> <p>A series of comments.</p> </div>
Notice that we have not included the actual show/hide links in the content – this is so that if Javascript is unavailable there will not be pointless, broken links present.
Accessibility concerns
As mentioned above, using “display: none” as the default styling of an element presents problems if Javascript is disabled, and many screenreaders also ignore content that is marked-up that way.
Our original CSS will not make any reference to the display property of the “article” or “comments” DIVs, so if Javascript is disabled or unavailable for any reason, the article and comments will be displayed in full, with no loss of site functionality.
Hiding the comments
We’re going to approach this in reverse order of complexity, and handle the hiding of the comments first. What we want to do is hide the contents of the DIV with the id of “comments”, and replace it on the page with a link that when clicked reveals the hidden comments.
The first step is the easy bit – hiding the existing comments:
if (!document.getElementById) return;
var el = document.getElementById("comments");
el.style.display = "none";
Here we are checking that the browser understands what we will be doing (if the getElementById object does not exist, stop running the function and return), grabbing the “comments” DIV into a variable ‘el’, and setting its display property to “none”.
Fairly simple stuff so far, right?
Next we need to create a new element – a link – and tell it to reveal the hidden comments when clicked:
var rl = document.createElement("a");
rl.appendChild(document.createTextNode("Show comments"));
rl.setAttribute("href","#comments");
rl.onclick = function() {
document.getElementById("comments").style.display = "block";
document.getElementById("showcomments").style.display = "none";
}
Using the createElement method, we create a new A tag and assign it to the variable ‘rl’ (stands for replacement link – you may want to be a little more verbose in your variable naming to make it easier to remember what each is for when you come back to your code at a later date). A new text node is created (document.createTextNode) with the value “Show comments”, and appended as a child element of our new link.
To make sure that it behaves like a proper link we must also give it an ‘href’ attribute. As it is revealing previously hidden content it might be nice if clicking the link takes us to where the new content has appeared, so we give the ‘href’ a value of ”#comments” to link to the newly-revealed “comments” DIV (I believe this also helps screenreader users, although I am not 100% sure – advice welcome).
Finally, the function to reveal the comments is created and attached to the onclick event handler (there are more advanced ways to attach events using the DOM methods addEventListener and attachEvent, but for our purposes the old-skool way will do just fine). When the click event fires, two rules are processed – one to reveal the comments DIV by setting its display property to “block”, and one to hide the “showcomments” element. What “showcomments” element? More on that in a minute.
To ensure that the new link looks the same as the original content text, I’m going to wrap it in a P tag – that way it will pick up the correct font size, etc. that the designer intended:
var rp = document.createElement("p");
rp.setAttribute("id","showcomments");
rp.appendChild(rl);
That is our new “showcomments” element, which is hidden when the link is clicked.
The last step is to insert our new node into the document – we’re going to use the original reference to the “comments” DIV to insert it just before the start of the comments:
el.parentNode.insertBefore(rp,el);
This line uses two DOM properties/methods – el.parentNode references the direct parent of ‘el’ in the document tree; and insertBefore tells the browser that we want to insert the element held in the variable ‘rp’ as a child of the parent node just before the element referenced by the ‘el’ variable. Hope that makes sense!
Our final function looks like this:
function hideComments() {
if (!document.getElementById) return;
var el = document.getElementById("comments");
el.style.display = "none";
var rl = document.createElement("a");
rl.appendChild(document.createTextNode("Show comments"));
rl.setAttribute("href","#comments");
rl.onclick = function() {
document.getElementById("comments").style.display = "block";
document.getElementById("showcomments").style.display = "none";
}
var rp = document.createElement("p");
rp.setAttribute("id","showcomments");
rp.appendChild(rl);
el.parentNode.insertBefore(rp,el);
}
Hiding (some of) the content – enter the re-div
The functionality we have used to hide the comments is obviously not particularly useful for the main body of content – our users need to know whether they want to read any more, so they need to be able to see something in the first place! – so what we want to do is hide everything apart from the first three paragraphs.
But we have no way of directly referencing all but the first three paragraphs – they’re not in their own DIV that we can easily change the display property of! We could loop through all the child elements of the “article” DIV and set each one after the first three to “display: none”, but to give us more control over the hidden portion of the article we are going to ‘re-div’ it; all but the first three paragraphs will be chopped off the bottom of the “article” DIV and put in a new DIV called “remainder”, which will be hidden and added back into the “article” DIV. This illustration shows what we are hoping to achieve:
Our function starts off in much the same way as the hideComments() one, by checking for browser support and grabbing the relevant DIV into a variable. Next, we loop through all the child nodes of the “article” DIV and copy them into a new array called ‘elements’:
if (!document.getElementById) return;
var el = document.getElementById("article");
var children = el.childNodes;
var elements = new Array();
for (var i=0,j=0;i<children.length;i++) {
if (children[i].nodeType = = 1) {
elements[j] = children[i];
j++;
}
}
The second loop is checking the nodeType of each childNode to make sure it is a real node and not just empty space; actual DOM node objects have a nodeType of 1. At the end of this section, we now have an array called ‘elements’ which contains all the paragraphs, lists, images and any other content inside the original “article” DIV.
The first thing we can do now is see if we need to bother re-diving anything:
if (elements.length <= 3) return;
Obviously if there are three or less elements in the article, we can give up now! If there are more than three, however, the next step is to create our new DIV, give it a name, and hide it:
var nd = document.createElement("div");
nd.setAttribute("id","remainder");
nd.style.display = "none";
‘nd’ stands for new div, in case you hadn’t worked out the naming convention yet.
Now we need to loop through the elements we picked up earlier, and move all but the first three into our new DIV:
for (var k=3;k<elements.length;k++) {
nd.appendChild(elements[k]);
}
The appendChild method moves the referenced element from its original location (in the ‘elements’ array, which is itself a reference to the element’s position in the “articles” DIV) to the new DIV ‘nd’.
The rest of the function is the same as the earlier hideComments() one – we create a new link, put it inside a paragraph, and add it to the bottom of the “articles” DIV, along with the new “remainder” DIV (which is now full of content):
var rl = document.createElement("a");
rl.appendChild(document.createTextNode("Show full article"));
rl.setAttribute("href","#remainder");
rl.onclick = function() {
document.getElementById("remainder").style.display = "block";
document.getElementById("showarticle").style.display = "none";
}
var rp = document.createElement("p");
rp.setAttribute("id","showarticle");
rp.appendChild(rl);
el.appendChild(rp);
el.appendChild(nd);
And here’s the final complete function:
function hideContent() {
if (!document.getElementById) return;
var el = document.getElementById("article");
var children = el.childNodes;
var elements = new Array();
for (var i=0,j=0;i<children.length;i++) {
if (children[i].nodeType = = 1) {
elements[j] = children[i];
j++;
}
}
if (elements.length <= 3) return;
var nd = document.createElement("div");
nd.setAttribute("id","remainder");
nd.style.display = "none";
for (var k=3;k<elements.length;k++) {
nd.appendChild(elements[k]);
}
var rl = document.createElement("a");
rl.appendChild(document.createTextNode("Show full article"));
rl.setAttribute("href","#remainder");
rl.onclick = function() {
document.getElementById("remainder").style.display = "block";
document.getElementById("showarticle").style.display = "none";
}
var rp = document.createElement("p");
rp.setAttribute("id","showarticle");
rp.appendChild(rl);
el.appendChild(rp);
el.appendChild(nd);
}
Last steps
All we have to do now is make sure our new functions are run when the page is loaded:
window.onload = function() {
hideContent();
hideComments();
}
Here is our final example page – both functions are available in the source code.
Improvements
You may not want to hide the ‘show’ links when the content is revealed, as you might want to allow users to ‘hide’ the content as well as reveal it; this can easily be accomplished by adding a simple if-then-else statement to the functions that we attached to the new links, and some new code to change the text of the link(s).
This sort of functionality does not have to be confined to the blog format; in fact it can be of use in any situation where you are dealing with content of an unpredictable length, or where you are not able to mark-up the content as you would wish (a common scenario when dealing with CMS-generated content).
The functions discussed here have been tested in IE6 and Firefox on Windows – please contact me or add a comment if you find any issues on other browsers/platforms and I will update the code as necessary.