Logical Tabs: a Rails helper for persistent HTML tab interfaces
June 9, 2010
logical_tabs is a Rails plugin that assists with the creation of a tabbed panel interface. It has a number of advantages over other solutions, but the primary one is that the tabs persist: if you reload or revisit a page with a tabbed panel, it will remember which tab you last had open on that page. There is a demo project available that shows how to use it with both ERB and HAML templates.
logical_tabs works with both Prototype and jQuery. Here's a screenshot of it in use on a social networking site:
It's still a little rough and I have future plans for it, but I'm using it in production on three sites so I figured I'd give it an early release while I'm hacking at RailsConf 2010.
Why build a new tool, when others exist?
There are already other solutions for building tabbed interfaces, most notably the jQuery UI Tabs tool. Another solution, which I had used previously, is railstabbedpanel.
The jQuery UI tool still requires you to construct all the HTML yourself which can be tedious, and it doesn't work for the large number of Rails products out there using Prototype rather than jQuery. railstabbedpanel provides HTML helpers, but the rendering code is heavily dependent on the specifics of the ActionView rendering/capturing code and so kept breaking when Rails was upgraded, or when HAML was upgraded. I found it very difficult to keep it working as I upgraded my toolset. Also, RTP's javascript was not unobtrusive.
Moreover, neither solution persists tab selections across page loads. So, I wrote this plugin which provides all of these advantages:
- Simple helpers to make it easy to build semantic HTML structures for the tabbed panels.
- Helper rendering that is isolated from the internals of ActionView and shouldn't break when Rails or HAML are upgraded.
- Javascript for both Prototype and jQuery environments
- Persistence of tab selection across page loads - revisit a page, get the same tab.
- Unobtrusive javascript.
Installing logical_tabs
logical_tabs is currently in version 0.6 and only supports Rails 2.x. I have only tested it in Rails 2.3.x, but it should work in 2.2 and 2.1 ... let me know if it doesn't.
Install the plugin:
> script/plugin install git://github.com/LRDesign/logical_tabs.git
Copy in the appropriate javascript file:
If your project uses Prototype:
> cp vendor/plugins/logical_tabs/javascripts/logical_tabs_prototype.js public/javascripts
If your project uses jQuery:
> cp vendor/plugins/logical_tabs/javascripts/logical_tabs_jquery.js public/javascripts
Copy stylesheets (optional)
logical_tabs includes starter stylesheets in both css and sass, you can find them in vendor/plugins/logical_tabs/stylesheets; copy the appropriate file to your public/stylesheets directory and include it in your application, or create your own.
Using the view helpers
logical tabs gives you a view helper, create_tabbed_panel, which takes a block with an argument - that argument will be a TabbedPanel object, the code for which can be found in vendor/plugins/logical_tabs/lib/logical_tabs/tabbed_panel.rb. This object is responsible for tracking all the tabs and their content, generating appropriate element IDs and the like.
There is example code - both ERB and HAML - in the logical_tabs_demo demo project, but the basic usage looks like this. Individual tab/pane pairs are generated simultaneously and can take their content either as a block or as a :content => "content" option.
<% create_tabbed_panel do |tabbed_panel| %>
<% tabbed_panel.add_tab("Tab One") do %>
Content for Tab One
<% end %>
<% tabbed_panel.add_tab("Tab Two") do %>
Content for Tab Two
<% end %>
<% tabbed_panel.add_tab("Tab Three", :content => "<p>Content for tab three</p>") %>
<% end %>When you close the block for create_tabbed_panel, the object will render the HTML for all tabs you have added. It will generate a div with class="tabbed_panel that contains two UL lists; one for the tabs and one for their associated content panels.
That's about it. If you have the correct javascript file for your environment installed, it should just work.
Other Options
logical_tabs generates a default format for the IDs and classes for generated; it uses this to track multiple panels on the same page, for example. However, you can override the generated IDs if you want them formatted in a specific way for custom javascript you've written:
<%= create_tabbed_panel(:base_id => "my_panel") do |panel| %> ...etc...
Will use "my_panel" as the ID of the outer div and as the prefix for the individual IDs of the sub-tabs.
logical_tabs always uses the ID of the outer div as the prefix for all internal components, to guarantee that you don't have duplicate IDs (i.e. two LIs with id='tab_1') if you have multiple panel structures on the page.
Tab Persistence
When using tab interfaces in the past, one of the most common complaints I heard from clients is that the panel would "forget" which tab they had selected when visiting a previous page. The two cases where this was most frequently a problem were:
- When the panels contained forms. If you submit a form but the data fails validation, you may be returned to the same view but with the wrong panel selected, so the user can't see the error messages or the color-coded input fields. This is extremely confusing.
- When the panels list items that the user may want to edit on another page. For example, one client has a tabbed panel in a project management system that lists a project's work orders, action items, and problem reports in separate tabs. If the user clicks to edit a problem report, when finished they may be redirected back to the project page, but now the list of work orders is the active tab - they can't see the list containing the item they just edited. Ouch.
The solution I chose was to store the tab selection in a cookie. logical_tabs stores the current tab selection for each tabbed panel structure (in case you have more than one) along with the url, and at each page load the JS it checks to see if there was previously a tab selected.
Caveats
In some ways, logical_tabs is still a bit of a weekend hack. In particular, I'm not thrilled with the ugly element ID formats, and the jQuery script is not a proper jQuery plugin, doesn't interface with jQueryUI, and isn't themeable with themeroller.
Most of my projects use Prototype, and Logical Reality is only recently beginning to switch to jQuery for new projects. So, the adaptation of the javascript for logical_tabs to jQuery was a very quick hack - my best attempt to clone the Prototype behavior with jQuery selectors. If you think it's ugly, it is, and that's why. :-)
Future Plans
- Clean up the jQuery and make it a properly-behaved jQuery UI plugin skinnable with themeroller.
- Rails 3 compatibility.

