Introducing Tabulous: Tabs in Rails

Easy Tabs for Your Rails Application

If you’re like me, most of the Rails applications you’ve written use tabbed navigation. And if you’re like me, you find that writing the code to handle tabs becomes increasingly more boring with each new application. So I wrote tabulous. Tabulous aims to solve this problem once and for all with a quick and easy way to set up and manage your tabs.

Getting Started

The tutorial will get you off to a good start. Have fun!

Update: This blog post is about an old version of tabulous. Tabulous 2 has a completely different syntax. Read more about the new version of tabulous.

Design Decisions

Consolidation

Artwork reminiscent of Jackson Pollock's style.

Whenever I implemented tab code in Rails I always ended up adding logic to my views and peppering my controllers with tab-related code.

Although it worked, it never felt quite right to have my tab-related code splattered all over my views and controllers in tiny little droplets like some sort of new Pollock-inspired software design pattern. So I decided to consolidate tab code as much as possible. It all lives in app/tabs/tabulous.rb. The only exceptions are the calls you have to make in your layout(s) to <%=tabs%> and <%=subtabs%> and any CSS you write for the tabs.

Ruby Table

Perhaps the most unusual design decision I made was to part ways with existing Ruby idioms and create my own idiom which I call a “Ruby table”. It looks like this:

  config.tabs do
    [
      #--------------------------------------------------------------------------------------------------------------------------------------------#
      #    TAB NAME                     |    DISPLAY TEXT      |    PATH                    |    VISIBLE?                         |    ENABLED?    #
      #--------------------------------------------------------------------------------------------------------------------------------------------#
      [    :home_tab                    ,    'Home'            ,    root_path               ,    true                             ,    true        ],
      [    :people_tab                  ,    'People'          ,    people_path             ,    true                             ,    true        ],
      [    :preferences_tab             ,    'Preferences'     ,    preferences_path        ,    true                             ,    true        ],
      [    :services_tab                ,    'Services'        ,    services_path           ,    true                             ,    true        ],
      [    :jobs_tab                    ,    'Jobs'            ,    jobs_path               ,    true                             ,    true        ],
      [    :schedules_tab               ,    'Schedules'       ,    "/schedules"            ,    can? :view, Schedule             ,    true        ],
      [    :unavailable_dates_subtab    ,    'Availability'    ,    "/unavailable_dates"    ,    can? :manage, UnavailableDate    ,    true        ],
      #--------------------------------------------------------------------------------------------------------------------------------------------#
      #    TAB NAME                     |    DISPLAY TEXT      |    PATH                    |    VISIBLE?                         |    ENABLED?    #
      #--------------------------------------------------------------------------------------------------------------------------------------------#
    ]
  end

Now Ruby has an idiom which I absolutely love: sending arguments to a method using a hash. Here is a method call with and without the idiom:

  display 'some message', 'red', 40, 5
  display 'some message', :color => 'red', :max_length => 40, :indentation => 5

Let’s see what happens when we use hashes to represent the above tab data:

  config.tabs do
    {
      :home_tab                 => { :path => root_path },
      :people_tab               => { :path => people_path },
      :preferences_tab          => { :path => preferences_path },
      :services_tab             => { :path => services_path },
      :jobs_tab                 => { :path => jobs_path },
      :schedules_tab            => { :path => "/schedules",
                                     :visible => can? :view, Schedule },
      :unavailable_dates_subtab => { :text => 'Availability',
                                     :path => "/unavailable_dates",
                                     :visible => can? :manage, UnavailableDate }
    }
  end

The hash version has two benefits:

  1. It can rely on intelligent defaults so the code is terser.
  2. Rubyists are quite used to hashes, even nested hashes.

The Ruby table also has some compelling benefits which is why I ultimately went with it. One is explicitness. The hash version relies on intelligent defaults which means that the programmer needs to be familiar with how these defaults are determined. The hash version also does not show the programmer all of the available options. The Ruby table explicitly shows all options and all values. No guesswork.

Secondly, the Ruby table is easier to scan than the nested hash. Since code is read more often than it is written, I opted for the solution that was easier to read even though it’s more verbose to write.

The Ruby table idiom is not without its drawbacks, however. It forces you into the drudgery of constantly realigning the table columns. That’s why tabulous comes with a tabs:format rake task that does that for you.

You can skip to the end and leave a response. Pinging is currently not allowed.

29 Responses to “Introducing Tabulous: Tabs in Rails”

  1. ramanr says:

    Is there a way to change the markup? To be precise, I want the id of the nav element be changed to something with my own rather than “#tabs” or “#subtabs”.

  2. techiferous says:

    Hi ramanr,

    You can monkey_patch Tabulous.render_tabs and Tabulous.render_subtabs in (see lib/tabulous/tabulous.rb). Sorry that I don’t offer a better interface for that directly.

  3. ramanr says:

    thanks for your response. I did see I can monkey patch your code and change the markup. As raised an issue in github, I wanted to find out is there any other way to get it done.

  4. Brandon Zylstra says:

    I for one love the Ruby Table. The only thing I don’t like is the thought of keeping the ASCII graphics lined up every time I make a change… perhaps there *is* some use for overtype mode after all…

Leave a Reply