views:

256

answers:

3

Hey there,

I am trying to build a simple nested html menu using HAML and am not sure how to go about inserting the elements with the correct indentation, or the general best way to build nested trees. I would like to be able to do something like this, but infinitely deep:

- categories.each_key do |category|
    %li.cat-item{:id => "category-#{category}"}
        %a{:href => "/category/#{category}", :title => "#{category.titleize}"}
            = category.titleize

It feels like I should be able to accomplish this pretty easily without resorting to writing the tags by hand in html, but I'm not the best with recursion. Here is the code I've currently come up with:

View Helper

def menu_tag_builder(array, &block)
  return "" if array.nil?
  result = "<ul>\n"
  array.each do |node|
    result += "<li"
    attributes = {}
    if block_given?
      text = yield(attributes, node)
    else
      text = node["title"]
    end
    attributes.each { |k,v| result += " #{k.to_s}='#{v.to_s}'"}
    result += ">\n"
    result += text
    result += menu_tag_builder(node["children"], &block)
    result += "</li>\n"
  end
  result += "</ul>"
  result
end

def menu_tag(array, &block)
  haml_concat(menu_tag_builder(array, &block))
end

View

# index.haml, where config(:menu) converts the yaml below
# to an array of objects, where object[:children] is a nested array
- menu_tag(config(:menu)) do |attributes, node|
 - attributes[:class] = "one two"
 - node["title"]

Sample YAML defining Menu

menu:
  -
    title: "Home"
    path: "/home"
  -
    title: "About Us"
    path: "/about"
    children: 
      -
        title: "Our Story"
        path: "/about/our-story"

Any ideas how to do that so the output is like this:

<ul>
  <li class='one two'>
    Home
  </li>
  <li class='one two'>
    About Us
  </li>
</ul>

...not like this:

<ul>
<li class='one two'>
Home</li>
<li class='one two'>
About Us</li>
</ul>

... and so it's properly indented globally.

Thanks for the help, Lance

A: 

It's because you send a pur HTML by your helper. The indentation become with HAML. You can can generate some HAML in your helper.

shingara
It's not actually possible to generate Haml code in helpers and have it be rendered into HTML dynamically (unless you manually invoke the Haml compiler).
nex3
+1  A: 

The trick to nicely-indented, Ruby-generated Haml code is the haml_tag helper. Here's how I'd convert your menu_tag method to using haml_tag:

def menu_tag(array, &block)
  return unless array
  haml_tag :ul do
    array.each do |node|
      attributes = {}
      if block_given?
        text = yield(attributes, node)
      else
        text = node["title"]
      end
      haml_tag :li, text, attributes
      menu_tag_builder(node["children"], &block)
    end
  end
end
nex3
A: 

Awesome, @shingara's hint put me in the right direction :). This works perfectly:

def menu_tag(array, &block)
  return "" if array.nil?
  haml_tag :ui do
    array.each do |node|
      attributes = {}
      if block_given?
        text = yield(attributes, node)
      else
        text = node[:title]
      end
      haml_tag :li, attributes do
        haml_concat text
        menu_tag_builder(node[:children], &block)
      end
    end
  end
end

If somebody can make that even shorter, or make it more easy to customize the attributes on the nested nodes, I'll mark that as correct instead of this.

Cheers.

viatropos