Sandbox: JavaScript

Example tabs

Add quick, dynamic tab functionality to transition through panes of local content, even via dropdown menus.

Basic Usage

one

two

three

four

<!-- This is the default usage and requires no additional configuration. -->
<ul class="nav nav-tabs nav-append-content" data-behavior="BS.Tabs">
  <li><a>one</a></li>
  <li><a>two</a></li>
  <li><a>three</a></li>
  <li><a>four</a></li>
</ul>
<div class="tab-content">
  <div class="tab-pane">
    <p>one</p>
  </div>
  <div class="tab-pane">
    <p>two</p>
  </div>
  <div class="tab-pane">
    <p>three</p>
  </div>
  <div class="tab-pane">
    <p>four</p>
  </div>
</div>

With Window Hash Option

This is the same as the example above but the hash option is set. This means that if you select a tab it updates the window hash with a query string that references this state. Reload the page and the selected tab is preserved. If you have more than one tab UI on the page that you wish to do this with, just ensure they both have unique values for the hash option.

There's also a cookie version of this, but use these sparingly, as overloading the browser with a whole bunch of cookies increases response time for requests.

Note If you give your tabs id values, that's what the hash value is stored as (in the url). Otherwise it's the index of the tab. In the example below, I've given the "three" tab an id.

one

two

three

four

<ul class="nav nav-tabs nav-append-content" data-behavior="BS.Tabs" data-bs-tabs-hash="example">
  <li><a>one</a></li>
  <li><a>two</a></li>
  <li><a id="three">three</a></li>
  <li><a>four</a></li>
</ul>
<div class="tab-content">
  <div class="tab-pane">
    <p>one</p>
  </div>
  <div class="tab-pane">
    <p>two</p>
  </div>
  <div class="tab-pane">
    <p>three</p>
  </div>
  <div class="tab-pane">
    <p>four</p>
  </div>
</div>

With Dropdown

one

two

three

four

<!-- Tabs selector must be specified so that the dropdown tab isn't included; otherwise the BS.Tabs filter
     will throw an error in console an exit quietly.. -->
<ul class="nav nav-tabs nav-append-content" data-behavior="BS.Tabs" data-bs-tabs-options="{
      'tabs-selector': 'a.tab'
    }">
  <li class="active"><a class="tab">one</a></li>
  <li><a class="tab">two</a></li>
  <li class="dropdown" data-behavior="BS.Dropdown">
    <a id="myTabDrop1" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>
    <ul class="dropdown-menu" role="menu" aria-labelledby="myTabDrop1">
      <li><a class="tab">three</a></li>
      <li><a class="tab">four</a></li>
    </ul>
  </li>
</ul>
<div id="myTabContent" class="tab-content">
  <div class="tab-pane">
    <p>one</p>
  </div>
  <div class="tab-pane">
    <p>two</p>
  </div>
  <div class="tab-pane">
    <p>three</p>
  </div>
  <div class="tab-pane">
    <p>four</p>
  </div>
</div>

More abstract usage

You can really use the tabs for anything to toggle visibility for any set of sections. They don't have to use the default Tabs UI. A simple example would be a menu next to some content like this:

one one one one one one one one one one one one one one one one one one one one one one one one

two two two two two two two two two two two two two two two two two two two two two two two two

three three three three three three three three three three three three three three three three

four four four four four four four four four four four four four four four four four four four

<div class="row abstract-tabs">
  <div class="col-md-2">

    <ul class="nav nav-list nav-list-vivid" data-behavior="BS.Tabs" data-bs-tabs-options="
      'sections-selector': '!div.abstract-tabs .section'
    ">
      <li><a>one</a></li>
      <li><a>two</a></li>
      <li><a>three</a></li>
      <li><a>four</a></li>
    </ul>
  </div>
  <div class="col-md-4">
    <div class="section"><p>one one one one one one one one one one one one one one one one one one one one one one one one </p></div>
    <div class="section"><p>two two two two two two two two two two two two two two two two two two two two two two two two </p></div>
    <div class="section"><p>three three three three three three three three three three three three three three three three </p></div>
    <div class="section"><p>four four four four four four four four four four four four four four four four four four four  </p></div>
  </div>
</div>

Behaviors: BS.Tabs vs Tabs

BS.Tabs is specifically intended to be used with the Boostrap HTML layout conventions of their tabs ui. In particular, the tabs themselves are expected to be anchors inside of li tags. If you want to use tabs to hide/show correlated content without using a list, just use the Tabs Behavior instead; it has no layout conventions. Note that it doesn't support some of the features that BS.Tabs does, so compare closely. See docs on dev.clientcide.com.

