JavaScript Tab Solution using Prototype

I make a best effort to write valid XHTML and use the W3C Validator throughout development. So, when I needed a tab solution, I knew exactly where to look. The W3C Validator consists of 3 forms with several form options. They bring these together very intricately on a single page with a custom tab solution. But I required something a bit more general. Furthermore, their solution used MooTools. Nothing against MooTools – or other JavaScript Frameworks for that matter – I just use Prototype. So, yet again, I wrote a port.

Tab Requirements

For me, a tab solution should meet the following requirements:

  • The XHTML is valid, semantic, and clean
  • JavaScript is added progressively, and therefore should degrade gracefully
  • Configurable options

Their Solution

As I said before, the W3C’s solution was a bit specific. The containing elements where <fieldsets> within a <form>. I converted these to a <div> wrapped within a <div>. It may border "divitis", but it affords greater flexibility.

Their solution did an excellent job of adding the JavaScript progressively. With JavaScript disabled, it degrades to stacking the tabbed content. The tabs still function as navigation. This comes together elegantly by a named anchor. Many of the solutions I reviewed had obtrusive JavaScript with onclick attributes or href="#". So credit to the W3C developers on this.

As a small note, their solution manipulated the hash (named anchor link) with JavaScript. Maybe a requirement for their needs, but it didn’t seem necessary. In addition, with JavaScript disabled it breaks page links containing the hash. Very small, but it can function without manipulating the hash.

Finally, there were not configuration options. At a minimum it should have the following options:

  • Multiple tab solutions on a page
  • Equalizing the height for the tab content for a consistent look
  • Changing the "toggle" event for the tabs
  • Rounding the content container

The Prototype Port

So here is an example of the tab solution or you can download the source. I will discuss the XHTML, CSS, and JavaScript below. However, I recommend looking thru the example first.

The XHTML

The markup requires three placeholders. A containing element with an id attribute referenced by JavaScript and class attribute of tabset. A toggle element with a class attribute of tabset_toggle. I use a <ul> for semantic reasons and style the <li> and anchor tags to make the tabs. You can wrap the anchor within other tags, such as a heading tag for SEO. Finally, a content element with a class attribute of tabset_content. Again, I use a <div> for flexibility. Tabs are paired with content in order, so the first tab links to the first content <div>. I could have paired them by id with the named anchor. However, that would require code which I felt unnecessary given the content should always follow in the same order as the tabs.

<div id="tabs1" class="tabset">
    <ul class="tabset_tabs">
        <li class="active"><a href="#tab-one">Tab One</a></li>
        <li><a href="#tab-two">Tab Two</a></li>
        <li><a href="#tab-three">Tab Three</a></li>
    </ul>
    <div class="tabset_content_container">
        <div id="tab-one" class="tabset_content">
            <h2>Tab One</h2>
            <p>Content goes here....</p>
        </div>
        <div id="tab-two" class="tabset_content">
            <h2>Tab Two</h2>
            <p>Content goes here....</p>
        </div>
        <div id="tab-three" class="tabset_content">
            <h2>Tab Three</h2>
            <p>Content goes here....</p>
        </div>
    </div>
</div>

The JavaScript

tabs.js contains a class extension using Prototype that allows you to create Tab objects. You can create a new Tab with the following code:

document.observe('dom:loaded', function() {
    new Tab({id: "tabs1", rounded: 1, height: 1});
});

This will setup the behavior and add any markup progressively based on the options provided. Currently, there are three: id, rounded, and height. id is required and must match the id attribute of the containing element with markup above. rounded is optional. If set to a true value, by JavaScript convention, it will create markup to give the content area rounded corners in the top right and bottom. Finally, height is optional. If set to a true value, it will set all tab content areas to the height of the tallest content area.

If multiple tabs exist on a page the explicit creation of a new Tab seemed redundant. Furthermore, if you use a CMS or work on a team, adding JavaScript may not be an option. The follow code leverages the existing tabset class and could be added to a JavaScript init script.

function prepareTabs() {
    $$('.tabset').each(function(e) {
        new Tab({id: e, rounded: 1, height: 1});
    });
}

As it is now, each tabset shares the same configuration. It would be difficult to adjust for individual tabs. One way around this would be to create more tabset classes to represent different configurations. However, with more than few options, the permutations add up.

The CSS

As with most of my UI projects, the CSS is the trickiest part. However, by shifting all design responsibility to CSS I can style this to fit any design. There are a few areas of note. The tabs images use the sliding door technique. The content areas fade in by setting the opacity and toggling between display: none and display: block. If configured with rounded corners, JavaScript will add <div>‘s to the top and bottom of the content area with classes of tr, bl, and br. There are a few special styles for IE. All of which are commented. Most deal with hasLayout. I also had to add a background to the elements I noticed a ghosting bold during the effect. I hope to remove these in time. Finally, be aware of box model browser inconsistencies when adjusting the height, width, padding of your tab elements. They caused the most headaches.

In Closing

