SUBSCRIBE VIA RSS


Subscribe to our feed

Symfony Experts

Symfony Experts
If you have an urgent question for a symfony-related issue, this is the place to ask.

Topics

Stack Overflow


The old fashioned way

RECENT TUNES

CodeStereotabs: Tabs with Prototype and Scriptaculous

Features

Stereotabs is a small and lightweight script that allows you to easily add interactive tabs to your web pages. Very much inspired by Kevin Miller's Accordion script, this bit of code makes creating an smooth tabbed interface easy.

  • Unobtrusive » All behaviors are added after page load and don't muck up your html. Visitors without javascript will see all of your content.
  • Simple » You can add it to your existing pages quickly with just a few lines.
  • Small » Uncompressed the source is 82 lines long. 2k!
  • It Remembers » Your tabs are actually links to page anchors. Reload the page and the same tab will remain active.

Setup

Download the code.
Stereotabs requires prototype.js and effects.js (part of the scriptaculous library). If you don't want the fade-in / fade-out effect, you can do without effects.js

Include the source files.


<script type="text/javascript" src="js/prototype.js"></script>
<script type="text/javascript" src="js/effects.js"></script>
<script type="text/javascript" src="js/stereotabs.js"></script>
    

Initialize the tabs.


<script type="text/javascript">
//<![CDATA[
Event.observe(window, 'load', loadTabs, false);
function loadTabs() {
var tabs = new tabset('container'); // name of div to crawl for tabs and panels
tabs.autoActivate($('tab_first')); // name of tab to auto-select if none exists in the url
}
//]]>
</script>
    

Create your tabs.
Stereotabs will crawl the DOM element you specify for elements that contain the "tab" class and "panel" class (you can specify class names if you wish). It then uses these elements' ID tags to know which element to display when each tab is clicked. Here is a simple example. Notice each tab has the class "tab" and each tab content has the class name "panel". Also not that the IDs of the tabs match up with the IDs of the tab content.


<div id="container">
<ul id="tabnav">
  <li><a href="#first" id="tab_first" class="tab">Tab One</a></li>
  <li><a href="#second" id="tab_second" class="tab">Tab Two</a></li>
  <li><a href="#third" id="tab_third" class="tab">Tab Three</a></li>
</ul>
<div id="panel_first" class="panel">
  Here is the content for the first tab.
</div>
<div id="panel_second" class="panel">
  Here is the content for the second tab.
</div>
<div id="panel_third" class="panel">
  Here is the content for the third tab.
</div>     
</div>
        

Configuration

You can create a tab set and pass it a few options to customize it to your current page structure. By default, the code will search for tabs by using the class name "tab", and tab content using the class name "panel". It also will apply the class name "selected" to the currently selected tab.

You may also have noticed that the id attributes for the tags and tag content started with either "tab_" or "panel_". This is a way to link a tab with its specific content while still keeping the ids unique. You can change these in the configuration as well.

Finally, if you wanted, you can turn off the fade effect, and also change the event that triggers the tab activation.

Below is an example of how you can pass options to your tab set.


var tabs = new tabset('container', {
classNames: {
  tab:        'tab',      // class name used to identify the tabs
  panel:      'panel',    // class name used to identify the tab content
  tabActive:  'selected'  // class name added to the active tab
},                        
ids: {                    
  tab:        'tab_',     // what to strip off the tab id to get the tab name
  panel:      'panel_'    // what to strip off the tab content id to get the tab name
},                        
onEvent:      'click',    // perhaps you want to activate on mouseover? not recommended
effects:      true        // set this to false if you do not want to include effects.js
});
          

Download

New version works with FF3 and Prototype 1.6! Download the new version of stereotabs 1.6.