Using anchors to switch tabs

You can also have links anywhere you like that reference different tabs allowing them to change the displayed tab when clicked. Just specity an id for any section. Then make a standard link that names that id with its #anchor and clicking it will show the appropriate tab.

Assuming that the anchors you want to behave this way are NOT inside of the element with the BS.Tabs behavior, you should specify in your options a selector delegationTarget which is a common parent of all links that should be able to switch tabs.

two

three

four

<div class="delegation-container">
  <ul class="nav nav-tabs nav-append-content" data-behavior="BS.Tabs" data-bs-tabs-options="
    'delegationTarget': '!.delegation-container'
  ">
    <li><a>one</a></li>
    <li><a>two</a></li>
    <li><a>three</a></li>
    <li><a>four</a></li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane">
      <p><a href="#two">Show two.</a></p>
    </div>
    <div class="tab-pane" id="two">
      <p>two</p>
    </div>
    <div class="tab-pane">
      <p>three</p>
    </div>
    <div class="tab-pane">
      <p>four</p>
    </div>
  </div>
</div>

Ajaxing in Tab Content

You may wish to delay the loading of tab content into the page until the tab is clicked. Why fetch data no one is going to see? This requires coupling your Tabs UI with the Ajax delegator and optionally the Startup Behavior (to load the first tab). If you load the first tab on the server side you don't need it, but if you enable either of the hash or cookieName options then you need the Startup Behavior to load the tab selected from the hash or cookie value.

one

two

<ul class="nav nav-tabs nav-append-content" data-behavior="BS.Tabs">
  <li>
    <a href="/sandbox/echo_html?html=%3Cp%3EI%27m+some+HTML+content+for+%3Cb%3Etab+one%3C%2Fb%3E.%3C%2Fp%3E"
    data-ajax-options="
      'target':'!ul ~.tab-content .one p',
      'action':'replace',
      'loadOnce':true,
      'useSpinner':true
    " data-behavior="Startup" data-startup-options="
      'delegators':{
        'Ajax':{
          'delay':100,
          'target':'!li',
          'method':'hasClass',
          'arguments':['active'],
          'value':true
        }
      }
    " data-trigger="Ajax">one</a>
  </li>
  <li>
    <a href="/sandbox/echo_html?html=%3Cp%3EI%27m+some+HTML+content+for+%3Cb%3Etab+two%3C%2Fb%3E.%3C%2Fp%3E"
    data-ajax-options="
      'target':'!ul ~.tab-content .two p',
      'action':'replace',
      'loadOnce':true,
      'useSpinner':true
    " data-behavior="Startup" data-startup-options="
      'delegators':{
        'Ajax':{
          'delay':100,
          'target':'!li',
          'method':'hasClass',
          'arguments':['active'],
          'value':true
        }
      }
    " data-trigger="Ajax">two</a>
  </li>
</ul>
<div class="tab-content">
  <div class="tab-pane one">
    <p>one</p>
  </div>
  <div class="tab-pane two">
    <p>two</p>
  </div>
</div>

Here's what those tab links look like in Ruby:

<%= link_to "one", sandbox_echo_html_path(html: "<p>I'm some HTML content for <b>tab one</b>.</p>"), data: {
trigger: 'Ajax',
ajax_options: {
  target: '!ul ~.tab-content .one p',
  action: 'replace',
  loadOnce: true,
  useSpinner: true
},
behavior: 'Startup',
startup_options: {
  delegators: {
    Ajax: {
      delay: 100, # this delay to give the Tabs behavior time to select the startup tab
      target: '!li',
      method: 'hasClass',
      arguments: ['active'],
      value: true
    }
  }
}
} %>

Show All

Finally there's a plugin for Thanx (Behavior.Tabs.ShowAll.js) that allows you to create a special link to expose all the tabs at once. Simply include a link and specify it in your options via the showAllSelector option. Be sure your option for the tabs doesn't include this element.

one

two

three

four

<!-- This is the default usage and requires no additional configuration. -->
<ul class="nav nav-tabs nav-append-content" data-behavior="BS.Tabs" data-bs-tabs-options="
  'tabs-selector': 'a.tab',
  'showAllSelector': 'a.showAll'
">
  <li><a class="tab">one</a></li>
  <li><a class="tab">two</a></li>
  <li><a class="tab">three</a></li>
  <li><a class="tab">four</a></li>
  <li><a class="showAll">show all</a></li>
