Skip to content

Nested HTML from the arrange method

Joost Baaij edited this page Aug 24, 2018 · 2 revisions

Recursively render a Rails partial

You will need a helper:

module AncestryHelper
  # Recursively render a partial from an Ancestry arranged subtree.
  def arranged_tree_table_rows(tree)
    tree.each do |node, children|
      concat render partial: 'tree_node', object: node
      arranged_tree_table_rows(children) if children.present?
    end
  end
end

As you would expect, put the subtree in a controller:

class SomeController < ApplicationController
  def index
    @subtree = TreeNode.find_by_name('Crunchy').subtree.arrange
  end
end

and render it all in the corresponding view:

<% arranged_tree_table_rows(@subtree) %>

list

If you have a need to render your ancestry tree of any depth you can use the following helper to make it so. The Helper takes some options, mostly for styling. There is an option for sorting the tree by the current level (or group) we are in.

Note: This helper should not care about filtering or children depth. That should be handled via the normal ancestry methods before arrange() is called.

Secondary Note: This helper also assumes you are using the has_ancestry :cache_depth => true option. If you are not using this, remove the references in the helper to ancestry_depth.

#### Configuration options
:list_type # the type of list to render (ul or ol)
:list_style # this is used for setting up some pre-formatted styles. Can be removed if not needed. 
:ul_class # applies given class(es) to all parent list groups (ul or ol)
:ul_class_top # applies given class(es) to parent list groups (ul or ol) of depth = 0
:ul_class_children # applies given class(es) to parent list group (ul or ol) of depth > 0
:li_class # applies given class(es) to all list items (li)
:li_class_top # applies given class(es) to list items (li) of depth = 0
:li_class_children # applies given class(es) to list items (li) of depth > 0
:sort_by # sort the hash by attributes of your ancestry object e.g. [:name] or [:order, :name]

Helper

# app/helpers/ancestry_helper.rb
module AncestryHelper
  # arranged as tree expects 3 arguments. The hash from has_ancestry.arrange() method, 
  # options, and a render block
  def arranged_tree_as_list(hash, options = {}, &block)

    options = {
      :list_type            => :ul,
      :list_style           => '', 
      :ul_class             => [],
      :ul_class_top         => [],
      :ul_class_children    => [],
      :li_class             => [],
      :li_class_top         => [],
      :li_class_children    => [],
      :sort_by              => []
    }.merge(options)

    # setup any custom list styles you want to use here. An example is excluded
    # to render bootstrap style list groups. This is used to keep from recoding the same
    # options on different lists
    case options[:list_style]
      when :bootstrap_list_group
        options[:ul_class] << ['list-group']
        options[:li_class] << ['list-group-item']
    end
    options[:list_style] = ''

    output = ''

    # sort the hash key based on sort_by options array
    unless options[:sort_by].empty?
      hash = Hash[hash.sort_by{|k, v| options[:sort_by].collect {|sort| k.send(sort)} } ]
    end

    current_depth = 0
    # and here... we... go...
    hash.each do |object, children|

        li_classes = options[:li_class]  
              
        if object.depth == 0
          li_classes += options[:li_class_top]
        else
          li_classes += options[:li_class_children]
        end 
               
        if children.size > 0
          output << content_tag(:li, capture(object, &block) + arranged_tree_as_list(children, options, &block).html_safe,  :class => li_classes)
        else
          output << content_tag(:li, capture(object, &block), :class => li_classes).html_safe
          current_depth = object.depth
        end

    end

    unless output.blank?
      
      ul_classes = options[:ul_class]
      
      if current_depth == 0
        ul_classes += options[:ul_class_top]
      else
        ul_classes += options[:ul_class_children]
      end      
      
      output = content_tag(options[:list_type], output.html_safe, :class => ul_classes)
    end
    
    return output.html_safe
     
  end

end

How to use in view

# Normally this variable would be set from the controller, but for examples sake
# Lets pretend we have a Category model that has_ancestry
@categories = Category.where({:active => true}).arrange

# simple ul > li structure
<%= arranged_tree_as_list(@categories) do |category| %>
    # stuff that goes inside the <li></li> tags
    <%= link_to category.name, category_path(category) %>
<% end %>
     
# use bootstrap styling, as setup in the helper
<%= arranged_tree_as_list(@categories, {:list_style => :bootstrap_list_group}) do |category| %>
    # stuff that goes inside the <li></li> tags
    <%= link_to category.name, category_path(category) %>
<% end %>

# sort by category name and then id
<%= arranged_tree_as_list(@categories, {:sort_by => [:name, :id]}) do |category| %>
    # stuff that goes inside the <li></li> tags
    <%= link_to category.name, category_path(category) %>
<% end %>