Search code examples
ruby-on-railsrubyruby-on-rails-3ruby-on-rails-plugins

Multi level block method is generating issue


I have a class

class DataListBuilder
    include ActionView::Helpers::TagHelper
    include ActionView::Helpers::CaptureHelper
    include ActionView::Helpers::UrlHelper
    attr_accessor :object, :output_buffer

    def initialize(object)
      @object, @output_buffer = object, nil
    end

    def column (&block)
      if block_given?
        content_tag(:li, block.call(self))
      else
        content_tag(:li, "")
      end
    end

    def options_column(&link_block)
      if block_given?
        content_tag(:li, content_tag(:dl, "<dt><a href='#'>&nbsp;</a></dt><dd><ul>#{link_block.call(self)}</ul></dd>".html_safe, :class=>'options'))
      else
        content_tag(:li, "")
      end
    end

    def link_item(title, url, options={})
      content_tag :li, link_to(title, url, options)
    end
  end

and calling it as

<%= l.options_column do |c| %>
        <%= c.link_item 'Show', lead_path(c.object) %>
        <%= c.link_item 'Edit', edit_lead_path(c.object) %>
        <%= c.link_item 'New Note', "leads/#{c.object.id}/notes/new", :class=>"display-newxdoc", :id=>c.object.id %>
        <%= c.link_item 'Create Opportunity', new_lead_opportunity_path(c.object) %>
    <% end %>

the desired output is

<li><dl class="options"><dt><a href="#">&nbsp;</a></dt><dd><ul style="display: none;">
    <li><a data-remote="true" class="plus" href="leads/details/309">&nbsp;</a></li>
    <li>3w</li>
    <li>Simon Wu</li>
    <li>1-714-553-0888</li>
    <li>omnisw@unifiedbeat.com</li>
    <li>Unified Beat</li>

        <li><a href="/leads/309">Show</a></li>
        <li><a href="/leads/309/edit">Edit</a></li>
        <li><a id="309" class="display-newxdoc" href="leads/309/notes/new">New Note</a></li>
        <li><a href="/leads/309/opportunities/new">Create Opportunity</a></li>

but it is generating

<li><a href="/leads/309">Show</a></li>
<li><a href="/leads/309/edit">Edit</a></li>
<li><a id="309" class="display-newxdoc" href="leads/309/notes/new">New Note</a></li>
<li><a href="/leads/309/opportunities/new">Create Opportunity</a></li>
<li><dl class="options"><dt><a href="#">&nbsp;</a></dt><dd><ul style="display: none;">
    <li><a data-remote="true" class="plus" href="leads/details/309">&nbsp;</a></li>
    <li>3w</li>
    <li>Simon Wu</li>
    <li>1-714-553-0888</li>
    <li>omnisw@unifiedbeat.com</li>
    <li>Unified Beat</li>

        <li><a href="/leads/309">Show</a></li>
        <li><a href="/leads/309/edit">Edit</a></li>
        <li><a id="309" class="display-newxdoc" href="leads/309/notes/new">New Note</a></li>
        <li><a href="/leads/309/opportunities/new">Create Opportunity</a></li>
</ul></dd></dl></li>
    </ul></dd></dl></li>

Can any one help me in it.

Complete code is listed here.


Solution

  • First of all we refactored your helper for more intensive usage of content_tag (just to get whats going in this code ^_^).

    Next we add usage of output_buffer which was defined but not used at all in helper.

    After it all methods wich should be called from erb should return nil so they didn't appear in HTML.

    And last syntactic suger was usage of instance_eval so you don't need {|c| ...} style blocks. You can use all variables from DataListBuilder directly there.

    module DataListHelper
      def list_headers(args=[])
        args    = Array.new(args)
        columns = []
        args.map { |o| columns << content_tag(:li, o.split(":").first, :style=>"width:#{o.split(":").second}px;") }
        content_tag(:ul, columns.join(" ").html_safe, :class=>"list-headers")
      end
    
      def data_list_total_records(array)
        content_tag(:div, page_entries_info(array).html_safe, :class=>"total-records")
      end
    
      def data_list_for(object, headers=[], &block)
    
        if object.is_a? Array
          if object.length == 0
            list_headers(headers).concat(content_tag(:strong, "<br />No records found".html_safe))
          else
            res_obj = data_list_total_records(object)
            res_obj << content_tag(:ol, :class=>"data-list") do
              res_ol = content_tag(:li) do
                res = list_headers(headers)
                object.each do |o|
                  builder = DataListBuilder.new(o)
                  res << content_tag(:li) do
                    content_tag(:ul, :id=>o.id, :class=>"list-row #{cycle('odd', 'even')}") do
                      capture(builder, &block)
                      builder.output_buffer.html_safe
                    end
                  end
                end
                res
              end
              res_ol << data_list_pagination(object)
            end
            res_obj
          end
        else
          list_headers(headers).concat(content_tag(:strong, " <br />Not available."))
        end
      end
    
      class DataListBuilder
        include ActionView::Helpers::TagHelper
        include ActionView::Helpers::CaptureHelper
        include ActionView::Helpers::UrlHelper
        include Rails.application.routes.url_helpers
    
        attr_accessor :object, :output_buffer, :controller
    
        def initialize(object)
          @object, @output_buffer = object, ''
        end
    
        def column (&block)
          @output_buffer << if block_given?
            content_tag(:li, instance_eval(&block))
          else
            content_tag(:li, "")
          end
          nil
        end
    
        def options_column(&link_block)
          @output_buffer << if block_given?
            content_tag(:li) do
              content_tag(:dl, :class=>'options') do
                res = content_tag(:dt) do
                  content_tag(:a, '&nbsp;'.html_safe, :href => '#')
                end
                res << content_tag(:dd) do
                  content_tag(:ul) do
                    instance_eval &link_block
                  end
                end
              end
            end
          else
            content_tag(:li, "")
          end
          nil
        end
    
        def link_item(title, url, options={})
          content_tag :li, link_to(title, url, options)
        end
      end
    end
    

    And your view became this:

    <%= data_list_for @leads, [" :10", "Age:30", "Contact:140", "Phone:140", "Email:180", "Company:100", ""] do |l| %>
        <%= l.column { link_to "&nbsp;".html_safe, "leads/details/#{object.id}", :class=>:plus, :remote=>true } %>
        <%= l.column { object.age } %>
        <%= l.column { object.contact.complete_name } %>
        <%= l.column { object.contact.phones.blank? ? "-" : object.contact.phones.first.phone_number } %>
        <%= l.column { object.contact.emails.blank? ? "-" : object.contact.emails.first.email } %>
        <%= l.column { object.company.title } %>
        <%= l.options_column do %>
            <%= link_item 'Show', lead_path(object.id) %>
            <%= link_item 'Edit', edit_lead_path(object.id) %>
            <%= link_item 'New Note', "leads/#{object.id}/notes/new", :class=>"display-newxdoc", :id=>object.id %>
            <%= link_item 'Create Opportunity', new_lead_opportunity_path(object.id) %>
        <% end %>
    <% end %>