4

I am trying to write a Rails helper method to convert a nested hash into a nested HTML list.

For example:

{
  :parent => "foo",
  :children => [
    {
      :parent => "bar",
      :children => [
        {
          :parent => "baz",
          :children => []
        }
      ]
    }
  ]
}

should become:

<ul>
  <li>foo</li>
  <ul>
    <li>bar</li>
    <ul>
      <li>baz</li>
    </ul>
  </ul>
</ul>

The hash may have any number of levels, and any number of parents per level.

What is the best way to achieve this please?

1
  • Render a partial that takes the data and renders the same partial for the sub-items. Commented Feb 15, 2013 at 23:10

2 Answers 2

7

You can make a recursive method to render to hash to a nested set of lists. Place this in your relevant helper:

def hash_list_tag(hash)
  html = content_tag(:ul) {
    ul_contents = ""
    ul_contents << content_tag(:li, hash[:parent])
    hash[:children].each do |child|
      ul_contents << hash_list_tag(child)
    end

    ul_contents.html_safe
  }.html_safe
end
Sign up to request clarification or add additional context in comments.

5 Comments

I've created a helper method as you said and inserted this erb code <div> <% hash_list_tag Tag.hash_tree do |tag| %> <%= link_to tag.name, tag_path(tag.name) %> <% end %> </div> in index.html.erb file but no list is generated. So far, I've created Post and Tag models and used Closure Tree to create Tag hierarchies.
Thanks @Zach kemp for your post. My question is why we can not use "div" instead of "ul".
@Gagan, you can use div if that's what you need. This particular question asked about ul.
@ZachKemp When i use div instead of ul. it does not work properly. <div> <li>foo</li> </div> <div> <li>bar</li> </dv> <div> <li>baz</li> </div> This is the output when i use div.
notice that you dont actually need this html = assignment
1

Zach Kemp's answer very effectively addresses the question. If you are looking for something a bit more generic (a nested hash for which you will not know the key names), as I was, the following module may be helpful (also at https://github.com/sjohnson/auto_hash_display with more details):

module HashFormatHelper
  # These methods add classes to the HTML structure that are defined in Bootstrap (and can be defined for other CSS frameworks)
  def format_hash(hash, html = '')
    hash.each do |key, value|
      next if value.blank?
      if value.is_a?(String) || value.is_a?(Numeric)
        html += content_tag(:ul, class: 'list-group') {
          ul_contents = ''
          ul_contents << content_tag(:li, content_tag(:h3, key.to_s.underscore.humanize.titleize), class: 'list-group-item')
          ul_contents << content_tag(:li, value, class: 'list-group-item')

          ul_contents.html_safe
        }
      elsif value.is_a?(Hash)
        html += content_tag(:ul, class: 'list-group') {
          ul_contents = ''
          ul_contents << content_tag(:li, content_tag(:h3, key.to_s.underscore.humanize.titleize), class: 'list-group-item')
          inner = content_tag(:li, format_hash(value), class: 'list-group-item')
          ul_contents << inner

          ul_contents.html_safe
        }
      elsif value.is_a?(Array)
        html += format_array(value)
      else
        Rails.logger.info "Unexpected value in format_hash: #{value.inspect}"
        Rails.logger.info "value type: #{value.class.name}"
      end
    end
    html.html_safe
  end

  def format_array(array, html = '')
    array.each do |value|
      if value.is_a?(String)
        html += content_tag(:div, value).html_safe
      elsif value.is_a?(Hash)
        html += format_hash(value)
      elsif value.is_a?(Array)
        html += format_array(value)
      else
        Rails.logger.info "Unexpected value in format_array: #{value.inspect}"
        Rails.logger.info "value type: #{value.class.name}"
      end
    end
    html
  end
end

This code can also be used to display XML by setting the hash value equal to Hash.from_xml(your_xml_data) and then passing that in to format_hash(hash).

Please note that the from_xml method may strip off XML tag attributes, so it works best for XML that doesn't have attributes.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.