Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails helper method: Nested hash to nested HTML list

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?

like image 352
gjb Avatar asked Feb 18 '23 12:02

gjb


2 Answers

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
like image 150
Zach Kemp Avatar answered Feb 20 '23 17:02

Zach Kemp


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.

like image 45
SamuelLJohnson Avatar answered Feb 20 '23 17:02

SamuelLJohnson