</ul>
<div class="tab-content">
  <div class="tab-pane">
    <p>one</p>
  </div>
  <div class="tab-pane">
    <p>two</p>
  </div>
  <div class="tab-pane">
    <p>three</p>
  </div>
  <div class="tab-pane">
    <p>four</p>
  </div>
</div>

Usage

You can activate a tab or pill navigation without writing any JavaScript by simply specifying data-behavior="BS.Tabs" on the .nav.nav-tabs or .nav.nav-pillselement.

Behavior Options

Name type default description
tabs-selector string ">li" A selector to find the tab elements that the user clicks.
sections-selector string "+.tab-content > div" A selector to find the sections that correlate to each tab.
smooth boolean false Fade in the content when the tabs are switched.
smoothSize boolean false Transition the height of each tab section as its displayed.
selectedClass string "active" The class added to the active tab.
hash string -- If defined, will add a hash value to the window location so that if the user reloads the selected tab will be selected by default.
delegationTarget string -- If defined, searches from the target element to find another element which will monitor its children for clicks. Any click on an element with an href anchor that begins with # will trigger a search by id for a corresponding section to show. If not defined, the target element with the BS.Tabs behavior will be the link delegation target.
showAllSelector string -- If defined, searches from the target element to find an element that, when clicked, will display all the tabs at once.

via Javascript

This behavior implements the TabSwapper class from the Clientcide repo.

new TabSwapper(container, options);

Related Documentation

Note: Not all links in these work.

Class: Tabs

