How to show a Tree (TreeView) in a Rails view?

Well there seems to be no one-way for displaying a tree in Rails. But it has to be some javascript/AJAX doing its thing with CSS on HTML lists. So I took a look at YUI Tree (the first thing that google threw up) but it felt bloated and the learning curve was a bit too steep. Some time later.. I found a tree plugin for JQuery and also found that there is a RailsCast to use JQuery with Rails (so I knew its possible to interop).
I've been wanting to look at what JQuery is - with the massive good-will it is building up. Did a couple of tutorials 1 and 2 on their site to get started. Downloaded the jquery.js file (1 file for all the goodness !! awesome).
Next it's time to download the zip for the excellent JQuery tree plugin by Jorn Zaefferer - incidentally its the same person who wrote the second tutorial. I felt better about my chances for success.

I'm assuming you have a Rails app and the code for your hieararchy all done to this point. It's going to be a simple tree showing the hierarchy (no fancy AJAX callbacks or delayed loading of children... although I think its supported)

Unzip the treeview plugin folder v1.4 (it has a version of jquery bundled too inside the lib subfolder). I see
  • jquery.treeview.XXX.js (the javascript files) : Moved them to my public\javascripts folder within my Rails project folder. Also copied the lib subfolder here.
  • jquery.treeview.css (the CSS stylesheet) : Moved them to my public\stylesheets folder
  • images subfolder : Moved the images in this folder to a new subfolder public\images\jquery.treeview.images
Now the CSS file has references to the images. So do a find-replace to make sure all the images are reachable in their new locations; "url(images/" becomes "url(../images/jquery.treeview.images/"

I have a show action on my controller that creates @root_node which is a typical Node object with a Children attribute - an array of child nodes. So now we need something to render it as an hierarchical HTML list - a Rails helper method for your controller



  def display_segment( node )

html = "<li>"
node_class = node.children.length == 0 ? "file" : "folder"
html << "<span class=\"#{node_class}\">#{h(node.to_s)} </span>"
html << "<ul id=\"children_of_#{h(node.sid)}\">"
node.children.each{|child_node|

html << display_segment( child_node )
}
html << "</ul></li>"
end



Let's also take a look at the view show.html.erb



<html>
<head>
<h3> You are viewing <%= @data_file_path %> </h3>

<title><%= controller.action_name %></title>
<%= stylesheet_link_tag 'appstylesheet'%>
</head>

<body>
<ul id="my_tree" class="filetree">
<%= display_segment(@root_node) %>
</ul>

</body>
</html>


Things to note
  • First we need an enclosing ul block with css class set to filetree. This would be picked up our custom javascript snippet to JQuery-magically turn into a tree. So we need to make a note to give it a good id - here my_tree
  • Next the content is rendered via our recursive helper function. Children of a node are in a nested ul block. Each leaf-node is a li item and has css class as "file". parents have css class as "folder"
So now it should look something like this.



Now the stage is set for JQuery to do its thing. First lets introduce these 2 lines to the head tag of our view.


<%= javascript_include_tag 'lib/jquery', 'jquery.treeview', 'custom' %>
<%= stylesheet_link_tag 'jquery.treeview.css' %>


So we say we want to use the jquery javascript file, followed by the treeview plugin and finally our own custom javascript file that we'll write next.
We also want to use the treeview CSS stylesheet. You can do a "View Source" in firefox to see what the translated HTML looks like - std. stuff.

We're nearing the end here - Our \public\javascripts\custom.js file to sprinkle some JQuery dust on this thing


$(document).ready( function() {
$("#my_tree").treeview();
} );


Here we're giving a function to be executed when the page/document is ready. This function finds our top-level list element with id = "my_tree" and calls the treeview() method on it.



Victory Jig time!

3 comments:

  1. It might be noted for those who are new to both rails and/or JQuery that the same could be accomplished without using the id attribute of the unordered list.
    Using the class is nice too if you have multiple treeview's or any type of other function that needs to be called multiple times and improves visual performance.

    <ul class="filetree">

    </ul>

    ---- include tag ----
    $(document).ready( function() {
    $(".filetree").treeview();
    } );

    ReplyDelete
  2. If you can't see the icons and lines but the list seems to behave correctly otherwise, then there's a difficulty locating the images.

    To fix place a '/' before the images path in the jquery.treeview.css file.
    url(images/jquery.treeview.images/treeview-default.gif)
    becomes
    url(/images/jquery.treeview.images/treeview-default.gif)

    ReplyDelete