For a recent client project, I needed to make a collapsing/expanding tree navigation for a FAQ section. The site is being built on the CakePHP Framework which comes bundled with Prototype, so it was time for me to play around some more with “The JavaScript Framework that aims to ease development of dynamic web applications.”
The navigation’s functionality is fairly basic - all categories and questions should be hidden on page load, except for the first category and its related questions. When a category is clicked, that section will be shown on the page and the nested list will also be shown so that the user can skip to each question. The other sections and sub navs will be hidden.
The reason for this UI is because this particular FAQ houses more than 150 questions - showing them all at once would be a tad unwieldy, not to mention overwhelming.
So, the way I thought of tackling this task was to give the two elements that needed to be shown first the obvious class of first, and all the all the links that point to the answers were given the class more. Below is a sample page I made to illustrate the example, borrowing my content from the WikiPedia entry on a popular syndicated television show in the United States:
<ul id="nav">
<li>
<a href="#bundys" class="more">The Bundy Family</a>
<ul class="first">
<li>
<a href="#al_bundy">Al Bundy</a>
</li>
<li>
<a href="#peggy_bundy">Peggy Bundy</a>
</li>
<li>
<a href="#kelly_bundy">Kelly Bundy</a>
</li>
<li>
<a href="#bud_bundy">Bud Bundy</a>
</li>
</ul>
</li>
<li>
<a href="#neighbors" class="more">The neighbors</a>
<ul>
<li>
<a href="#marcy_darcy">Marcy Rhoades D&amp;#x27;Arcy</a>
</li>
<li>
<a href="#steve_rhoades">Steve Rhoades</a>
</li>
<li>
<a href="#jefferson_darcy">Jefferson D&amp;#x27;Arcy</a>
</li>
</ul>
</li>
<li>
<a href="#recurring" class="more">Recurring characters</a>
<ul>
<li><a href="#griff">Griff</a></li>
<li><a href="#bob_rooney">Bob Rooney</a></li>
<li><a href="#officer_dan">Officer Dan</a></li>
<li><a href="#ike">Ike</a></li>
</ul>
</li>
</ul>
<div id="bundys" class="content first">
<h2>
The Bundy Family
</h2>
<p>
The creators of the show named the "Bundy" family after their favorite wrestler King Kong Bundy, though some fans[who?] mistakenly believed that the name was derived from serial killer Ted Bundy. King Kong Bundy once appeared on the show as Peg&amp;#x27;s hick inbred uncle Irwin, and again appeared as his wrestling persona, since "NO MA&amp;#x27;AM" (National Organization of Men Against Amazonian Masterhood, a fictional club depicted on the show) were big fans of the wrestler.
</p>
<h3 id="al_bundy">
Al Bundy
</h3>
<p>
The head of the Bundy family, Al (Ed O&amp;#x27;Neill) is doomed to fail in all aspirations because of the &amp;#x27;Bundy curse.&amp;#x27; Once a promising fullback for fictional Polk High School (his proudest moment in life was scoring four touchdowns in a single game), he was on his way to college on a scholarship until he impregnated his girlfriend, married her, broke his leg, and ended up a shoe salesman at &amp;#x27;Gary&amp;#x27;s Shoes&amp;#x27; in the &amp;#x27;New Market Mall.&amp;#x27;
</p>
<h3 id="peggy_bundy">
Peggy Bundy
</h3>
<p>
Margaret "Peggy" Bundy (n&amp;eacute;e Wanker) (Katey Sagal) is Al&amp;#x27;s very lazy high school drop-out housewife. She refuses to cook or clean the house, and prefers looking for new clothes to washing them. She does not even think of having a job. During the day, she likes to watch all the daytime talk shows, sitting on the beloved family couch, and eating tons of bonbons (without getting fat).
</p>
<h3 id="kelly_bundy">
Kelly Bundy
</h3>
<p>
Kelly (Christina Applegate) is the older child in the Bundy family, born on approximately November 27, 1972 or 1973 or sometime before February 19, as noted in "Peggy Turns 300," where Kelly says her birthday is in February, but erroneously refers to herself as an "Aquarium" instead of an Aquarian (Aquarius). "Pumpkin," as Al often calls her, is a promiscuous bimbo and stereotypical "dumb blonde." She may have inherited her behavior from her mother, known as "The Big Easy" in high school. Peg has attempted to convey some of her other "values" to Kelly, most notably advice on how to avoid working.
</p>
<h3 id="bud_bundy">
Bud Bundy
</h3>
<p>
Bud Franklin Bundy, (David Faustino) is the second child, born on January 22 around 1974. In the first season, Bud is revealed to be in fifth grade, making him 10 or 11, but in subsequent seasons, he was aged to be within one year of Kelly, graduating high school in 1991.
</p>
<p>
He was named after Al&amp;#x27;s favorite beer, Budweiser. The first word Bud spoke was "hooters." He believes himself to be attractive, sexy, and smooth, but often is typically caught in sexually humiliating scenarios. He is also shorter in stature than his sister, and a lot shorter than his mother. He does not appear to know how to impress women upon meeting them, and is often rejected. It is unclear when Bud lost his virginity, as it was depicted that he may have bedded women as far back as age 14, but in the fourth season, it is mentioned that he is still a virgin.
</p>
</div>
<div id="neighbors" class="content">
<h2>
The Neighbors
</h2>
<h3>
Marcy Rhoades D&amp;#x27;Arcy
</h3>
<p>
Marcy D&amp;#x27;Arcy (Marcy Rhoades from Episodes 0101&amp;ndash;0512, played by Amanda Bearse) is Peggy&amp;#x27;s best friend, Al&amp;#x27;s nemesis, and the family&amp;#x27;s next-door neighbor. Though she considers herself to be better than the Bundy family, Marcy often sinks to their level. She originally worked as a loan officer at the city bank (in a higher position than her husband, Steve), and then as the manager of the Kyoto National Bank since the second season. But for a brief time, she was demoted to drive-up window teller as punishment for approving a loan Al could not re-pay (in fact the purpose was to make Al to be able to re-pay a previous loan approved by Steve, but Al instead turned this loan into his "shoe hotline" project as well, and lost it too). She wins back her old job after frugging on her boss&amp;#x27;s desk for 20 minutes, clad only in a slip, while the other drive-up window tellers tossed quarters at her.
</p>
<h3 id="steve_rhoades">
Steve Rhoades
</h3>
<p>
Steven "Steve" Bartholomew Rhoades (David Garrison) is Marcy&amp;#x27;s first husband. Much like the name "Bundy" the creators chose the surname "Rhoades" after professional wrestler Dusty Rhodes, a good-guy character who worked opposite bad guys like King Kong Bundy. He is a banker who seems unfazed by his lower position than Marcy at the city bank. (When Marcy moves up to a high position at another bank, he gets her former job.) Steve initially condescends to the Bundys, but eventually becomes more like them, and generally turns to Al for male-bonding. Marcy was initially attracted to him because of his self-centered materialism.
</p>
<h3 id="jefferson_darcy">
Jefferson D&amp;#x27;Arcy
</h3>
<p>
Jefferson Milhouse D&amp;#x27;Arcy (Ted McGinley) is Marcy&amp;#x27;s second husband (original age unknown, but younger than Steve Rhoades, but one episode mentioned that he celebrated his 40th birthday), a "pretty-boy" who marries her for her money. Self-centered and lazy, he is a male equivalent of Peggy. Marcy met Jefferson (a bartender) at his workplace after a bankers&amp;#x27; convention when she got drunk, and found herself married to him the next morning; she was horrified to find out that her name was now Marcy D&amp;#x27;Arcy. He is the closest friend of Al, and often angers Marcy when he is bonding with him; unlike Steve Rhoades, who was more of a foil, or straight man, to Al, Jefferson tends to be very encouraging and attuned to Al&amp;#x27;s behavior. Marcy constantly bosses Jefferson around to keep him in check. However, behind her back, Jefferson often insults Marcy, ignores her orders (and has implied numerous times that he cheats on her). When Marcy&amp;#x27;s favorite squirrel Zippy dies, Jefferson tells her that he will give it a proper burial, only to punt it out of his sight when Marcy turns around.
</p>
</div>
<div id="recurring" class="content">
<h2>
Recurring characters
</h2>
<h3 id="griff">
Griff
</h3>
<p>
Griff (Harold Sylvester) &amp;ndash; First appears early in Season 9, and is a friend and co-worker of Al at the shoe store. He is also a member of Al&amp;#x27;s "NO MA&amp;#x27;AM" organization. A divorcee, he shares many of Al&amp;#x27;s characteristics as far as work ethic and views on women go. However, Griff isn&amp;#x27;t quite as impolite and outspoken to their customers, or to their boss, Gary. He is also less callous; occasionally he feels uneasy when going along with one of Al or Jefferson&amp;#x27;s many schemes. Griff drives a GEO Metro, and is often mocked for this. However, Griff is happy because it is still more reliable than Al&amp;#x27;s 1970s Dodge. (Al says Griff&amp;#x27;s car is easier to push.) When Bud and Griff first met, Bud said Al never mentioned having a co-worker, and Griff said Al never mentioned having a son, a daughter, or a living wife, but had already annoyed him with all the times he mentioned scoring four touchdowns in one single game.
</p>
<h3 id="bob_rooney">
Bob Rooney
</h3>
<p>
Bob Rooney (E. E. Bell) &amp;ndash; One of Al&amp;#x27;s friends from the neighborhood, and treasurer of "NO MA&amp;#x27;AM." He works as a butcher, has a wife named Louise (who is a friend of Peggy), and played on the same football team as Al at Polk High. He is always called by both his first and last name, even by his wife, and it is spelled as one word on his bowling shirt. Bell was the only member of the extended cast to spend a lot of time on the Usenet newsgroups fielding questions from viewers.
</p>
<h3 id="officer_dan">
Officer Dan
</h3>
<p>
Officer Dan (Dan Tullis, Jr.) &amp;ndash; A friend of Al&amp;#x27;s who is also in "NO MA&amp;#x27;AM." Surprisingly, though he is part of "NO MA&amp;#x27;AM," he often arrests them for their illegal antics. However, he does admit to his friends that he is a corrupt officer, which indicates he does help out the group now and then. In one of the times he was about to arrest them, he changes his mind and joins them when he learns they&amp;#x27;re trying to bring back "Psycho Dad". Though he was usually a cop, in season 6 Officer Dan arrives at the Bundy front door as an FBI agent looking for Steve Rhodes.
</p>
<h3 id="ike">
Ike
</h3>
<p>
Ike (Tom McCleister) &amp;ndash; Another member of "NO MA&amp;#x27;AM." Sergeant of Arms of the organization. Believes Elvis is still alive. The character was named after producer Kim Weiskopf&amp;#x27;s best friends&amp;#x27;s son.
</p>
</div>
We have plenty of hooks to use, and we will of course have the mighty Prototype to help us walk the DOM tree. You can probably tell by looking at the the HTML source code how this going to work. Here’s the snippet of code that makes our page zing to life:
document.observe("dom:loaded", function() {
var els = $$('div.content, ul#nav li ul');
els.each(function(el) {
if(!el.hasClassName('first')) {
el.hide();
}
});
$$('a.more').each(function(link) {
var section_id = link.readAttribute('href').sub('#','');
link.observe('click', function(event) {
els.invoke('hide');
els.each(function(el) {
if (el.match('div')) {
el[el.identify() == section_id ? 'show' : 'hide']();
event.element().next('ul').show();
}
event.preventDefault();
});
});
});
});
The script is pretty simple. On page load (before the images are finishing loading) we loop through all the divs containing FAQ answers, which have the classcontent, and all the nested uls inside our main nav. We hide all the elements that don’t contain the class first.
Next, we loop through all the links with class more, read their href value and store that in the variable section_id. When one of the more links is clicked, we hide all the elements (including those with the class first) and iterate through them to see if the element is a div. If it is a div, we see if its ID matches that of the link href. If it does, we show that section, if not, we hide it.
Next, we need to show the corresponding sub list, otherwise the user won’t be able to navigate to the answers. Since we’re already tracking the event in the click function, we can use Prototype’s element function to find out on which element the event occurred. Since Prototype skips the white space for us, we know that the element clicked is an anchor tag. We then move to the next unordered list after the anchor tag and show it.
Finally, we need to stop the browsers default action of following the main category links. This may not be necessary depending on the set up - sometimes following that main link might help usability if there are no sub links.
That’s it! it’s a pretty simple, straight forward script, but there is of course room for improvement. For example, I think the code be both condensed and be made more efficient, but you could probably say the same for virtually any bit of JavaScript, unless John Resig wrote it.
Comments are of course open for suggestions, thoughts and improvements!
Here is the working sample, with JS included inline for the sake of brevity.