Handles the scripting for a common UI layout: the tabbed box. If you have a set of DOM elements that are going to toggle visibility based on the related tabs above them (they don't have to be above, but usually are) you can instantiate a Tabs and it's handled for you.

Implements

Syntax

new Tabs(options);

Arguments

  1. options - (options) a key/value set of options

Options

  • selectedClass - (string) the css class for the tab when it is selected; defaults to 'tabSelected'
  • deselectedClass - (string) the class for the tab when it isn't selected; defaults to empty string
  • mouseoverClass - (string) the class for the tab when the user mouses over; defaults to 'tabOver'
  • rearrangeDOM - (boolean) arranges the tabs and sections in the DOM to be in the same order as they are in the class; defaults to true.
  • tabs - (array) a collection of DOM elements for the tabs (these get the above classes added to them when the user interacts with the interface); can also be a $$ selector (string).
  • clickers - (array) a collection of DOM elements for the clickers; if your tab contains a child DOM element that the user clicks - not the whole tab but an element within it - to switch the content, pass in an array of them here. If you don't pass these in, the array of tabs is used instead (the default). Can also be a $$ selector (string).
  • sections - (array) a collection of DOM elements for the sections (these change when the clickers are clicked); can also be a $$ selector (string).
  • initPanel - (integer) the panel to show on init; defaults to 0 (zero)
  • smooth - (boolean) use opacity effect to smooth transitions; defaults to false
  • smoothSize - (boolean) smoothly resize the sections from one section to the other; false is default
  • cookieName - (string) if defined (it isn't by default), the browser will remember the user's previous tab selection using a cookie
  • cookieDays - (integer) how many days to remember the user's tab selection with the cookie; it's ignored if cookieName isn't set; defaults to 999
  • effectOptions - (object) the options to pass on to the transition effect if the "smooth" option is set to true; defaults to {duration: 500}
  • preventDefault - (boolean) if true (the default), clicks to the tabs call preventDefault on tab clicks.

Events

  • onBackground - (function) callback executed when a section is hidden; passed three arguments: the index of the section (integer), the section (element), and the tab (element)
  • onActive - (function) callback executed when a section is shown; passed three arguments: the index of the section (integer), the section (element), and the tab (element)
  • onActiveAfterFx - (function) callback executed when a section is shown but after the effects have completed (so it's visible to the user); passed three arguments: the index of the section (integer), the section (element), and the tab (element)

Example

<ul id="myTabs">
    <li><a href="1">one</a></li>
    <li><a href="2">two</a></li>
    <li><a href="3">three</a></li>
</ul>
<div id="myContent">
    <div>content 1</div>
    <div>content 2</div>
    <div>content 3</div>
</div>

var myTabs = new Tabs({
    selectedClass: 'on',
    deselectedClass: 'off',
    mouseoverClass: 'over',
    mouseoutClass: 'out',
    tabs: $$('#myTabs li'),
    clickers: $$('#myTabs li a'),
    sections: $$('#myContent div'),
    smooth: true,
    cookieName: 'rememberMe'
});

Notes

  • you don't have to specify the classes for mouseover/out
  • you don't have to specify a click selector; it'll just use the tab DOM elements if you don't give it the click selector
  • the click selector is NOT a subselector of the tabs; be sure to specify a full css selector for these
  • you can initialize the class without any tabs or sections and add them subsequently with addTab

Tabs Method: addTab

Adds a tab to the interface.

Syntax

myTabs.addTab(tab, section[, clicker, index]);

Arguments

  1. tab - (mixed) A string of the id for an Element or an Element reference to the tab
  2. section - (mixed) A string of the id for an Element or an Element reference to display when the element clicks to change tabs
  3. clicker - (mixed, optional) A string of the id for an Element or an Element reference to the element (typically within the tab) that the user clicks to switch tabs
  4. index - (integer, optional) where to insert this tab; defaults to the last place (i.e. push)

Returns

  • (object) This instance of Tabs

Tabs Method: removeTab

Removes a tab from the Tabs; does NOT remove the DOM elements for the tab or section from the DOM.

Syntax

myTabs.removeTab(index)

Arguments

  1. index - (integer) the index of the tab to remove.

Returns

  • (object) This instance of Tabs

Tabs Method: moveTab

Moves a tab from one location to another (ie it changes its index).

Syntax

myTabs.moveTab(from, to);

Arguments

  1. from - (integer) the index of the tab to move
  2. to - (integer) its new location

Returns

  • (object) This instance of Tabs

Tabs Method: show

Shows a given section (as if the user clicked the tab).

Syntax

myTabs.show(index);

Arguments

  1. index - (integer) the index of the tab to show

Returns

  • (object) This instance of Tabs

Class: Tabs.Hash

Extends the Tabs class to store tab selection as a url querytring in the window.location.hash.

Extends

Tabs

Options

  • hash - the query string in which to store the hash.

/*

name: Behavior.Tabs description: Adds a tab interface (TabSwapper instance) for elements with .css-tabui. Matched with tab elements that are .tabs and sections that are .tabsections. provides: [Behavior.Tabs] requires: [Behavior/Behavior, /TabSwapper.Hash] script: Behavior.Tabs.js

... */

Behavior.addGlobalFilters({

Tabs: {
    defaults: {
        'tabs-selector': '.tabs>li',
        'sections-selector': '.tab_sections>li',
        smooth: true,
        smoothSize: true,
        rearrangeDOM: false,
        preventDefault: true
    },
    setup: function(element, api) {
        var tabs = element.getElements(api.get('tabs-selector'));
        var sections = element.getElements(api.get('sections-selector'));
        if (tabs.length != sections.length || tabs.length == 0) {
            api.fail('warning; sections and sections are not of equal number. tabs: ' + tabs.length + ', sections: ' + sections.length);
        }

        var ts = new TabSwapper.Hash(
            Object.merge(
                {
                    tabs: tabs,
                    sections: sections
                },
                Object.cleanValues(
                    api.getAs({
                        initPanel: Number,
                        hash: String,
                        cookieName: String,
                        smooth: Boolean,
                        smoothSize: Boolean,
                        rearrangeDOM: Boolean,
                        selectedClass: String,
                        initPanel: Number,
                        preventDefault: Boolean
                    })
                )
            )
        );
        ts.addEvent('active', function(index){
            api.fireEvent('layout:display', sections[0].getParent());
        });

        // get the element to delegate clicks to - defaults to the container
        var delegationTarget = element;
        if (api.get('delegationTarget')) delegationTarget = element.getElement(api.get('delegationTarget'));
        if (!delegationTarget) api.fail('Could not find delegation target for tabs');

        // delegate watching click events for any element with an #href
        delegationTarget.addEvent('click:relay([href^=#])', function(event, link){
            if (link.get('href') == "#") return;
            // attempt to find the target for the link within the page
            var target = delegationTarget.getElement(link.get('href'));
            // if the target IS a tab, do nothing; valid targets are *sections*
            if (ts.tabs.contains(target)) return;
            // if no target was found at all, warn
            if (!target) api.warn('Could not switch tab; no section found for ' + link.get('href'));
            // if the target is a section, show it.
            if (ts.sections.contains(target)) {
                event.preventDefault();
                var delegator = api.getDelegator();
                if (delegator) delegator._eventHandler(event, ts.tabs[ts.sections.indexOf(target)]);
                ts.show(ts.sections.indexOf(target));
            }
        });

        element.store('TabSwapper', ts);
        api.onCleanup(function(){
            ts.destroy();
            element.eliminate('TabSwapper');
        });
        return ts;
    }
}

});