I was looking for a quick way to build a hierarchical navigation tool in Ruby on Rails. There are good examples to build your own tree in Ruby when you use acts_as_tree and Single Table Inheritance etc., but nothing very easy to do otherwise.
A little bit of searching led me to the railstree plugin (source). Unfortunately, the documentation and any notes I found were in Portuguese so I thought it will be useful to illustrate how to use it in English. You could just try the Google translation of the Portuguese page and get most of what I have below (and get a few laughs at the translation while doing it).
First download railstree plugin from one of the rubyforge mirrors and install the files into the appropriate directories. You might need to hack the RAILS_ROOT path in the install.rb to correctly install the files using the install.rb script. You could also just repeat what the script is doing by manually copying the files yourself. (The script/plugin method of installation mentioned in the homepage of railstree did not work for me).
Now you have to include the javascript standard files with the two additional lines at the bottom in your view.rhtml file:
(Note that I have not figured out how to show HTML tags inside my snippets so remember to add < and > to HTML tags in the snippets.)
< %= javascript_include_tag ‘prototype’ %>
< %= javascript_include_tag ‘effects’ %>
< %= javascript_include_tag ‘dragdrop’ %>
< %= javascript_include_tag ‘controls’ %>
< %= stylesheet_link_tag ‘tree’ %>
< %= javascript_include_tag ‘tree’ %>
You could always do a simpler version
< %= javascript_include_tag :defaults %>
< %= stylesheet_link_tag ‘tree’ %>
< %= javascript_include_tag ‘tree’ %>
Now you need to construct the tree in your controller. In my RoR project, the navigation tree is displayed in all controllers so I just put it in app/controllers/application.rb:
def create_tree_menu
if (@menutree)
return @menutree
end
@menutree = Tree.new("MyTree", "")
Parent.find(:all).each do |p|
pnode = Node.new
#(p.name, "/parents/show/#{p.id}")
pnode.link_to_remote p.name, {:base => self, :update => 'content',
:url =>{:controller => 'parents', :action => 'show', :id => "#{p.id}"}}
Child.find(:all, :conditions => "parent_id=#{p.id}").each do |c|
cnode = Node.new
#(c.name, "/children/show/#{c.id}")
cnode.link_to_remote c.name, {:base => self, :update => 'content',
:url =>{:controller => 'children', :action => 'show', :id => "#{c.id}"}}
pnode < < cnode
end
@menutree << pnode
end
@menutree
end
Now the explanation: The above code is constructing a navigation tree for a two-level hierarchy with a Parent Model and a Child Model. The Child Model has a parent_id attribute linking to the Parent table. The default action when someone clicks on any node on the tree is to call the show Action for that node. This is done by setting the pnode.link_to_remote and cnode.link_to_remote with the parameters as above. The update attribute points to the div of the view to be updated when the node is clicked in the tree. The base attribute is very important since that reference is used internally in the railstree plugin to callback so it better be not null.
I did not try modifying the images for the icons of the tree but you can do that by using the pnode.icon method. You can also set the open/close status of the node by calling the open method but we will come back to that later.
Now how do you show this in your view?
div id="dtree"
< % if (@menutree) %>
< %= @menutree.to_s %>
< % end %>
/div
div id="content"
Your ERb code
/div
Viola! Now you have the tree just like it shows on the homepage of the project. For an additional tweak, I managed to figure out how to show/hide the tree by clicking on a link. Just add this at the top of the code snippet above (check to make sure the div ids are the same, dtree in the example here):
script type="text/javascript"
//< ![CDATA[
function hideTreeMenu(){
Element.toggle('dtree');
if (Element.visible('dtree')) {
Element.setStyle('content', {marginLeft: "210px"});
} else {
Element.setStyle('content', {marginLeft: "5px"});
};
};
//]]>
/script
a
href="#" onclick="hideTreeMenu();">Show/Hide Tree
/a
The extra adjusting of the Element.setStyle is to just move my content into the space where I had my tree so hiding actually gives more space in the code for the content div. If you do not care about moving the content to reclaim space, you could just do a Element.toggle call. Note that I am a javascript noobie so others might have better ways of doing this.
Also, since I am a javascript and cookies noobie I could not figure out how to set a cookie to save/retrieve the state of the tree when someone actually clicks on a node in the tree. In the current form, when someone clicks on any node in the tree, it updates the content div and then curls up and shows a fully collapsed tree than the last state. I do not think I managed to even save/retrieve the Show/Hide state before and after a click on a node. Other than telling me to try cookies, let me know if you can explain how to do it.
Thanks for its translation !!
(Note that I have not figured out how to show HTML tags inside my snippets so remember to add to HTML tags in the snippets.)
Umm, wouldn’t using &_lt; and &_gt; (w/o the underscores) print the lesser-than and greater-than symbols respectively? Like so: < and >.
Note: The line caught my eye when I stumbled upon this while browsing randomly, not a Ruby user
Yeah, the &_lt; (without the undescores) so not seem to work inside a code /code block!