开发者

Ruby using block and params to DRY up template

I have a pattern in a couple of templates that's repeated. The code is for rendering a set of tabs and content for each tab.

Contents of /app/views/general/_product_groupings.html.erb

<table cellpadding="1" cellspacing="0" class="sub_container clear">
  <tr>
    <% first_visible_tab = true %>
    <% @bundle.groupings.each do |group| %>
      <td id="tab_heading_for_group_<%= group.id %>" class="tab_heading <%= 'selected' if first_visible_tab %>" onclick="show_tab('product_type_<%= group.id %>')"><%= one_liner(group.name) %></td>
      <td></td>
      <% first_visible_tab = false %>
    <% end %>
    <td class="last"></td>
  </tr>
  <tr>
    <td colspan="99" class="tab_content">

      <% first_visible_tab = true %>
      <%# groupings is an array of products %>
      <% @bundle.groupings.each do |group| %>
        <div style="display: <%= (first_visible_tab) ? '' : 'none' %>" id="tab_body_for_group_<%= group.id %>" class="tab_body container_inner">
          <% first_visible_tab = false %>
          <% template = case group.grouping_type 
            when 'selection'
             开发者_开发百科 :product_selection_group
            when 'group'
              :product_optional_group
            when 'product'
              :product_optional_group
            end %>
            <%= render :partial => template.to_s, :locals => {:group => group} %>


        </div>
      <% end %>
    </td>
  <tr>
</table>

The code consists of some parts. There is the general parameter passed in, @bundle. There is a header section (lines 1-10):

<table cellpadding="1" cellspacing="0" class="sub_container clear">
  <tr>
    <% first_visible_tab = true %>
    <% @bundle.groupings.each do |group| %>
      <td id="tab_heading_for_group_<%= group.id %>" class="tab_heading <%= 'selected' if first_visible_tab %>" onclick="show_tab('product_type_<%= group.id %>')"><%= one_liner(group.name) %></td>
      <td></td>
      <% first_visible_tab = false %>
    <% end %>
    <td class="last"></td>
  </tr>

In the header section, there are parts that differ each place code like this is used: The collection iterated upon, @bundle.groupings. The parameter for show_tab() onclick. The id of the tab id="tab_heading_for_group_<%= group.id %>".

Below the header, there is an area that i reckon could be yielded as a block, the real content of each tab content area (lines 19-27):

          <% template = case group.grouping_type 
            when 'selection'
              :product_selection_group
            when 'group'
              :product_optional_group
            when 'product'
              :product_optional_group
            end %>
            <%= render :partial => template.to_s, :locals => {:group => group} %>

Just above the content is the same collection @bundle.groupings.each do ... repeated from the head section.

I would really much like to DRY this up. By defining a block with the content that can be yielded inside a helper method.

I think this method should take the following params:

  • id_template
  • onclick_method #perhaps with a pattern for the parameter to the onclick method
  • collection
  • block to be yielded inside the content area of the tab

Question is, how do I make a helper method to take both block and params?


After reading Coda Hales examples on blocks, I found a solution that worked. The trick is to add your usual parameters, followed by the block like so:

method_call(params) { |x| x.do_stuff_inside_the_methods_context }

And x will be given the scope of the yield inside your method. Here is the rewrite of the code used in the example. (for now, leaving out any dynamic parameters to the onclick method)

View:

<% options = {
  :iterator => @bundle.groupings,
  :id_prefix => "group"
} %>
<%= render_tabs(options) { |product|  
  template = case product.grouping_type 
            when 'selection'
              :product_selection_group
            when 'group'
              :product_optional_group
            when 'product'
              :product_optional_group
            end
  render :partial => template.to_s, :locals => {:group => product}

} %>

Helper:

  def render_tabs(opts, &block)
    collection = opts[:iterator]
    prefix     = opts[:id_prefix]
    buf = %(
    <table cellpadding="1" cellspacing="0" class="sub_container clear">
      <tr>
    )
    first_visible_tab = true 
    collection.each do |iter| 
      classnames = "tab_heading"
      classnames << " selected" if first_visible_tab
      td_id = "#{prefix}_#{iter.id}"
      buf << %(
        <td id="tab_heading_for_#{td_id}" class="#{classnames}" onclick="show_tab('#{td_id}')">#{one_liner(iter.name) }</td>
      )

      first_visible_tab = false 
    end 
    buf << %(<td class="last"></td>
      </tr>
      <tr>
        <td colspan="99" class="tab_content">)

    first_visible_tab = true 
    collection.each do |iter|
      td_id = "#{prefix}_#{iter.id}"
      display_attr = (first_visible_tab) ? '' : ' style="display:none"'
      buf << %(
        <div #{display_attr} id="tab_body_for_#{td_id}" class="tab_body container_inner">
        )
      first_visible_tab = false

      buf << yield(iter)
      buf << %(
        </div>
        )
    end
    buf << %(
        </td>
      <tr>
    </table>
    )

    buf
  end


The only thing I'd do here is to get rid of the case statement by putting the template name in the code, perhaps in the helper*, so that the view can just ask someone else which template to use:

<%= render :partial => template_for_group(group), :locals => {:group => group} %>

You don't show us the original templates, but I gather there is common code in them. I'd be inclined to factor that out by moving the common code into its own template(s) and having each template <% render :partial...> the common bits as needed.

*Although MVC seems to call for the template name to be decided in the view or helper, from an OO point of view it really belongs in group. Pick one. If it hurts, that's the code telling us that the other choice was better.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