O'Reilly logo

JavaScript Cookbook by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

14.5. Creating Collapsible Form Sections

Problem

You want to encapsulate form elements into collapsible sections, and expand when a label is clicked.

Solution

Use an accordion widget in combination with the aria-hidden and aria-expanded states, the tablist, tab, and tabpanel roles, and the aria-labeledby relationship indicator.

The entire set of accordion label/panel pairs is surrounded by one element, given a role of tablist and an attribute of aria-multiselect set to true to indicate that the element is a container for an accordion or multiselectable tablist. The text for the label is enclosed in a link to make it keyboard-accessible. Since these are groupings of form elements, the label/elements pair are surrounded by a fieldset, and the label is a legend element. If this application were a menu, these elements would most likely be div elements. The processing would, however, remain the same:

<form>
<div role="tablist" aria-multiselectable="true">
<fieldset>
<legend class="label" aria-controls="panel1" role="tab"
aria-expanded="true" id="label_1" >
<a href="">Checkboxes</a>
</legend>
<div  class="elements" id="panel_1" role="tabpanel"
aria-labeledby="label_1">
<input type="checkbox" name="box1" id="box1" value="one" />
<label for="box1">One</label><br />
<input type="checkbox" name="box2" id="box2" value="two" />
<label for="box2">Two</label><br />
<input type="checkbox" name="box3" id="box3" value="three" />
<label for="box3">Three</label><br />
<input type="checkbox" name="box4" id="box4" value="four" />
<label for="box4">Four</label><br />
<input type="checkbox" name="box5" id="box5" value="five" />
<label for="box5">Five</label><br />
</div>
</fieldset>
<fieldset>
<legend class="label" aria-controls="panel2" role="tab"
aria-expanded="true" id="label_2" >
<a href="">Buttons</a></legend>
<div class="elements" id="panel_2" role="tabpanel"
aria_labeledby="label_2">
<input type="radio" name="button1" id="b1" value="b one" />
<label>button one</label><br />
<input type="radio" name="button1" id="b2" value="b two" />
<label>button two</label><br />
<input type="radio" name="button1" id="b3" value="b three" />
<label>button three</label><br />
<input type="radio" name="button1" id="b4" value="b four" />
<label>button four</label><br />
<input type="radio" name="button1" id="b5" value="b five" />
<label>button five</label><br /><br />
<input type="submit" value="submit" />
</div>
</fieldset>
</div>
</form>

For each accordion label/panel pair, the label is displayed when the page is loaded, while the contents of the accordion panel are hidden. The label is given a role of tab, and the panel given a role of tabpanel. The aria-expanded attribute in the label is also set to false, to indicate to the AT devices that the panel is collapsed, and the aria-hidden is set to true on the panel, to indicate that the contents are not displayed:

// process tab panel elements
var elements = document.querySelectorAll(".elements");
for (var i = 0; i < elements.length; i++) {
    elements[i].style.display="none";
    elements[i].setAttribute("aria-hidden","true");
}

// process tab elements
var labels = document.querySelectorAll(".label");
for (var j = 0; j < labels.length; j++) {
    labels[j].onclick=switchDisplay;
    labels[j].style.display="block";
    labels[j].setAttribute("aria-expanded","false");
}

When the label is clicked, if the aria-expanded attribute is set to false, the panel is displayed, its aria-hidden attribute is set to false, and the label’s aria-expanded attribute is set to true. The opposite occurs if the aria-expanded attribute is set to true:

// when tab is clicked or enter key clicked
function switchDisplay() {

  var parent = this.parentNode;
  var targetid = "panel_" + this.id.split("_")[1];
  var target = document.getElementById(targetid);

  if (this.getAttribute("aria-expanded") == "true") {
    this.setAttribute("aria-expanded","false");
    target.style.display="none";
    target.setAttribute("aria-hidden","true");
  } else {
    this.setAttribute("aria-expanded","true");
    target.style.display="block";
    target.setAttribute("aria-hidden","false");
  }
  return false;
}
</script>

Discussion

The solution is a modification of the accordion solution in Recipe 13.5. The major structural differences are that one container element is used to wrap all of the accordion label/panel pairs, to indicate a grouping, each tab/panel pair is surrounded by a fieldset element, and the div element that acted as a label was replaced by a legend element.

One aspect of working with ARIA that I wasn’t expecting is that it led me to reexamine how I create my widget-like applications, such as an accordion. In Recipe 13.4, I didn’t group the individual accordion pairs into one cohesive whole because I didn’t need to for the solution I created, and I wanted to keep my element use to a minimum.

The ARIA attributes reminded me that there are semantics to the structure of an accordion—semantics I wasn’t capturing. For instance, I was working with a form but wasn’t using form-specific elements. In the new version, instead of using div elements for all aspects of the form element accordion, I used actual form elements: fieldset and legend.

Another structural change is that a link is used to wrap the text in the label so that the element can receive keyboard focus. Hitting the Enter key in the label triggers a click event, since a link is a focusable element. Since I’m not capturing the click event in the link, it bubbles up to the parent element—the label.

I also added an additional visual element—a background image with an arrow, to indicate the direction the panel will open, as shown in Figure 14-3.

An accessible accordion

Figure 14-3. An accessible accordion

The reason for the addition of the arrows is to ensure clarity of purpose for the grouping of elements. Those users unfamiliar with accordion applications, or who may have cognitive disabilities, can see there is an action associated with the label. I’m not sure about the type of arrow I used—a better approach might be a simple triangle.

The other change to the example was the addition of ARIA roles and states. At first, the accessibility additions may seem to be rather numerous, but each provides a specific piece of information necessary to understand what’s happening with the accordion widget. Consider how you would describe an accordion if you were describing it over the phone to a friend: “The accordion is a group of label elements, each of which has text. Each label also has an arrow, and I know from prior experience with similarly structured web page objects that clicking each label opens a panel underneath. When I do click each label, a section of previously hidden web page is displayed directly beneath the label, and I’m able to see its contents. When I click other labels, their associated panels also display. However, when I click the label for an already open label, the panel it is associated with is removed from view.”

In ARIA, these visual impressions are conveyed by the use of the tablist role on the outer container, and the role of tab on the label. In addition, the use of aria-multiselect also indicates that more than one panel can be expanded at the same time.

The fact that the panels are collapsed is indicated by the aria-expanded attribute, and that the panels aren’t displayed is indicated by the aria-hidden on each. When you open the application with a screen reader, such as NVDA in Firefox, and close your eyes, what I described is more or less what the device states.

The application works with all the book’s target browsers. However, the use of querySelectorAll doesn’t work with IE7. An alternative would be to access elements by tag name, and then check each element’s class to see how to handle it. You’ll also want to avoid using CSS attribute selector syntax with IE7.

See Also

See Recipe 13.4 for another implementation of the accordion. For an excellent demonstration of the use of the ARIA attributes with an accordion, see the accordion example at the Illinois Center for Information Technology and Web Accessibility.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required