Search code examples
ruby-on-railsrecursionancestry

The best way to display Windows Explorer-type tree view with Rails 4


I've managed to produce some recursive code to draw a Windows Explorer-type tree as an HTML table, based on a Rails model that uses the Ancestry gem. I'd like to know whether there is a more elegant method than string concatenation to draw the table. I tried (thinking that surely there should be) but couldn't get a call to a second erb partial to work.

Here is my code, starting with the controller:

def index
  @trees = Tree.all
  @root = Tree.first.root
  @table_string = ''
end

Here is the helper:

module TreesHelper

def draw_branch(t)
    ht = ""
    ht << '<tr>'
    ht << "\n\t\t\t"
    ht << '<td class="data" align="left" style="padding-left: '
    ht << ((t.ancestry_depth+1)*15).to_s
    ht << 'px"><img src="assets/icons/folder.gif" class="icon_folder" width="18">'
    ht << (link_to t.name, t).to_s
    ht << '</td>'
    ht << "\n\t\t\t"
    ht << '<td class="data" align="right">'
    ht << number_to_currency(t.value, precision: 0).to_s
    ht << '</td>'
    ht << "\n\t\t\t"
    ht << '<td class="data">'
    ht << t.note
    ht << '</td>'
    ht << "\n\t\t\t"
    ht <<   '<td class="data" align="right">'
    ht << (t.id).to_s
    ht << '</td>'
    ht << "\n\t\t\t"
    ht << '<td class="data" align="right">'
    ht << (t.ancestry_depth).to_s
    ht << '</td>'
    ht << "\n\t\t\t"
    ht << '<td class="data" align="right">'
    ht << (t.has_children?).to_s
    ht << '</td>'
    ht << "\n\t\t\t"
    ht << '<td class="data" align="right">'
    ht << t.parent_id.to_s
    ht << '</td>'
    ht << "\n\t\t"
    ht << '</tr>'
    ht << "\n\t\t"
end

def tree_table(t)
    # render partial: "branch_as_row", object: t    Doesn't work whereas the next line does...
    @table_string << draw_branch(t)
    t.children.each do |tt|
        tree_table(tt)
    end
    @table_string.html_safe
end
end

Here is the original partial which calls the helper function:

<table>
  <thead>
    <tr>
        <th>Folder</th>
        <th>Value</th>
        <th>Note</th>
        <th>ID</th>
        <th>Depth</th>
        <th>Has Children?</th>
        <th>Parent</th>
    </tr>
  </thead>
  <tbody>
    <%= tree_table(@root) %>
  </tbody>
</table>

FWIW, here is the partial that doesn't work (_branch_as_row.html.erb):

<tr data-level="<%= t.depth+1%>">
  <td class="data" align="left" style="padding-left: <%=((t.ancestry_depth+1)*10).to_s%>px"><img src="assets/icons/folder.gif" width="14"></td>
  <td class="data" align="right"><%= t.id %></td>
  <td class="data" align="right"><%= t.ancestry_depth %></td>
  <td class="data" align="right"><%= t.has_children? %></td>
  <td class="data"><%= link_to t.name, t %></td>
  <td class="data" align="right"><%= number_to_currency(t.value, precision: 0) %></td>
  <td class="data"><%= t.note %></td>
  <td class="data" align="right"><%= t.parent_id %></td>
</tr>

It produces a "wrong number of arguments (0 for 1..2)" error.


Solution

  • This may not be as good as it gets but the effort of writing the question up then reading the official docs again made me think differently and I have answered my own question.

    Here is my view now:

    <table id="tree" border="1" cellspacing="0" cellpadding="6" class="table table-hover table-bordered">
        <thead>
            <tr data-level="header" class="header">
                <th>Folder</th>
                <th>ID</th>
                <th>Depth</th>
                <th>Has Children?</th>
                <th>Folder</th>
                <th>Value</th>
                <th>Note</th>
                <th>Parent</th>
            </tr>
        </thead>
        <tbody>
    <%= render partial: "branch_as_row", object: @root, as: :t %>
        </tbody>
    </table>
    

    and here is the (slightly improved) partial which calls itself:

    <tr data-level="<%= t.depth+1%>">
      <td class="data" align="left" style="padding-left: <%=((t.ancestry_depth+1)*15).to_s%>px"><img src="assets/icons/folder.gif" width="18" class="icon_folder"><%= link_to t.name, t %></td>
      <td class="data" align="right"><%= t.id %></td>
      <td class="data" align="right"><%= t.ancestry_depth %></td>
      <td class="data" align="right"><%= t.has_children? %></td>
      <td class="data"><%= link_to t.name, t %></td>
      <td class="data" align="right"><%= number_to_currency(t.value, precision: 0) %></td>
      <td class="data"><%= t.note %></td>
      <td class="data" align="right"><%= t.parent_id %></td>
    </tr>
    <% if t.has_children? %>
      <%= render partial: "branch_as_row", collection: t.children, as: :t %>
    <% end %>
    

    Much better than the ugly string concatenation method! The breakthrough was in understanding that a partial can call itself, and also using the proper syntax to pass variables into a partial.