I'm a 34'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.
Posted on 05/19 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(template, options={})
@template = template
@options = options
@tabs = []
end
def create(tab_id, tab_text, options={}, &block)
raise "Block needed for TabsRenderer#CREATE" unless block_given?
@tabs << [tab_id, tab_text, options, block]
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(:li, link_to(content_tag(:span, tab[1]), "##{tab[0]}") )
end
end
end
def render_bodies
@tabs.collect do |tab|
content_tag(:div, capture(&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@!
oh perfect, I hadn’t thought of that. Thanks for the tip Dietrich .... I’ll add that this evening. =)
Added in your suggestion Dietrich
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.
Foundation's Edge, RJones Family, We're Not.com (only for staging), Ailee Jones (same as rjones for now)
Aaron, Barnaby, Brian, Chris, Dirk, Frank, Fred, Four, Justin, Matt, Mike, Monty, Paul, Sean, Travis
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.
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 %>