Skip to page content or skip to Accesskey List.
Search evolt.org
evolt.org login: or register

Work

Main Page Content

Collapsible page elements with DOM

Rated 4.09 (Ratings: 12) (Add your rating)

Log in to add a comment
(28 comments so far)

Want more?

 
Picture of codepo8

Chris Heilmann

Member info | Full bio

User since: July 29, 2002

Last login: April 27, 2006

Articles written: 17

Show me, hide me

Sometimes you want to enable surfers to hide and show elements of a document, whether it be to reveal extra information about a subject or to allow them to get rid of right hand navigation.

In older DHTML scripts this was done by adding an ID to the element to access its visibility property

&#60;script language=&#34;javascript&#34; type=&#34;text/javascript&#34;&#62;<br>
&#60;!--<br>
// Evil Netscape and IE 4.x function<br>
function show(id,trigger){<br>
if (trigger==1){<br>
if (document.layers) document.layers[id].visibility = &#34;show&#34;<br>
else if (document.all) document.all[id].style.visibility = &#34;visible&#34;<br>
}else{<br>
if (document.layers) document.layers[id].visibility = &#34;hide&#34;<br>
else if (document.all) document.all[id].style.visibility = &#34;hidden&#34;<br>
}<br>
}<br>
//--&#62;<br>
&#60;/script&#62;<br>
&#60;a href=&#34;javascript:show('myText',1)&#34;&#62;Show&#60;/a&#62;|<br>
&#60;a href=&#34;javascript:show('myText',0)&#34;&#62;Hide&#60;/a&#62;<br>
&#60;div id=&#34;myText&#34;&#62;Test&#60;/div&#62;<br>

These functions did not collapse or expand the DIV, but just hide it. Furthermore in Netscape they only worked on absolutely positioned elements. A different way of doing that for newer browsers is by accessing the display attribute of the div.

<script language="javascript" type="text/javascript">
<!--
// DOM function for >4 browsers
function show(id,trigger){
	if (trigger==1){document.getElementById(id).display="block";}
	else{document.getElementById(id).display="none";}
	}
}
//-->
</script>
<a href="javascript:show('myText',1)">Show</a>|
<a href="javascript:show('myText',0)">Hide</a>
<div id="myText">Test</div>

This way of showing and hiding actually deletes the DIV from the display, making content below it jump further up.

Now this enables us to show and hide, or in this case, collapse and expand a part of the page using its ID. However, this can be pretty tiring, as each element we add to the document needs a unique ID and a link to point to it. Furthermore we need a link for showing and one for hiding. It would be a lot handier to have a link with, for example, a "+" next to it that hints that it is an expandable element. Clicking it would turn the "+" into a "-" and show the collapsed element. Clicking the link again would hide the element and turn the "-" into a "+".

Climbing down the node tree

DOM allows us to do exactly that, if we use some straight forward HTML.

Let's say we want a menu with collapsible elements in it. The HTML is:

<style type="text/css">#menu div {display:none;}</style>
<div id="menu">
	<h5><a href="javascript:show(0)">+ Test1</a></h5>
	<div>Test 1</div>
	<h5><a href="javascript:show(1)">+ Test2</a></h5>
	<div>Test 2</div>
	<h5><a href="javascript:show(2)">+ Test3</a></h5>
	<div>Test3</div>
</div>

We use the H5 tags to be able to access the links later. Otherwise it gets a lot harder to access the "+" in the link text. We set the display of each DIV to none to collapse them.

To access the menu we use document.getElementById("menu"), to access the collapsible elements we use document.getElementById("menu").getElementsByTagName("div").

We can then count through the DIVs to access one of them, read its display attributes and reverse it.

<script language="javascript" type="text/javascript">
<!--
function show(which){
	m=document.getElementById("menu");
	trig=m.getElementsByTagName("div").item(which).style.display;
	if (trig=="block") trig="none";
	else if (trig=="" || trig=="none") trig="block";
	m.getElementsByTagName("div").item(which).style.display=trig;
}
//-->
</script>
<style type="text/css">#menu div {display:none;}</style>
<div id="menu">
	<h5><a href="javascript:show(0)">+ Test1</a></h5>
	<div>Test 1</div>
	<h5><a href="javascript:show(1)">+ Test2</a></h5>
	<div>Test 2</div>
	<h5><a href="javascript:show(2)">+ Test3</a></h5>
	<div>Test3</div>
