Tabulous Reference

Contents

Tabsets

A tabbed navigation bar, which we will call a tabset, is specified using a tabs block:

tabs do
  unicorns_tab do
    ...
  end

  rainbows_tab do
    ...
  end
end

Multiple Tabsets

You can have more than one tabset on a page by passing in a name to the tabs block:

tabs(:fun) do
  unicorns_tab do
    ...
  end

  rainbows_tab do
    ...
  end
end

tabs(:colors) do
  red_tab do
    ...
  end

  blue_tab do
    ...
  end
end

Then, in your layout, you need to tell tabulous which tabset to draw:

<%= tabs(:fun) %>
<%= subtabs(:fun) %>
<p>some markup</p>
<%= tabs(:colors) %>
<%= subtabs(:colors) %>

Tab Order

Tabs are displayed in the order they appear. So if you want a set of tabs that looks like this:

  ____________   ____________   __________
_|  UNICORNS  |_|  RAINBOWS  |_|  PONIES  |_

your tabs block should look like this:

tabs do
  unicorns_tab do
    ...
  end

  rainbows_tab do
    ...
  end

  ponies_tab do
    ...
  end
end

Subtabs

Any tab can have several subtabs associated with it that become visible when the parent tab is clicked. Declare subtabs just like tabs, except make sure that their name ends in _subtab. Subtabs should be declared immediately after their parent tab. Parent tabs must include a_subtab_is_active in their active_when declaration:

tabs do
  unicorns_tab do
    ...
  end

  rainbows_tab do
    ...
    active_when { a_subtab_is_active }
  end

  monorainbows_subtab do
    ...
  end

  double_rainbows_subtab do
    ...
  end

  ponies_tab do
    ...
    active_when { a_subtab_is_active }
  end

  shetland_ponies_subtab do
    ...
  end
end

Tab Names

It doesn't matter what you call a tab, but it must end in _tab or _subtab, and it can't have the same name as another tab.

tabs do
  unicorns do  # BAD: does not end in _tab
    ...
  end

  rainbows_tab do
    ...
  end
end

Tab Declarations

Each tab has five required declarations (text, link_path, visible_when, enabled_when and active_when) and one optional declaration (http_verb).

The text, link_path, visible_when and enabled_when declarations do not need to be given a block. If given a block, the block will be executed in the context of the layout where the tabs are rendered.

You can place any Ruby code in the tabulous blocks; it will be executed as if it were in a view.

Here is an example of a tab with the five required declarations:

unicorns_tab do
  text          'Unicorns'
  link_path     '/unicorns'
  visible_when  { current_user.loves_unicorns? }
  enabled_when  true
  active_when   { in_action('any').of_controller('unicorns') }
end

text

text must evaluate to a string. This string will be what users see written on the tab.

link_path must evaluate to a string which is a valid URL or path. This is the URL that the browser will load when the tab is clicked.

narwhals_tab do
  ...
  link_path  { narwhals_path }
  ...
end

Since a block given to link_path is evaluated in the context of a view layout and not any particular view, you should not reference HTTP params:

narwhal_tab do
  ...
  link_path  { narwhal_path(params[:narwhal_id]) }  # WRONG
  ...
end

http_verb

http_verb is an optional declaration that must be either :get, :post, :put, :patch or :delete. It defaults to :get. When http_verb is not :get, a data-method attribute will be added to the tab's anchor tag. This is useful for adding a "sign out" subtab action link, for example.

narwhals_tab do
  ...
  http_verb :delete
  ...
end

visible_when

If visible_when evaluates to false (or a falsy value like nil), the HTML for that particular tab will not be generated. This could be used to hide a tab conditionally, for instance:

visible_when  { current_user.present? }

could be used to hide a tab from anonymous users.

enabled_when

If enabled_when evaluates to false (or a falsy value like nil), li tag for that tab will have a "disabled" class.

active_when

Tabulous sets a tab as active depending on the controller action that generated the web page. active_when should list out all of the actions for which the tab should be selected:

unicorns_tab do
  ...
  active_when do
    in_action('index').of_controller('unicorns')
    in_action('show').of_controller('unicorns')
    in_action('new').of_controller('unicorns')
    in_action('create').of_controller('unicorns')
    in_action('edit').of_controller('unicorns')
    in_action('update').of_controller('unicorns')
    in_action('destroy').of_controller('unicorns')
  end
end

As a convenience, you can list more than one action at a time using in_actions:

unicorns_tab do
  ...
  active_when  { in_actions('index', 'show', 'new', 'create', 'edit', 'update', 'destroy').of_controller('unicorns') }
end

Since it is common for there to be a one-to-one correspondence between a controller and a tab, you can list the 'any' action as a catch-all:

unicorns_tab do
  ...
  active_when  { in_action('any').of_controller('unicorns') }
end

The actions that make the tab active do not have to be limited to one controller:

unicorns_tab do
  ...
  active_when do
    in_action('any').of_controller('unicorns')
    in_action('show').of_controller('horses')
  end
end

