Mon
12
May '08

Spry: A Completely Unobtrusive Accordion Example

I can’t tell you how much fun I have when challenged by the “standards” crowd to implement something with the Spry framework for AJAX. At the end of last week, I was privy to a conversation that Stephanie Sullivan was having with one of her fellow WaSP colleagues concerning Spry’s ability to be completely unobtrusive. In other words, to provide a completely standards-based, bare bones version of the page free of any extraneous semantic markup and/or JavaScript, and then to progressively enhance the page with AJAX functionality only at runtime for JavaScript-enabled user agents.

As you may have read here, I covered this technique in chapter 6 of Mastering CSS with Dreamweaver CS3 using not only the ability to externalize the JavaScript code which initializes the Spry widgets (a Spry Menubar and Sliding Panels widget), but also dynamically assigning the Spry attributes such as “spry:region” via the same external JavaScript. The issue was raised, however, that there was still some unnecessary code in the page – from a standards’ viewpoint. To understand this, let’s take a look at a simple accordion in Spry.

An accordion is simply an HTML structure which receives its presentation from CSS and its behavior from JavaScript. The CSS and Javascript for every Spry widget are part of the framework, and are linked to in the head of each page using the widget – Dreamweaver CS3 does this automatically when you insert the widget for the common widgets which can be inserted in Design View (but you’ll need to do this by hand for the new widgets in Spry 1.6):

<script src="SpryAssets/SpryAccordion.js" type="text/javascript"></script>
<link href="SpryAssets/SpryAccordion.css" rel="stylesheet" type="text/css" />

In this example, our accordion is defined as a series of nested div elements (but note that you can use any HTML structure, not just a div):

<div id="Accordion1" class="Accordion" tabindex="0">
<div class="AccordionPanel">
<div class="AccordionPanelTab">Label 1</div>
<div class="AccordionPanelContent">Content 1</div>
</div>
<div class="AccordionPanel">
<div class="AccordionPanelTab">Label 2</div>
<div class="AccordionPanelContent">Content 2</div>
</div>
</div>

The individual div elements within the accordion structure are identified with classes which in turn are manipulated by JavaScript to open and close them, as well as to indicate which “piece” they actually represent. When an accordion is inserted into a HTML page in Dreamweaver CS3, a block of JavaScript is added as the last element of the page, causing the accordion to be “created” after all of the contents of the page have been processed:

<script type="text/javascript">
<!--
var Accordion1 = new Spry.Widget.Accordion("Accordion1");
//-->
</script>

The first step needed in order to adhere to standards, is that we need to externalize this last little bit of code by moving it to (and subsequently linking to) an external JavaScript file. To do this, we can simply remove the code block from the page and copy the var Accordion1 = new Spry.Widget.Accordion("Accordion1"); line into a new JS file. By itself, this line of code wouldn’t do anything, because we need the user agent to understand that this is something that the Spry framework needs to handle. Accomplishing this feat is actually quite simple due to the fact that the Spry framework for AJAX includes a utility, appropriately named Spry.Utils, that provides us with builtin functions to manipulate the DOM. We’ll surround the line with the following code:

Spry.Utils.addLoadListener(function() {
var Accordion1 = new Spry.Widget.Accordion("Accordion1");
});

This instructs the framework to listen for the page to finish loading and then create the accordion – exactly as it was doing earlier when it was the last element before the closing body tag. Finally, we need to link to the Spry library (ie, JavaScript file) that contains the instructions for the utilities themselves. We’ll add that link to the head of the document (I’ve also included the link to our external JS file here):

<script src="SpryAssets/SpryDOMUtils.js" type="text/javascript"></script>
<script src="external.js" type="text/javascript"></script>

Okay, so far so good. But the conversation took this as only half of the desired support for standards. Even though we have removed the JS from the HTML page, it’s still “heavy” with unneeded markup in the form of the classes which you see assigned to the individual div elements, such as “AccordionPanel”, “AccordionPanelTab” and “AccordionPanelContent”. You see, if the page is being viewed by a non-JavaScript-enabled user agent, then there’s simply no point in having these classes applied to the page elements, since they only come into play with JavaScript enabled.

Now, one could argue that we could simply style these elements independantly of the need for JavaScript making use of the class identifiers – but I decided to take the criticism at face value. No JavaScript – then no additional markup. In other words, all of the “pieces” of the accordion should have their corresponding classes applied at runtime in JS-enabled user agents.

Once again, we’ll make use of the very same Spry.Utils library. Here’s how it works…

In the external JavaScript, we’ll ask the Spry framework to look at the page markup and locate the element identified as an accordion through the id of “Accordion1″. Once it has found this element, we can tell it to append a class of “Accordion”, which is simply a pre-built class in the SpryAccordion.css file that was linked to when we inserted the widget into the page. Bear in mind that we can completely customize this css file to our liking. Here’s the code that we add to the LoadListener:

Spry.$$("#Accordion1").setAttribute("class", "Accordion");