</div>

Clicking the "Test 1" link (calling show(0)) now does the following:

  • First line: Access the element with the ID "menu"
  • Second line: Access the first DIV (computers start counting at zero) in the menu and read its display attribute.
  • Third and fourth line: If the attribute is "none", it gets set to "block" and vice versa.
  • Fifth line: Assign the changed attribute to the first DIV.

This now enables us to show and hide the elements when clicking on the links, however, it does not change the "+" to "-" yet.

To do this we need to go through the node tree and access the link's text value

<script language="javascript" type="text/javascript">
<!--
function show(which){
	m=document.getElementById("menu");
	trig=m.getElementsByTagName("div").item(which).style.display;
	if (trig=="block") trig="none";
	else if (trig=="" || trig=="none") trig="block";
	m.getElementsByTagName("div").item(trigger).style.display=trig;

	var highlighttext="-";
	varnormaltext="+";
	t=m.getElementsByTagName("h5").item(which);
	h=t.getElementsByTagName("a").item(0).firstChild;
	if (trig=="none"){h.nodeValue=h.nodeValue.replace(highlighttext,normaltext);}
	else {h.nodeValue=h.nodeValue.replace(normaltext,highlighttext);}
}
//-->
</script>
<style type="text/css">#menu div {display:none;}</style>
<div id="menu">
	<h5><a href="javascript:show(0)">+ Test1</a></h5>
	<div>Test 1</div>
	<h5><a href="javascript:show(1)">+ Test2</a></h5>
	<div>Test 2</div>
	<h5><a href="javascript:show(2)">+ Test3</a></h5>
	<div>Test3</div>
</div>

We define two variables called "highlighttext" and "normaltext". These are the "-" for the expanded state and the "+" for the collapsed state.

