Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Acts As Tree Category Display (See related posts)

You should have a categories table with a parent_id field. Root categories should have the parent_id of 0.

I modified this post:
http://www.bigbold.com/snippets/posts/show/296

because the first one worked if you did stuff in your view to make the root categories come up, but I agree with the commenter that things should be contained to the helper - however I've managed to break the DRY rule in doing so. If anyone has any suggestions for a better way around this I'd love to hear it, so please comment. So anyway.. this should list your root categories ONLY and NOT your other sub-cats as well so that you have a very pretty little tree.

Slap this in your view:

<%= display_categories(@categories) %>


And put this in a helper file. You'll need it in the application helper most likely since you'll want to call it from multiple views.

      def display_categories(categories)
	    	   ret = "<ul>"
	   for category in categories
		   if category.parent_id == 0
			ret += "<li>"
			ret += link_to category.name
			ret += find_all_subcategories(category)
			ret += "</li>"
		   end
		   
	   end
	   ret += "</ul>"
    end
    
   def find_all_subcategories(category)
    if category.children.size > 0
      ret = '<ul>'
      category.children.each { |subcat| 
        if subcat.children.size > 0
          ret += '<li>'
          ret += link_to h(subcat.name), :action => 'edit', :id => subcat
          ret += find_all_subcategories(subcat)
          ret += '</li>'
        else
          ret += '<li>'
          ret += link_to h(subcat.name), :action => 'edit', :id => subcat 
          ret += '</li>'
        end
        }
      ret += '</ul>'
    end
  end

Comments on this post

coda posts on May 07, 2006 at 04:29
This will cause a N+1 query condition, though, as none of the child nodes have been pulled from the database.

To make this a single database call (and speed things up by quite a bit), you need to do something like this:

  @categories = Category.find( :all, :conditions => 'parent_id IS NULL', :include => [ { :children => [ :children, :parent ] }, :parent ] )


This will get the first two levels of categories. Either that or you select all categories and handle the hierarchy yourself based strictly on id and parent_id values.
mrdabomb posts on May 28, 2006 at 19:41
If you instead use the following method, you can recurse infinitely into your tree:
def display_tree_recursive(tree, parent_id)
  ret = "<ul>"
  tree.each do |node|
    if node.parent_id == parent_id
      ret += "<li>"
      ret += link_to node.title
      ret += display_tree_recursive(tree, node.id)
      ret += "</li>"
    end
  end
  ret += "</ul>"
end

Just call it in your view with the 0 parent_id:
<%= display_tree_recursive(@pages, 0) %>
mrdabomb posts on May 28, 2006 at 19:55
Oh, and to further expand upon this, use a block to pass how you want each line formatted:
def display_tree_recursive(tree, parent_id)
  ret = "<ul>"
  tree.each do |node|
    if node.parent_id == parent_id
      ret += "<li>"
      ret += yield node
      ret += display_tree_recursive(tree, node.id) { |n| yield n }
      ret += "</li>"
    end
  end
  ret += "</ul>"
end

And call it with a block:
<%= display_tree_recursive(@pages, 0) { |n| n.title } %>
littleguru posts on Jun 07, 2006 at 18:42
in the above display_tree_recursive using a block the following line:

ret += display_tree_recursive(tree, node.id) { |n| yield n }


should be something along these lines to avoid invalid xhtml output:

ret += display_tree_recursive(tree, node.id) { |n| yield n } unless node.children.size == 0

dudz.josh posts on Aug 12, 2006 at 13:33
in the above recursive method the line needs to be changed to this to print all leaves.

ret += display_tree_recursive(node.children, node.id) { |n| yield n } unless node.children.empty?
dudz.josh posts on Aug 13, 2006 at 04:19
My bad,

You can get that recursive method working so you can just pass in call to the database.

1 Query with eager loading:


@tree = Model.find(:all, :include => [ :children ])


Application Helper Method from above:


def display_tree_recursive(tree, parent_id)
ret = "\n<ul>"
tree.each do |node|
if node.parent_id == parent_id
ret += "\n\t<li>"
ret += yield node
ret += display_tree_recursive(tree, node.id) { |n| yield n } unless node.children.empty?
ret += "\t</li>\n"
end
end
ret += "</ul>\n"
end


Call from the view


<%= display_tree_recursive(@tree, nil) { |node| node.name } %>


The second paremter to the display_tree_recursive method is just the parent id you want to start from, my root doesn't have a parent_id so I just use the value nil.

You need to create an account or log in to post comments to this site.


Click here to browse all 5147 code snippets

Related Posts