This post was updated. See bottom of post for details.

I developed a set of JavaScript routines (W3C DOM standard) for hiding and revealing information on a page that you should be able to plug in to a wide range of content. Please feel free to use the code (though an acknowledgement would be nice.)

Files
JavaScript: expandcollapse.js

CSS: expandcollapsestyle.css
Original HTML: news.html
Resulting HTML: newsWithJS.html

The uncollapsed text. (Click to see larger version.)

We’ll illustrate how to apply this with an example. The picture shows what it looks like initially. (View the HTML.) We’ll collapse the additional news after the first item to just the headlines, but allow you to reveal the detail by clicking or tabbing and hitting return.

[I applied this to a hacked down version of someone else’s page, because I was short on time. This is good, in that it shows that it’s easy to apply this to existing pages. However, due to my hacking, the general markup of the page may look a little strange in parts. Please ignore that.]

Structuring the content

The markup of content you want to hide and reveal may be structured in a number of ways. This approach assumes that:

  1. you will click on a block element (which we will call the trigger) to cause some content below it to expand or contract
  2. the content revealed/hidden by clicking on the trigger can be in any number of block elements of any type. (You can also include other block elements above the trigger, if you like, though they won’t be hidden.)
  3. each trigger and its revealable content is bounded by a block element. (We will use a div, but it could be any block element.)
  4. all the expanding and collapsing content is surrounded by another element with an id. This allows you to work with expanding content in different areas on the same page separately. (Again we use a div, with the id otherNews, but the id could just as easily be on the body element, since we only have one area of affected content on this page.)

The diagram below shows the arrangement used in the example file. The trigger element is red. The content to be hidden/revealed is green. You don’t have to use an h3 as the trigger. You could even use an ordinary paragraph tag. If you do, however, you should use a class name on each trigger element, so that the trigger can be identified.

Note that the trigger element should not contain an <a> element, since the JavaScript will add an <a> element to create a clickable zone. (It doesn’t make sense, anyway.)

The structure of the content in the example.

Setting up the markup

Very little change is required to the markup.

What I did

Add this to the document head:
<script type="text/javascript" src="expandcollapse.js"></script>
<link rel="stylesheet" type="text/css" href="expandcollapsestyle.css"/>

Add this to the body element start tag:
onload="setCollapseExpand('otherNews', 'h3','');revealControl('On'); revealControl('Off');"

Add this next to the RSS icon, just above the expanding content:
<a id="On" name="On" onclick="openAll('otherNews', 'h3','');" href="#_" class="hideIfNoJS">Open All</a>
<a id="Off" name="Off" onclick="closeAll('otherNews', 'h3','');" href="#_" class="hideIfNoJS">Close All</a>

Notes:

  1. onload="setCollapseExpand('otherNews', 'h3','');"

    After the document has loaded, this collapses the content.

    The JavaScript will look through the div with id otherNews for all h3 elements. It then finds the parent of the h3 element, and adds a class name to all the remaining elements after the h3 within that parent (a div, in our case). The class is associated with styling that makes these elements disappear. It will also surround the contents of the h3 element with an a element. This allows keyboard users to access the functionality using tabs. Each a element is given an onclick function to enable it to toggle the hidden content on or off.

    If we had wanted to use an ordinary p tag with a class name of, say, trigger rather than the h3, the onload code would look like this:

    onload="setCollapseExpand('otherNews', 'p','trigger');"
  2. Optional. You may want to add some buttons to expand and collapse all text in one go. If so you’ll need to add these to the markup. In our example I added the following code alongside the RSS feed icon. I used an a element so that keyboard users can tab to it.

    <a id="On" name="On" onclick="openAll('otherNews', 'h3','');" 
       href="#_" class="hideIfNoJS">Open All</a>
    <a id="Off" name="Off" onclick="closeAll('otherNews', 'h3','');" 
       href="#_" class="hideIfNoJS">Close All</a>
    

    I added the class name hideIfNoJS to each a element. We can now use CSS to hide this text unless JavaScript is detected.

    We then need to add two more statements to the onload value on the body tag, one for each a element.

    revealControl('On'); revealControl('Off');

    After the document loads, the JavaScript will remove those class names, and the switches will become visible.

  3. <link rel="stylesheet" type="text/css" href="expandcollapsestyle.css"/>

    CSS will drive most of the behaviour. The JavaScript simply changes the class names associated with the markup. This references a stylesheet that will do all the hard lifting.

A walk through the CSS

Let’s take a look at the CSS in the expandcollapsestyle.css file.

First, we add some styling to the new ‘Open All’ and ‘Close All’ text we added. This will make this text look like small graphical buttons, and change the cursor to a pointing hand as we mouse over them.

   a#On, a#Off {
      padding: 0.1em 0.5em 0.1em 0.5em;
      margin: 0 0.5em 0 0;
      text-decoration: none;
      background: #005a9c;
      color: #fc6;
      font-weight: bold;
      cursor: pointer;
      }

Next, we add a rule to remove the ‘Open All’ and ‘Close All’ buttons from view initially. The revealControl calls in the body onload attribute will remove this class if JavaScript is enabled.

   .hideIfNoJS {
      display: none;
      }

Now, we style the trigger text (in our case the h3 elements).

The first set of rules makes the cursor become a pointer when we mouse over the text, and adds a graphic to show whether the content is revealed or not.

   .triggerOpen {
	background:url(http://www.w3.org/International/icons/open-thin.gif)
              no-repeat left 2px #fffaf0;
	}

   .triggerClosed {
	background:url(http://www.w3.org/International/icons/close-thin.gif)
              no-repeat left 2px #fffaf0;
	}

You can, of course, change the styling to suit yourself. For example, you may want to use a different graphic.

We also fix the colour of the trigger text, pads the left side of the text so that you can see the graphic, and make the trigger change colour as you mouse over it. (Note that the JavaScript has introduced this a element.)

.triggerOpen a, .triggerClosed a {
	padding-left:14px;
	color:#000;
	text-decoration: none;
	cursor: pointer;
	}

.triggerOpen a:hover, .triggerClosed a:hover {
	color:#00f;
	}

Finally, we add the styling for the content that will be hidden/revealed. The .hiddenContent class will be attached to content by the JavaScript to hide it.

   .hiddenContent {
      display: none;
      }

When that content is not hidden, it gets the revealedContent class. We added some styling to pad the left side of the blocks by the same amount as the trigger text.

   #otherNews .revealedContent {
      padding-left: 14px;
      }
   #otherNews ul.revealedContent {
      padding-left: 30px;
      margin-left: 0;
      }

The end result

The collapsed text. (Click to see larger version.)

This picture shows what you will see when you open the page in a user agent that has JavaScript turned on. (See the HTML.) If JavaScript is turned off, you will see exactly what you saw before.


Updates to this post

2007-07-01: Moved cursor:pointer from rules for .triggerOpen and .triggerClosed to the rules for ‘.triggerOpen a, .triggerClosed a’. Stops the pointer appearing to the right of the trigger text. Also added note about <a> in trigger.

2007-06-05: Small change to CSS to ensure that the expand/collapse works when clicking on the + or – icon too. (Moved the padding.)

2007-04-18: Largely rewrote the text to make it more readable, and to take into account changes made to the JavaScript and CSS files (which incorporate the ideas from several comments below).