Your git repository requires a password !
Hmm … the repository is set to be public, so it should work fine. (I just tested with a non authenticated account and it worked ok).
How are you trying to connect to it? Are you trying to load the github page in a browser, or install the plugin, or clone the repo? This may help me debug your problem.
your git repo does ask for a password
secondly, do you have a working demo of this? I’d like to know if it works when JS is disabled
thanks
I’ll take another look at the git repo issue. The repository is set as public on github. Again … how are you guys connecting? Are you visiting it in a browser, or are you installing it as a plugin, or are you cloning the repo?
There is a demo project, which you can find at http://github.com/LRDesign/logical_tabs_demo. It shows using it with both ERB and HAML.
With Javascript turned off, it will draw correctly (assuming you have the right stylesheets), but it won’t switch between tabs or remember tabs.
[...] NOTE: This post is now deprecated; I have stopped using the plugin described here and written my own, logical_tabs, which has several advantages over this approach and is more compatible across versions of Rails. Please see the new post instead. [...]
@stephen, @Ho Phoung Nam,
Could you be more specific? I just logged out of github and was able to both download the archived source and clone the repo. So, what do you need a password to do?
For the jQuery stylesheet you have as the path:
vendor/plugins/logical_tabs/javascripts/logical_tabs_prototype.js
Your probably mean:
vendor/plugins/logical_tabs/javascripts/logical_tabs_jquery.js
Thanks for posting this great project!
@Mark Lorenz: Thanks for catching the typo! I’ve updated the post.
Glad to know someone’s using logical_tabs; I enjoyed writing it!
Well, I’m trying to use it, but I seem to be having the same problem as @stephen and @Ho
Specifically:
$ script/plugin install http://LRDesign@github.com/LRDesign/logical_tabs.git
Initialized empty Git repository in /Users/mark/Dropbox/src/BridalCore/vendor/plugins/logical_tabs/.git/
Password:
Sorry for turning your comment section into a conversation thread. I was able to git by doing the following:
$ cd vendor/plugins
$ git clone git://github.com/LRDesign/logical_tabs.git
$ rake vendor/plugins/logical_tabs/tasks/logical_tabs_tasks.rake
– Kill the server -
$ script/server
I’m a total moron when it comes to git and source control, so there maybe repercussions to do it this way. Also I’m not sure that rake task was really necessary.
Thanks again for the lovely tabs.
My program does an error in the ‘create_tabbed_panel’ method in the Helper when executing line 14 in helper.rb:
concat output
==> wrong number of arguments (1 for 2)
I have to use Rails 2.02 and Ruby 1.8.7 due to some reasons, so maybe it comes from there.
What exactly does this line do? Reading the code I think it should put together the tabbed_panel elements + output somehow.
I browsed back the git versions but did not find a clue how to solve this.
I fixed it in Rails 2.02 by using
concat output,proc.binding
@peter: thanks for the update and the note. I hadn’t tested in in Rails 2.0, so I appreciate the pointer. I may go back and see if I can incorporate your fix so that it will work in both versions of Rails, but supporting Rails versions that old is a relatively low priority … getting it working for Rails 3 is probably the next thing I’ll do.
I try and it work for Rails 3. Thanks.
Add A Comment