Download the old version for Prototype 1.5 (won't work with Firefox 3) here. Or, if you just want the source and not the whole archive, you can view stereotabs.js.

107 Responses to Stereotabs: Tabs with Prototype and Scriptaculous

  1. fadhlirahim says:

    This is so cool. I’m wondering whether this stereotabs can be integrated with velocity menu

  2. smeves says:

    I’m wondering if people think it would be better to eliminate the IDs and class names from the tabs themselves and just rely on the href tag?

    @fadhlirahim: I bet it would work with velocity menu… just include both javascript sources and initialize stereotabs second. it should be able to add the onclick event in conjunction with any existing tab events.

  3. kangax says:

    Clicking on tabs too fast screws everything up…
    Maybe you should consider putting “limit: 1” into Effect’s queue option?

    Best,
    kangax

  4. is there any way to load tab contents dynamically?

  5. naholyr says:

    @Yücel
    Sure, just use prototypejs’ Ajax.Updater
    http://prototypejs.org/api/ajax/updater

  6. Ben Arwin says:

    Very nice guys!

  7. Very nice!
    If you want to see it in real world:

    http://www.ihr-implantologe.eu

    Thank you very much!

  8. Lenrick says:

    Hello,

    Very usefull script. Only thing is when you refresh the page, you see all of the content flashin before the content fades. Is there a way to fix this? Hope to see a reply.

    Thx.
    len

  9. Scott Meves says:

    Len,

    If you add a “display:none” style to your panels they will not display when the page loads. However, this will make your page inaccessible to visitors with javascript turned off. It’s a trade-off.

  10. Joel says:

    Thank you for a great script! It works great although I encoutered a small problem when using it without effects. I think this is what you meant (from line no 55-67):

    
       if (this.showPanel) {
          if (this.options.effects) {
            new Effect.Fade(this.showPanel, {queue: 'front'});
          } else {
            $(this.showPanel).hide();//$(this.currentPanel).hide();
          }
        }
        
        if (this.options.effects) {
          new Effect.Appear(this.currentPanel, {queue: 'end'});
        } else {
          $(this.currentPanel).show(); //$(this.showPanel).show();
        }
    }}}
    
    
  11. Lenrick says:

    Hi Scott,

    Thx for your fast reply. It worked.

    Len.

  12. Chris says:

    doesn’t work with prototype 1.6
    Any fix?

  13. Hey, Love the script! I just have a quick question, though… I was wondering if there was any way to highlight the selected tab using CSS? cheers. D.

  14. david says:

    hey awsome script

    one thing though:

    is there a way to make it not flicker when you first come upon the tabbed page? (it seems that the script first loads, and displays the tabs, and then hides it)

  15. Scott Meves says:

    @david, check out my comment to Len above.

    (If you add a “display:none” style to your panels they will not display when the page loads. However, this will make your page inaccessible to visitors with javascript turned off. It’s a trade-off.)

  16. david says:

    lol omg

    i totally missed that

  17. samantha says:

    Hi,

    Clicking several tabs very fast will display several tabs contents together. Is there anyway that can ensure only one tab content is display at a time?

  18. Eric says:

    Yes, I noticed that the contents wil be displayed together if you click the tabs fast. It’s easy to reproduce this.

    Is this perhaps related to the fading effect?

  19. Henly says:

    Whenever the tab is clicked, the page will jump back to the top. Does anyone know how to fix it?

  20. Kate says:

    Thanks for this script. Everything works fine, except: I cannot switch off the fading effect. I tried to set effects: false in line 34. But then it doesn’t work anymore. Any suggestions?

  21. Bart says:

    Thnx;
    But I have the same problem as Kate. If I set the effect to false It does not work anymore…

  22. Ben says:

    Hi,
    Same problem as Samantha, whent he tabs are clicked even remotely fast the content overlaps inside.. Try clicking the demo above, it will happen.

    I am using Safari 3.0, and am experiencing the issue in all browsers..

    Thanks for any help on this!

  23. Scott Meves says:

    Anyone want to post and update to this script that fixes the issue? I’m short on time but plan on doing so in the future. I think we’ll have to add the Effect.Fade into an Effects queue.

  24. Conan says:

    Nice script.

    However, would like to know if we want to print the page, how can all the content be displayed?

  25. Scott Meves says:

    Conan, you could accomplish this by creating a new stylesheet for print.

    In that stylesheet you can add print-specific styles, such as making sure your tabs are visible:

    
    .panel {
       display: block;
    }
  26. Conan says:

    I have add a specific style sheet for print with “.panel {display: block;}”, however only the current tag is displayed and other content still be hidden. Please help. Thanks.

  27. Scott Meves says:

    Conan, If it’s not working perhaps it’s because the prototype functions hide() and scriptaculous’ Effect.Fade() set a display:none style directly to the elements you want to print. You have two options, which I will leave up to you to implement. You could make a print button on the page that when pressed would first make all the elements visible in the page before it prints (or you could make a printer-friendly version of the page without the tabs script). You could also rewrite the code so that instead of calling hide() you instead apply a new class name with something like $(element).addClassName(‘hiddenTab’). and then make sure the inline this class would be in your normal stylesheet as:

    .hiddenTab { visibility: none; display: none }

    Then, in your printer stylesheet, you would override these styles:

    .hiddenTab { visibility: visible; display: block }
  28. Conan says:

    Thanks for the suggestion.

    How can I apply the addClassName function together with fade-in/fade-out effect?

  29. J. A. Rodriguez says:

    Really great piece of code. Just one objection, though. I don’t really like that orphaned exception. I mean, if I have set options.effects = false, throwing an exception saying Effects is missing is out of order.

    My first solution is to check for Effects after confirming I want it in the first place:

    //extend options
    if (this.options.effects && typeof Effect == ‘undefined’)
    throw(“Stereotabs requires including script.aculo.us’ Effects library!”);

    BTW, I changed the message a bit, as you can see.

    Anyway, it may be even better to degrade to options.effects = false and show a warning instead of an exception.

    cheers

  30. cg says:

    Nice code. I’ve used this a few times and it works great. Have a new problem, though. I need to have links within the individual panels that should open other panels but I can’t figure out how to make them work. I tried using the relative and absolute url with the hash for the panel name, but the page doesn’t refresh since the browser just views these as anchor links. How can I use a text link to open a different panel?

  31. Evan says:

    I love the script, but for the life of me cannot get the current tab in a selected state. How was that created in the demo above?

    I see the stylesheets are just a tad different than what is here in the page as opposed to the downloaded file, but the changes don’t reflect the functionality as well.

    Any ideas? The site I’ve implemented the code at is here: http://www.mooshoopork.net

  32. Evan says:

    Nevermind, figured it out.

    the class “ul#tab li a.active” needs to change to “ul#tabnav li a.selected” and that did the trick.

    Tested in firefox and IE 6 for PC.

  33. Scott Meves says:

    @Evan,

    The javascript adds a classname to the currently selected tab called “selected”. So, you could add a style like:

    ul#tabnav li a:hover, ul#tabnav li a.selected {
    background-color: #fff
    

    It looks like the selected state is working fine on your site in FF but not when I checked it in safari. You may need to add “!important” to your a.selected style.Looks like you fixed it while I was checking! Great work.

  34. damien says:

    how can you turn off the effect?

  35. Andriy Tyurnikov says:

    Hello Scott,

    First of all – thanks for sharing good code, I’ve searched for proper tabbing solution, and right now – yours looks like a good fit.

    I want to offer few improvements for you to review, and possibly apply.

    //line 33
    fixHeight: true, //this is to automatically resize panels to have same size as highest one

    //line 46
    if(this.options.fixHeight) {
    var maxPanelHeight = this.panels.max(function(panel) {
    var visible = Element.visible(panel), height;
    Element.setStyle(panel, {position: ‘relative’, width: ‘100%’, left: ‘0px’});
    if (!visible) Element.show(panel);
    height = Element.getHeight(panel);
    if (!visible) Element.hide(panel);
    return height;
    });

    this.panels.each(function(panel) {
    panel.style.height = maxPanelHeight.toString() + ‘px’;
    });

    }

  36. Patrik Ilola says:

    Hi! Great job! Thanks! Just what I was looking for!

    Im working on creating a site for gamers and right now I’m using your script so users can watch “hot”-things posted on the site trough a div at the main page by clicking the tabs. The thing is that this function would be enhanced so friggin much if it would cycle trough all tabs automatically with js, instead of the user always having to click on the tabs to see the next panel. Like the one on this site: http://www.gameplayer.se. It’s exactly what I’m looking for except it’s in flash, wich sucks.

    So question! Is there an easy way to create this? Can’t belive it’s too hard 😛 Tips are appreciated 😀

    Cheers
    Patrik

  37. Scott Meves says:

    @Patrik:

    This is a great idea. If you are familiar with prototype.js, you would want to make use of the periodicalExecuter (http://www.prototypejs.org/api/periodicalExecuter) function to automatically call the ‘activate()’ method on the next tab. This is a quick example I just came up with and it probably won’t work right out of the box, but it might get you going if you feel like diving into it on your own.

    function showNextTab() {
          useNextTab = false;
          
          myTabset.tabs.each(function(tab) {
            console.log('using next tab?', useNextTab);
            if (useNextTab == true) {
              myTabset.activate(tab);
              return;
            }
            var tabName = tab.id.replace(myTabset.options.ids.tab,'');
            console.log(myTabset.currentPanel, myTabset.options.ids.panel+tabName);
            if (myTabset.currentPanel == myTabset.options.ids.panel+tabName) {
              // this is the active tab, so we want to use the next one
              useNextTab = true;
            }
          });
          // we did not find a follow up tab to activate, so we start at the beginning
          myTabset.activate(myTabset.tabs[0]);
        }
    
        new PeriodicalExecuter(showNextTab, 10);
  38. Patrik Ilola says:

    @Scott

    Hmm. Actually I’m not that familiar with prototype.

    Maybe it’s too much to ask for, but is there any chance that you could help me out? Would really like to get it to work. I’m kinda stuck there. I bet that others would be interested too 🙂

  39. Scott Meves says:

    I have to be honest– this is not high on the list right now. But, I’ll keep it in mind for future a future update. Maybe someone out there reading this will want to pitch in.

  40. Patrik Ilola says:

    Okey! I understand! Thanks anyway!

  41. Fadhli Rahim says:

    I recommend the duration of effects to be set to 0.5 instead of the default Fade effect.

  42. Nick Sellen says:

    this code is broken for use in Firefox3 RC2 due to a native implementation of getElementsByClassName which returns a node collection.

    it can be fixed by changing ‘getElementsByClassName’ to ‘select’ and adding a ‘.’ before the class name.

    e.g.
    before : getElementsByClassName(‘tab’)
    after : select(‘.tab’)

  43. julien says:

    Well, thanks Nick for that tip. i just installed FF3 and started to panic a bit when i check the site using this script.

    out of curiosity, does this mean that FF3 is going break many script or just the one using getElementsByClassName function?

    i’m not sure if it’s such a major thing (newbie to js) but kind of odd if it impacts so many script to make such change with any fall back from FF, no?

    thanks again.

    cheers

  44. Adrian Schoenig says:

    Great script. I had a similar problem as Nick mentioned above, though. However, not with Firefox but with Safari 3 and Prototype 1.6. It’s working fine with Prototype 1.5, but in order to work with 1.6 you have to change the getElementsByClassName to select as in Nick’s comment.

  45. foxmask says:

    Nick : where do you change all of this, please ?
    i’ve made the changes in the stereotabs.js but that does not do the trick.

    regards.

  46. Mike says:

    Hi… Looks like bahzillions of people have upgraded to Firefox 3, and it doesn’t work with Stereotabs…

    I’m going to need to switch to another tabs system until a fix is released… any word on a timeframe?

    Stereotabs FTW!!! (once it works with FF3!) 🙂

  47. Scott Meves says:

    Just uploaded a new version that works with Firefox 3 and prototype 1.6. Thanks to Nick’s comment the fix was super easy. Thanks Nick!!!

  48. evan says:

    still can’t get to work in FF3 – does something other than the .js files need to be updated?

  49. evan says:

    I get the error “this.tabs.each is not a function”
    line 42 in stereotabs.js