about me

I'm a 37'ish year old web application developer from South Portland, Maine. I love meeting fellow techies, drop me a line if you want to talk shop.

Categories

Ruby on Rails, JQuery UI … and TabsRenderer!

Posted on 05/19/2008 at 05:46 AM

I’m a big fan of JQuery UI and have been using the Tab plugin a lot recently.

However, I found the code to create each Tab a bit repetitious. It became especially ugly in situations where you might want to display a tab conditionally ... first displaying the tab header, then the tab body, and having to re-use the same condition in both places. yuck!

Sooo, since I had recently given a presentation at our local Ruby Users Group on Design Patterns in Ruby, I thought maybe a little template pattern would be a nice fit here. The result: TabsRenderer!!

TabsRenderer is a view helper that lets me build my tabs in a logical order, and then call a special method to return the final result. As well, creating a tab conditionally is a breeze!

I should mention, since I created TabsRenderer as a helper Class and not just a helper method, I had to address the issue of not being able to use Rail’s other helpers within it, as they would be outside my class’s scope. My solution was to borrow a technique I learned recently over at railscasts called ”Refactoring Out Helper Object”.

On to the code ...

5/20/08 - updated with Dietrich’s 3rd param options hash suggestion
6/03/08 - updated with param for options hash on main div as well
6/22/08 - an official repository for this code has been set up at github

Plop this into your helpers directory, and name it tabs_renderer.rb

class TabsRenderer
  
  def initialize
(templateoptions={})
    @
template template
    
@options options
    
@tabs []
  end
  
  def create
(tab_idtab_textoptions={}, &block)
    
raise "Block needed for TabsRenderer#CREATE" unless block_given?
    @
tabs << [tab_idtab_textoptionsblock]
  end
  
  def render
    content_tag
(:div, (render_tabs render_bodies), {:id => :tabs}.merge(@options))
  
end
  
  
private #  ---------------------------------------------------------------------------
  
  
def render_tabs
    content_tag 
:ul do
      @
tabs.collect do |tab|
        
content_tag(:lilink_to(content_tag(:spantab[1]), "##{tab[0]}") )
      
end
    end
  end
  
  def  render_bodies
    
@tabs.collect do |tab
      
content_tag(:divcapture(&tab[3]), tab[2].merge(:id => tab[0])) 
    
end.to_s
  end
  
  def method_missing
(*args, &block)
    @
template.send(*args, &block)
  
end
  
end

Now, include your tab assets and initialize your tabs as usual. TabsRenderer assumes that you want to ID your tab’s div as ‘tabs’.

<% javascript_include_tag("tabs/ui.tabs.pack.js") %>
<% 
stylesheet_link_tag("tabs/ui.tabs.css") %>

<
script type="text/javascript" charset="utf-8">
    $(function()
{
        
$('#tabs > ul').tabs();
    
});
</script>

Now use TabsRenderer to create some tabs ...notice how you can put conditions on the tail end of the block. We pass ‘self’ to our helper so that it knows where to delegate method calls that aren’t it’s own. Creating a tab is as simple as specifying a div #id and an Tab title.

<% tabs TabsRenderer.new(self) %>

    <% 
tabs.create('basic_tab''Basic Info') do %>
        
# ...
    
<% end %>

    <% 
tabs.create('gallery_tab''Gallery') do %>
        
# ...
    
<% end unless @images.blank? %>

    <% 
tabs.create('admin_tab''Admin') do %>
        <%= 
render :partial => "super_secret_stuff" %>
    <% 
end if current_user.has_role'admin' %>
    
<%= 
tabs.render %>

And here is the code that would be generated from the above example.

<div id="tabs">
    <
ul>
        <
li><a href="#basic_tab"><span>Basic Info</span></a></li>
        <
li><a href="#gallery_tab"><span>Gallery</span></a></li>
        <
li><a href="#admin_tab"><span>Admin</span></a></li>
    </
ul>
    <
div id="basic_tab">
        
# ... 
    
</div>
    <
div id="gallery_tab">
        
# ... 
    
</div>
    <
div id="admin_tab">
        
# ... rendered partial here
    
</div>
</
div>

Hope this is of use to someone else! Its sure been useful to me. I would love to hear your comments and criticisms. Please feel free to request changes to the class.

ps. Rails folks wanting to use JQuery in their projects should check out jrails, it wraps a lot of the prototype helpers.

pps. JQuery Rocks@!

18 Comments

Comment #1 by Dietrich  on  05/20  at  05:54 PM

Nice… gonna try this with some of my jquery/rails helpers…

You might want to add an options={} to pass styles for example:
def create(tab_id, tab_text, options={}, █)
@tabs << [tab_id, tab_text, options, block]
end

def render_bodies
@tabs.collect do |tab|
content_tag(:div, capture(&tab;[3]),tab[2].merge({:id => tab[0]}))

<% tabs = TabsRenderer.new(self) %>
<% tabs.create(’basic_tab’, ‘Basic Info’, {:style=>"background-color: #00FF00"}) do %>
# ...
<% end %>

Comment #2 by CodeOfficer  on  05/20  at  06:04 PM