Namespaced controllers, such as Admin::UserAccountsController, can be referenced like so:

unicorns_tab do
  ...
  active_when  { in_action('any').of_controller('admin/user_accounts') }
end

It is an error for active_when rules of different tabs to overlap or conflict with each other:

unicorns_tab do
  ...
  active_when  { in_action('any').of_controller('unicorns') }
end

list_unicorns_tab do
  ...
  active_when  { in_action('index').of_controller('unicorns') }  # WRONG, conflicts with above active_when
end

If a tab has subtabs, it is automatically set to active if any of its subtabs is active. To make this behavior explicit, the parent tab must specify a_subtab_is_active as one of the rules in active_when.

Customizing

Tabulous tries to provide good default behavior to make it easy to quickly add tabs to your app. Tabulous gives you control over some of its choices if you don't like the defaults.

renderer

This specifies the class that the tabs and subtabs view helpers use to generate the tab markup.

customize do
  renderer :default
end

There are four other renderers you can use:

  • :html5 -- uses nav tags instead of divs
  • :bootstrap -- generates markup compatible with Twitter Bootstrap; it creates a ul tag with a "nav-tabs" class
  • :bootstrap_pill -- generates markup compatible with Twitter Bootstrap; it creates a ul tag with a "nav-pills" class
  • :bootstrap_navbar -- generates markup compatible with Twitter Bootstrap's navbar; it creates a ul tag with a "navbar-nav" class

If this doesn't suit your needs, you can write your own custom renderer. Here's an example of a custom renderer class:

class CustomRenderer < Tabulous::DefaultRenderer

  def tabs_html
    <<-HTML.strip_heredoc
      <div class="tabs" class="from_custom_renderer">
        <ul>
          #{ tab_list_html }
        </ul>
      </div>
    HTML
  end

  def subtabs_html
    <<-HTML.strip_heredoc
      <div class="subtabs" class="from_custom_renderer">
        <ul>
          #{ subtab_list_html }
        </ul>
      </div>
    HTML
  end

protected

  def tab_html(tab)
    return '<li>stub</li>'
  end

end

Put this class in app/tabs alongside the tabulous.rb file. To use this, pass the underscored name of the renderer class (without renderer) to the customize block:

customize do
  renderer :custom
end

You do not have to call the class CustomRenderer. For example, if you specified this renderer:

customize do
  renderer :double_rainbow
end

tabulous will look for a class called DoubleRainbowRenderer when generating markup for tabs.

The best way to understand how to write a custom renderer is to look at the tabulous gem code at how the various tabulous renderers are written.

active_tab_clickable

By default, the active tab is clickable. If you want to prevent the active tab from being clicked, set this to false:

customize do
  active_tab_clickable false
end

when_action_has_no_tab

When tabulous tries to set a tab active based on the current controller action and it can't find any tab that says it should be active, by default tabulous does not render the tabset at all:

customize do
  when_action_has_no_tab :do_not_render
end

This default makes multiple tabsets play nicely with each other. If you want to consider not finding an active tab to be an error, you can tell tabulous to raise an error:

customize do
  when_action_has_no_tab :raise_error
end

Finally, if you want tabulous to draw the tabs anyway, even though none of them are active, you can:

customize do
  when_action_has_no_tab :render
end

render_subtabs_when_empty

By default, tabulous does not include any subtab markup at all if no subtabs are visible. If you always want subtab HTML to appear in the page, even if there is nothing in the list of subtabs, set render_subtabs_when_empty to true:

customize do
  render_subtabs_when_empty true
end

CSS Scaffolding

If you are prototyping a new app and want your tabs to instantly look decent, tabulous provides you with quick and dirty CSS that you can turn on by specifying use_css_scaffolding in your Tabulous.setup block:

Tabulous.setup do

  tabs do
    ...
  end

  customize do
    ...
  end

  use_css_scaffolding

end

You can customize the colors:

use_css_scaffolding do
  background_color '#cec'
  text_color '#464'
  active_tab_color '#fff'
  hover_tab_color '#dfd'
  inactive_tab_color '#aca'
  inactive_text_color '#8a8'
end

You should turn off the CSS scaffolding and use your own CSS before launching your web app, since the scaffolded CSS is placed directly inside your HTML page, which is technically invalid HTML.

Helpers

Tabulous generates HTML for you to place in your layout. It does this with the following helpers:

<%= tabs %>
<%= subtabs %>

If you have multiple tabsets, you need to call these helpers for each tabset:

<%= tabs(:fun) %>
<%= subtabs(:fun) %>

<%= tabs(:colors) %>
<%= subtabs(:colors) %>

Some renderers, like the Twitter Bootstrap renderers, generate markup that has subtabs interspersed within tabs instead of a separate list of tabs and subtabs. If a renderer does this, it is an error to call the subtabs helper:

<%= tabs %>     # always ok
<%= subtabs %>  # DO NOT DO THIS if using a Bootstrap renderer

Generators

Tabulous comes with one generator that will create an app/tabs/tabulous.rb file based on your controllers:

rails generate tabs