Sneaky Abstractions

Subscribe to my Feed, follow me on , recommend me on Working With Rails or see my code on GitHub

Unobtrusive Ajax patterns: tabs

Posted on July 03, 2008 10:32 Tagged with tabs, javascript, ajax patterns.

(Ok, there’s not one bit of Ajax in this, but I’m just trying to keep it consistent :)

I guess you know what tabs are: There are two main components, the tabs themselves and the panes they control. To “tabify” an HTML element, we need to know which element is the container (like a form) and which elements are the tab panes and the titles to be used in the tabs. Let’s use a form as an example container:

<form class="tabs">
  <fieldset class="tab">
    <legend class="title">Tab 1</legend>
    <p><label>Koda</label><input type="text" /></p>
    <p><label>Basanda</label><input type="text" /></p>
  </fieldset>
  <fieldset class="tab">
    <legend class="title">Tab 2</legend>
    <p><label>Bosoya</label><input type="text" /></p>
    <p><label>Tikki</label><input type="text" /></p>
  </fieldset>
  <p class="button">
    <input type="submit" value="Ok" />
  </p>
</form>

Here you can see that the fieldsets are marked as tabs. Actually they’re tab panes, we’ll generate the tabs from the title elements (legend).

document.observe('dom:loaded', function(){

  //Find all elements with the "tabs" class (the containers)
  $$('.tabs').each(function(container){
    var tabs = container.select('.tab');//Panes
    //Create a list to contain the tab elements
    var tabList = new Element('ul', {'class':'tablist'});
    //Go through each tab pane
    tabs.each(function(tab){
      var li = new Element('li');//A tab
      li.update(tab.down('.title').innerHTML);
      //Observe the click event, this changes the active tab
      li.observe('click', function(){
        //First, inactivate all tabs
        tabList.select('li').invoke('removeClassName', 'active');
        //Then, activate this tab
        li.addClassName('active');
        //Inactivate all tab panes
        tabs.invoke('removeClassName', 'active');
        //Activate the associated tab pane
        tab.addClassName('active');
      });
      tabList.insert({bottom:li});//Add the tab to the list
    });
    container.addClassName('enhanced');//Make it targetable with CSS
    container.insert({top:tabList});//Add the tablist to the top of the container
    //Activate first tab
    tabs.first().addClassName('active');
    tabList.down('li').addClassName('active');
  });

});

Now all you need to do is add some CSS:

.tabs .tablist {
  list-style-type: none;
  margin: 0 !important;
  overflow: hidden;
}

  .tabs .tablist li {
    float: left;
    background: #ccc;
    border: 1px outset #ccc;
    border-bottom: 1px solid #bbb;
    color: #333;
    padding: 0.25em;
    margin-right: 2px;
  }

    .tabs .tablist li:hover {
      cursor: pointer;
      text-decoration: underline;
    }

    .tabs .tablist li.active {
      background-color: #ddd;
      border-color: #ddd;
    }

.tabs.enhanced .tab {
  display: none;
  background: #ddd;
  border: 1px outset #ddd;
  border-top: none;
  padding: 1em;
}

  .tabs.enhanced .title {
    display: none;
  }

  .tabs.enhanced .tab.active {
    display: block;
  }

And then you have tabs:

Tab 1

Tab 2

Tab 3

Tab 4

Of course this can be organised and abstracted more to make it seem a bit more object-oriented, but that’s the basics of it.

Ice Ice Baby