It should be fairly straightforward to see what this is doing. Now we can remove the “class=Accordion” attribute/value from the #Accordion1 element in the HTML, because this will now be added via the JavaScript. We also know that each panel of the accordion consists of three elements – an element defining the panel which then contains a tab element and a content element. Using the Element Selector Utility to help us find and define these elements is a matter of three lines of code – one for each element:

Spry.$$("#Accordion1>div").setAttribute("class", "AccordionPanel");
Spry.$$("div.AccordionPanel>div").setAttribute("class", "AccordionPanelTab");
Spry.$$("div.AccordionPanelTab+div").setAttribute("class", "AccordionPanelContent");

The first line uses a child selector to tell the framework to find our #Accordion1 element and locate divs within it which are its direct children and assign a class of AccordionPanel to them. In our example, there are two such elements. The second line then instructs the framework to locate divs within the document which (now) have been assigned the AccordionPanel class and locate the div elements which are children of them, assigning a class of AccordionPanelTab. Since there are two “div” children of each of the AccordionPanel elements, each of them initially receives this class. But no worries, we move on to the third line which instructs the framework to locate each AccordionPanelTab’s adjacent sibling (in other words, the element immediately after the AccordionPanelTab). This element is assigned the class of AccordionPanelContent, after which the framework moves on to the next AccordionPanelTab and repeats the procedure until all of the panel tabs are followed by a div.AccordionPanelContent element.

Note: We could also have used “.addClassName” instead of “.setAttribute” if we wanted to add the class to any class already defined on the elements, but in our example this was not the case.

For those of you who are really into your CSS, you might be concerned about the fact that IE, prior to version 7, does not support child/sibling relationships. Well, this is not a problem here because it’s the JavaScript that is using these attributes – more specifically, the Spry.Utils JavaScript, and it works perfectly fine… even in IE!

Viola! We can now remove all of the classes from these elements in the HTML page. The final markup looks like this:

<div id="Accordion1" tabindex="1">
<div>
<div>Label 1</div>
<div>Content 1</div>
</div>
<div>
<div>Label 2</div>
<div>Content 2</div>
</div>
</div>

And it’s all controlled by the external JavaScript, which looks like this:

Spry.Utils.addLoadListener(function() {
Spry.$$("#Accordion1").setAttribute("class", "Accordion");
Spry.$$("#Accordion1>div").setAttribute("class", "AccordionPanel");
Spry.$$("div.AccordionPanel>div").setAttribute("class", "AccordionPanelTab");
Spry.$$("div.AccordionPanelTab+div").setAttribute("class", "AccordionPanelContent");
Accordion1 = new Spry.Widget.Accordion("Accordion1");
});

Now we have an accordion widget progressively enhancing the page, but with absolutely no extraneous markup to weigh down the page if it is viewed by a non-JavaScript-enabled user agent. You can see the finished example here. I don’t know about you, but I think this is way cool. Cheers!

6 Comments »

6 Responses to “Spry: A Completely Unobtrusive Accordion Example”

  1. John Carroll Says:

    Thanks for this spry framework help. I am using the straight dreamweaver accordion to keep things simple for my CMS user community. I this the only accordion code I found that does no interfere with my CMS’s javascrript driven menu structure that exists on every page. Only problem is , of course, it renders funny in IE 6, but only from within the CMS. in other words, I can get the accordion to render fine in IE6 from a straight html file but from within the cms the accordion animation does not stop when it gets to the top–it keeps reopening itself. i tries using your first step of using the external file and the utils file,hoping there might be some solution here, but there was not. Any suggestions on how to correct for this?

  2. Spry turns two - The Web Standards Project Says:

    [...] own Greg Rowis has attempted to fill the gap by posting instructions on his personal blog for creating a more unobtrusive dynamic accordion list. Taking the lead from Greg, Adobe should devote some serious time and effort to showing how to use [...]

  3. Pankaj Sharma Says:

    I am also facing same problem in IE 6 where while hiding all the panels flicker. Any suggestions ASAP…

  4. Riester Says:

    thanks for your very interessting post.”Only problem is , of course, it renders funny in IE 6, but only from within the CMS. in other words, I can get the accordion to render fine in IE6 from a straight html file but from within the cms the accordion animation does not stop when it gets to the top–it keeps reopening itself”.
    I also think like John. keep on it.

  5. Jon Says:

    Did anyone ever find a solution to this IE flicker issue during animation? I’m using spry 1.6.1 with general dreamweaver accordion and everything works great in firefox and other browsers, but IE’s animation of the opening panels has a nasty flicker. It appears to open the full height of the longest panel then close and then open correctly. It does it so fast I cannot be certain yet.

    The one thing i’ve added is the { useFixedPanelHeights: false } which allows me to have the height of each panel adjust to the content within each. But this flicker or blinking was happening even before I added this.

    I’ve seen a lot of people asking about this issue, yet no one with any answers. Did everyone give up on this? Or did they find the answer and go on with their business?

  6. Ramon Hasbun Says:

    My accordion works fine in FireFox and Chrome, it flickers in IE, please help with the animation,

    thank you

Leave a Reply