Tab solutions are common and should be simple to use. They are a great way to group content and maximize page real-estate. This solution works very well in that respect. It provides several configurable options, fits any design, uses valid/semantic XHTML, and degrades gracefully.

Note: You could use these tabs for site navigation by splitting the content across other pages and transfer responsibility of some JavaScript code to a back-end language. Feel free to post a comment or contact me for more details.

JavaScript Accordion using Prototype

In my search for an accordion solution I came across two of mention. The first was from stickmanlabs. It had good functionality and the ability to create a horizontal accordion. But support for IE6 was lacking (although damn IE6) and and it used an older version of Prototype. I kept looking and came across the second, by Brian Crescimanno. This was a little smoother, with leaner markup. The code was cleaner and appeared very similar to stickmanlabs. So, I felt like I was on the right track. After reviewing the demo and reading through the comments, it needed more. So, in typical developer fashion, I made my own.

The Merge

In this case, I felt there were pieces of both solutions that were good. I decided to merge the two, and use Brian Crescimanno’s as a base. If you want more detail on the individual solutions, I suggest reviewing the articles above. As such, I have provided an outline of the changes:

  • Updated stickman’s horizontal functionality to Prototype 1.6.
  • Made more prototype-ish. Code wasn’t taking full advantage of Prototype.
  • Refactored methods by grouping similar actions (e.g. toggle/clickHandler)
  • Removed modification of display styles, used height/width consistently.
  • Converted initialize parameter into an options hash.
  • Ability to change the "toggle" event.
  • Ability for multiple nodes to be expanded in a vertical accordion. Neither solution had this, and in my opinion it mimics a true accordion.
  • Better degradation. Modified "toggle" elements to be anchor tags. Integrated CSS styles to allow proper display if JavaScript is disabled.

The Solution

So without farther ado, here is an example of the merged accordion solution or you can download the source.

The markup requires three placeholders. A containing element with an id attribute you reference with JavaScript. A toggle element with a class attribute of accordion-toggle. I use an anchor tag for semantic reasons, but it can be anything. A content element with a class attribute of accordion-content. There is a one to one relationship between toggle and content elements.

<div id="test-accordion">
    <a href="#" class="accordion-toggle">Main</a>
    <div class="accordion-content">
        <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
        <p>Mauris dictum congue lectus.</p>
    </div>

Currently the configuration options are the accordion type and event. Type can be horizontal, vertical (default), or vertical-multiple. Event can be any event supported by Prototype (e.g. mouseover, click). In addition, you can change the class names within the initialize method. However, keep in mind these are shared for all your accordions. The following code from the example creates a vertical and horizontal accordion on page load:

document.observe("dom:loaded", function() {
    accordion = new Accordion({id: "test-accordion"});
    accordion2 = new Accordion({id: "test2-accordion", type: 'horizontal'});
});

The CSS is the trickest part. In modifying the CSS, I found that most bugs were related to the styles. When styling your accordion remember the box model. Padding, margin, and borders all affect the effect. Which makes sense considering this solution modifies height/width. If you start noticing jumpiness in the effect, check these properties.

In Closing

An accordion solution is relatively progressive. Although I feel this solution degrades better than others, it is not fully functional without JavaScript enabled. To resolve this, you will need some back-end support. By modifying the toggle elements to link to the current page with a URL parameter. On page load the back-end could parse this URL parameter to identify which node to expand and add the necessary CSS class (active).

A Disclaiming Note

Understand the web is a continually evolving environment. The code within this article is offered with an as-is warranty. My goal is that the article may help more than the code. Nonetheless, I always welcome your feedback, good and bad. Just know, that I know, this is not the solution and therefore may not work for you… Although in an ideal development world it would.

The Fallout of Babel

From the Bible, the Tower of Babel, was intended to "reach the heavens". The result of an achievement of a united humanity. God, seeing what the people were doing, gave each person a different language to confuse them and scattered the people throughout the earth. From dictionary.com, babel is "a scene of noise and confusion". By analogy, this is what the world came to be a scattered, diversified place. The Fallout of Babel now radiates from everything and keeps humanity from achieving greatness.

A United Front

As a developer, I know over a dozen languages – C, Java, Perl, PHP, Ruby, ColdFusion… I’ve wondered why there are so many. Understandably technology evolves like anything else, and languages must be updated or replaced. But are each necessary?

Let’s look at it from the another angle. Similar to the original storyline, consider the capabilities of a united development community. Think of development using a single language, independent of hardware or medium. Platform and compatibility become non-issues. It’s like an open-source dream. How quickly could your projects be completed if they used a singular technology. How much could be shared? How quickly could we learn?

Is it just a dream?

Of course this reaches much farther than just the development community. Imagine the world at large using a unified language. (there are even variations of sign language.. psht!)

One could argue that such a world would lack competition. Competition that drives us to excel. Again, this is just conjecture. Be that as it may, I would argue in return that we may excel at a greater pace united.

I don’t know how we could get there, even just at a development community. Yet, I believe that united effort has great potential. I can’t explain why we haven’t achieved some variation of the. Maybe we just all seek to stand out. However, is this an inherit human quality or a pressure of society.