oh perfect, I hadn’t thought of that. Thanks for the tip Dietrich .... I’ll add that this evening. =)

Comment #3 by CodeOfficer  on  05/20  at  09:17 PM

Added in your suggestion Dietrich smile

Comment #4 by Jason  on  09/26  at  05:42 PM

Your tab helper is really great! 

In my example below <%=p.name%> does not get updated in each loop it gets stuck to the first ‘p.name’ returned, so every tab body contains the same as the first tab body, any ideas?

<% tabs_for do |tab| %>
<&#xf;o;r p in @protocols%>
<% tab.create(p.id, p.name) do %>
<%=p.name%>
<% end %>
<&#xe;n;d%>
<% end %>

Comment #5 by CodeOfficer  on  09/26  at  06:51 PM

not sure what to say, the following worked fine for me:

http://pastie.textmate.org/private/cvvslbkvqv8dripwexraaq

Comment #6 by Jason  on  09/26  at  11:18 PM

strange, all I did was change it to .each like in your pastie, instead of ‘for’ and it works now.  Thanks

Comment #7 by Chris  on  11/14  at  03:59 AM

Hi,

Cool renderer!

How would you go about changing it to allow a given tab, when clicked, to fire off an ajax call? Let’s say I have a tab called Invoices, and I want to fire off the index action on the Invoices controller when that tab is clicked. Index would populate the Invoices panel with the results of the index view or partial.

Chris

Comment #8 by CodeOfficer  on  11/14  at  12:53 PM

@Chris The Tabs plugins is ajax ready, all you have to do is put a URL in place of the #ID in the href attribute.

Comment #9 by Chris Johnson  on  12/08  at  12:48 PM

Hi,

I’ve been using this with great success! One question: are there any issues that you are aware of with having a set of tabs nested inside a specific tabs (subtabs)?

Chris

Comment #10 by Chris Sund  on  09/14  at  09:24 AM

Hi there,

This might be a real noob question, but I can’t see where you got the following files…

<% javascript_include_tag("tabs/ui.tabs.pack.js") %>
<% stylesheet_link_tag("tabs/ui.tabs.css") %>

I’ve visited the Jquery UI site and looked at their tabs widget, but I’m not sure how I take that and implement it with your example. It seems I’m missing some understanding. Where do I find your JS library’s and where do I put them in my rails app? Can I simply plop them into the javascripts folder under tabs?

Any help is appreciated.

Chris

Comment #11 by CodeOfficer  on  09/14  at  09:34 AM

Hi Chris. ui.jquery.com has a downloader you can use to build your installation of the UI kit. If you choose to use their tabs it will be included in the download. As for where to put things in your rails app, just throw files into their appropriate directories under ‘public’.

Comment #12 by brianp  on  10/19  at  11:52 PM

Hey,
I’m using your code provided from github I think it’s very well laid out but I can’t seem to get it to work. I feel it’s my styling. Do you have a working example anywhere with the recent code from github?

Thanks,
bp

Comment #13 by Heiko  on  06/15  at  08:15 AM

Thanks, nice tip!
Unfortunately it doesn’t work with Rails 2.3.8 because of the concatenated string in the render method (http://breakthebit.org/post/647352254/rails-2-3-8-forced-html-escaping-of-concatenated)

One workaround is to rewrite the method like in http://pastie.textmate.org/1005228

Heiko

Comment #14 by CodeOfficer  on  06/15  at  01:30 PM

Heiko, have a look at the repo, just pushed a change to handle that issue with rails 2.3.8, Thanks to Michelangelo Altamore for the heads up.

http://github.com/CodeOfficer/jquery-ui-rails-helpers

Comment #15 by Jason  on  06/28  at  08:02 PM

I think I am having html_safe issues when using this with Rails 3.  I get a string of escaped tags displayed in the browser, no tabs :( any word on an update for Rails 3

Comment #16 by CodeOfficer  on  06/29  at  03:46 AM

Check the github version and you should be fine. if you still have any problems post an issue there and I’ll check it out smile

http://github.com/CodeOfficer/jquery-ui-rails-helpers

Leave a comment?

Please use Pastie or Gist if you need to write code in your comments.

Name:

Email:

Location:

URL:

Remember my personal information

Notify me of follow-up comments?

Please enter the word you see in the image below:


RailsConf 2008 Often times I will release code for free or go that extra distance to help others online. If my skills were useful to you, please consider a small donation. Thank you very much.

recommend me!

Search

You Can Find Me

@ github.com
@ twitter.com
@ calendaraboutnothing

My Wishlists

@ Amazon.com

My Other Sites

Foundation's Edge, RJones Family, We're Not.com (only for staging), Ailee Jones (same as rjones for now)

Friends of Mine

Aaron, Barnaby, Brian, Chris, Dirk, Frank, Four, Justin, Matt, Mike, Monty, Paul, Sean, Travis

IRC Hangouts

I can usually be found lounging on irc.freenode.net while I work, on the following channels: #fauna, #github, #hello-heroku, #jquery, #passenger, #ruby, #rubyonrails, #slicehost, #sproutcore, #textmate, #werenot.