Then we access the link elements inside the H5 on the same level as the DIV we expanded or collapsed, t=m.getElementsByTagName(&#34;h5&#34;).item(which);.

Inside this H5 we access the first A (item 0) and get its first child element, which is the text value of the link, h=t.getElementsByTagName(&#34;a&#34;).item(0).firstChild;

We get the nodeValue of this child and, depending on which display is desired, we replace "+" (normaltext) with "-"(highlighttext) or vice versa.

Voila, we have accessed our collapsible page elements without using or adding IDs to them. For each DIV we add to the menu, we simply count up its number (Test 4 would be accessed via show(3) ).

However, this function only works in IE above 4 and Mozilla / Netscape above 6. Opera does know the getElementByID functionality but does not implement it properly. We need to think about something to make the content available to every browser.

Helping the less fortunate

The first thing we have to prevent happening is Netscape 4.x, Internet Explorer 4 and Opera trying to access the function and fail to make it work.

This can be done by wrapping it in a condition that checks for a functionality neither Netscape nor Opera have, but the others have.
if (document.getElementById && document.createTextNode){},
around the main function does the trick.

Then we need to ensure that the hidden DIVs are getting displayed for these browsers. We do that with the same condition, and by adding a new style rule overwriting the old one.

if (!document.getElementById && !document.createTextNode){<br>
document.write('&#60;style type="text/css"&#62;#menu div{display:block;}&#60;/style&#62;')<br>
}

The same hack should get applied when people do have the correct browsers, but have JavaScript disabled:

&#60;noscript&#62;&#60;style type="text/css"&#62;#menu div{display:block;}&#60;/style&#62;&#60;/noscript&#62;

And finally to make sure that all the horrid href="javascript:" links become working examples without javascript, we add a named anchor in each DIV, and link to that one.

&#60;h5&#62;&#60;a href="#test1" onclick="show(0)"&#62;+ Test1&#60;/a>&#60;/h5&#62;<br>
&#60;div&#62;&#60;a name="test1"&#62;&#60;/a&#62;Test 1&#60;/div&#62;

That's all. We now have collapsible elements that work in every browser that supports DOM, and the others can still access all the data.

The whole code

<script language="javascript" type="text/javascript">
<!--
function show(which){
	if (document.getElementById && document.createTextNode){
		m=document.getElementById("menu");
		trig=m.getElementsByTagName("div").item(which).style.display;
		if (trig=="block") trig="none";
		else if (trig=="" || trig=="none") trig="block";
		m.getElementsByTagName("div").item(which).style.display=trig;
		var	highlighttext="-";
		var	normaltext="+";
		t=m.getElementsByTagName("h5").item(which);
		h=t.getElementsByTagName("a").item(0).firstChild;
		if (trig=="none"){h.nodeValue=h.nodeValue.replace(highlighttext,normaltext);}
		else {h.nodeValue=h.nodeValue.replace(normaltext,highlighttext);}
	}
} 
//-->
</script>
<style type="text/css">#menu div {display:none;}</style>
<!-- Backward compatibility hacks -->
	<noscript>
		<style type="text/css">#menu div{display:block;}</style>
	</noscript>	
	<script language="JavaScript">
	<!--

	if (!document.getElementById && !document.createTextNode){
		document.write('<style type="text/css">#menu div{display:block;}</style>')
	}
	//-->
	</script>
<!-- Backward compatibility hacks end -->
<div id="menu">
	<h5><a href="#test1" onclick="show(0)">+ Test1</a></h5>
	<div><a name="test1"></a>Test 1</div>
	<h5><a href="#test2" onclick="show(1)">+ Test2</a></h5>
	<div><a name="test2"></a>Test 2</div>
	<h5><a href="#test3" onclick="show(2)">+ Test3</a></h5>
	<div><a name="test3"></a>Test3</div>
</div>

Of course you can access some other elements of this HTML to make the effect even more obvious. You can change the colour or background of the links according to their "highlight" or "normal" state.

To see and download a fully functional and a bit enhanced version of this effect to use, go to this page.

I have to thank Scott Benish, Craig Saila and Scott Andrew LePera of the webdesign-L mailing list for testing and enhancement tips.

Currently employed in London as a Lead Front End Developer, Chris has been bouncing around the globe working for several agencies and companies. A web enthusiast from 1997 on workplaces include Munich, London, Santa Monica and San Francisco. More of Chris' writings can be found at http://icant.co.uk and he blogs at http://wait-till-i.com

NOSCRIPT

Submitted by bmason on November 21, 2002 - 01:15.

Very neat, except that the noscript style is invalid HTML. Perhaps the display: block should be declared in the clear for all in the head, then a JavaScript onload (or the like) to hide things can follow afterward.

login or register to post comments

Noscript alternative

Submitted by kirkaracha on November 21, 2002 - 11:53.

Or you could have this in your style sheet: #menu div{display:block;}, then use JavaScript to write this #menu div{display:none;} in the HEAD. Also, h2 would probably be more appropriate than h5 as the header for the menu (unless it's a sub-sub-sub-sub-heeading, which seems unlikely). Assuming that you're using h5 for formatting, you could use this in the style sheet instead: #menu h2.

login or register to post comments

Noscript and H2

Submitted by codepo8 on November 22, 2002 - 06:44.

I'll add these ideas. I used H5 on purpose as it is unlikely to be used elsewhere, but your logic is right, we should use H2. About the noscript thing let me investigate further, I wonder if it is OK to have <style> in the body element anyways?

login or register to post comments

Updated

Submitted by codepo8 on November 22, 2002 - 08:00.

I implemented the changes on the demopage I simply add the hidden setting of the divs via document.write() now.

login or register to post comments

special character / html entities

Submitted by silvasonic on November 22, 2002 - 08:57.

everything was going great until i needed to add some html entities to some portuguese words that have accent marks. give this a try. add an entity like an 'a' acute (á) and click on the link more than once. the link gets destroyed and every time you click on it the string gets longer and longer. i'm not sure why though. any clues? maybe the ';' and '&' are messing it up? cool code though. good job.

login or register to post comments

Doesn't work on Opera

Submitted by jma on November 22, 2002 - 11:57.

The DOM collapse doesn't work on Opera 6.1. Opera reports errors on lines 48/75 (possibly from domCollapse.js) saying "Expression evaluated to null or undefined and is not convertible to Object."

login or register to post comments

entities and opera

Submitted by codepo8 on November 24, 2002 - 11:53.

silvasonic: it seems as if Javascript cannot handle entities, if you just use the special characters, it works fine, I tried it with umlauts.

jma: Does your Opera 6.1 display the content expanded? If so, then it's accessible and I am content. Opera 6.04 and 5 don't display any error here, Opera 7 even works. So upgrading your Opera does the trick, 6.1 was a rather dodgy version anyways and Opera was never dom compatible until version 7.

login or register to post comments

Here's a neat PHP alternative

Submitted by pepita on December 1, 2002 - 12:37.

I just found this too while looking for scripts to show/hide elements (it was the google result just below this page): http://www.scriptygoddess.com/archives/001449.php Check out the demos esp. on this site: http://jason.verberweb.com/demo.php?setcookie=30 (top of page) which is similar in effect to the demo posted above. Doesn't work with hovers and other effects like popup menus that Javascript does, but if using links or buttons to hide and show stuff, then it's the same magic result...

login or register to post comments

Opera support

Submitted by joek on December 6, 2002 - 20:17.

Using display over visibility is a great technique for showing and hiding elements, and I had recently used it on a client (real estate) site. But one problem with this method is support in Opera. Opera's specs list which CSS properties they support for DHTML, and display is not in the list. The good news is that it looks like Opera 7 will support it.

login or register to post comments

How to load elements only when headers clicked?

Submitted by georgepantela on February 2, 2003 - 00:39.

Hi all, not sure if anyone is still viewing comments on this script. I've implemented the script for my purpose and it works just great. However I was wondering if anyone has any ideas on loading whatever appears within each element only when the header is clicked. The reason for this is that I have a vbscript the generates a graph on the fly within each element and would rather do this only when the user clicks the relevant heading - the way the script is written now everything is written to the browser even though all links are collpased at the time of the page loading. Any help greatly appreciated. Regards George

login or register to post comments

How to fix the hovers?

Submitted by springload on February 7, 2003 - 15:27.

I've done something very similar to this, and have noticed that I lose my CSS hovers when I alter the element's color properties (I am coloring the header link when the sub menu/div is expanded). It seems as though setting style.color is overwriting the hover property of the link. Does anyone know if there a way to access the hover property to set the hover color back when the menu is collapsed again? I've thought of attaching event handlers there, but the handlers are different cross-browsers, and they can't take parameters (like, link and color). Thanks in advance...

login or register to post comments

Broken when you have to nest a div within div

Submitted by fireflight on January 7, 2004 - 08:52.

Okay, great script and works well. But I seem to have run across a case where the script dies.

I have a series of thumbnails using the Floating thumbnails technique which wraps each thumbnail in it's own div. When I wrap 10 thumbnails (each inside a div) with the empty div needed by domcollapse, the thumbnails no longer display.

Here's the css

div.float {
	float: left;
	width: 50px;
	padding: 10px;
	text-align: center;
	font-size: 70%;
  }

here's a sample bit of code.


- General Friends
  
     
[FRIEND NAME]
[FRIEND NAME]
[FRIEND NAME]

Does anyone have any suggestions?

login or register to post comments

Broken when you have to nest a div within div

Submitted by fireflight on January 7, 2004 - 08:52.

Okay, great script and works well. But I seem to have run across a case where the script dies.

I have a series of thumbnails using the Floating thumbnails technique which wraps each thumbnail in it's own div. When I wrap 10 thumbnails (each inside a div) with the empty div needed by domcollapse, the thumbnails no longer display.

Here's the css

div.float {
	float: left;
	width: 50px;
	padding: 10px;
	text-align: center;
	font-size: 70%;
  }

here's a sample bit of code.


- General Friends
  
     
[FRIEND NAME]
[FRIEND NAME]
[FRIEND NAME]

Does anyone have any suggestions?

login or register to post comments

Aw few craps sake

Submitted by fireflight on January 7, 2004 - 08:56.

Sorry about the double posts.

Here's the code( I hope this one works)

<div id="menu">
<h2><a href="javascript:domCollapse(0)">- General Friends</a></h2>
<div><a name="general" id="general"></a>
<div class="float"><img src="images/members/nopic_thm.gif"><br>[FRIEND NAME]</div>
<div class="float"><img src="images/members/nopic_thm.gif"><br>[FRIEND NAME]</div>
<div class="float"><img src="images/members/nopic_thm.gif"><br>[FRIEND NAME]</div>
</div>
< /div>

login or register to post comments

Well, fireflight

Submitted by codepo8 on January 7, 2004 - 09:51.

when dealing with DOM, make sure that your HTML is very clean and logical.

As we are using the DOM to first find our menu (via ID) and then loop through the DIVs, nesting them confuses the script, hence for your example you might be better off with a solution with IDs.

Also, there are linebreaks in between the divs, what for? The BRs are not closed, which might also be a problem.

Partly this article was meant to show you how to use DOM to collapse something, and that also means understanding it a bit. Your problem CAN be solved with DOM and looping through the divs, but it means the whole script has to change.

login or register to post comments

expand/collapse

Submitted by redux on April 17, 2004 - 03:13.

additionally, i'd have the expand/collapse links written out via javascript as well, as they don't make sense (and don't do anything) when js is disabled/unavailable...

login or register to post comments

display:none and google

Submitted by albee on June 23, 2004 - 00:51.

Hi, I'm planning to do a website using a lot of this collapsing techniques, but I've heard that google didn't like "display:none" used for "cloaking"... Do you think that the hack of writing the display:none css with javascript is a solution to give google all access, if google realy ignore js he will take the regular written css with display:block Hi know that I'm not on a SEO forum but, what do you mean?

login or register to post comments

albee

Submitted by codepo8 on June 23, 2004 - 00:58.

It is bad practise to hide things via CSS and then hide and show it. Better to hide them in a loop onload of the page. Google, or a text browser will see all the text, only javascript enabled browsers will show and hide it. If you also want to ensure that screen readers have no issue, you can use the off-left[1] hiding technique instead.

[1] http://css-discuss.incutio.com/?page=ScreenreaderVisibility

login or register to post comments

display:none and google

Submitted by albee on June 23, 2004 - 02:48.

Thank's for your answer! In fact my page layout is based on show/hide, my idea was to make a css with all div with display:block and after that to overwrite it via javascript (document.write) with a display:none, so if you have a non-javasctipt browser you mus see all (and google too I hope...) - it's what you explain in your article or am I wrong?. I've tried your tip with the onload event but with some browser it make a flash with all content visible before hidding, I don't want to have that...

login or register to post comments

albee

Submitted by codepo8 on June 23, 2004 - 02:53.

With the flash you'll have to live. This, or hiding your content from the user AND search engines. Do not use any document.write though, add the hiding and showing directly via Javascript. Check my newest article "a touch of class" on the frontpage for an example.

login or register to post comments

It doesn't work on Firefox

Submitted by deto on July 5, 2004 - 08:09.

This script doesn't work on Mozilla Firefox, IE mac and Safari Mac. I have an old version that works. Can someone correct it? Thank you, Lorenzo

login or register to post comments

deto

Submitted by codepo8 on July 5, 2004 - 08:33.

Have you checked the Date of the article? See Skinable Javascript for a more modern version of this script.

login or register to post comments

selectDiv - Improvement of this script

Submitted by deto on August 4, 2004 - 09:35.

I have tried to improve this script, but the new widget still has some bugs. Please look at it. Thanks. Lorenzo De Tomasi

login or register to post comments

one node uncollapsed when page loads

Submitted by larry on November 11, 2004 - 14:59.

hi there, nice script and useful. I was curious how you would revise the script to display 1 node and the rest collapsed when a page is first loaded? currently, when you view the sample page, all of them are collapsed. I'm thinking of implementing something like this in a "News Release" section where the current year displays all the headlines uncollapsed and previous years (ie. 2003, 2002, etc). are collapsed when the page is loaded. thank you.

login or register to post comments

Larry

Submitted by codepo8 on November 11, 2004 - 15:06.

Not much of a problem, simply add a class to the element that should not be collapsed and check for that class when looping through the ones to be hidden... I can give you an example if needed.

login or register to post comments

yes please =)

Submitted by larry on November 12, 2004 - 08:10.

ht there, yes plaase, can u provide a working and use your existing sample as a baseline? thanks,

login or register to post comments

This code here is too complex for that example

Submitted by codepo8 on November 12, 2004 - 08:36.

Check this example instead. It collapses all elements following a H2 unless the H2 already has a class called "open". The two different classes in the style allow you to change how the headlines look. The explanation of it is here,

login or register to post comments

thank you...

Submitted by larry on November 12, 2004 - 15:12.

now I understand it... thanks again.

login or register to post comments

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.orgEvolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.