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.
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.