Howto use the Ext JS Treeview (Ext.tree) with Ruby on Rails

ExtJS Tree Sample ScrenshotIn this little tutorial I am going to show you, how to connect a Ext.tree component (from the remarkable Ext JS Javascript framework) to a Ruby on Rails backend.

For starters, we need to download and extract the Ext framework to public/ext in our Rails project folder. We are going to use Rails 2.1.x for this Tutorial. That's not a particular requirement - but you would have to adapt certain Rails 2 concepts should you be using an older version.

To model a tree in Rails, we are going to use the acts_as_nested_set plugin - install it using script/plugin install acts_as_nested_set now.

To demonstrate the Ext.tree I will be using a Category model, having a root category with recursive sub-categories. We can use the Rails resource generator to initialize all neccessary files for us:

script/generate resource Category parent_id:integer lft:integer rgt:integer text:string

As you can see, each of our categories is just carrying a text attribute to store its name and the required attributes for the nested set (parent_id, lft, rgt).

To use the Category model, we just have to add the acts_as_nested_set decorator to the Category class, so it looks like this:

class Category < ActiveRecord::Base
  # For Rails 2.1: override default of include_root_in_json
  # (the Ext.tree.TreeLoader cannot use the additional nesting)
  Category.include_root_in_json = false if Category.respond_to?(:include_root_in_json)


We can now create some sample data using script/console (you did run rake db:migrate already, didn't you? ;-) ):

r = Category.create(:text => 'Frameworks')
r.add_child(c1 = Category.create(:text => 'Ruby on Rails'))
c1.add_child(Category.create(:text => 'Model'))
c1.add_child(Category.create(:text => 'View'))
c1.add_child(Category.create(:text => 'Controller'))
r.add_child(c2 = Category.create(:text => 'Ext JS'))
c2.add_child(c21 = Category.create(:text => 'tree'))
c21.add_child(Category.create(:text => 'TreePanel'))
c21.add_child(Category.create(:text => 'AsyncTreeNode'))
c21.add_child(Category.create(:text => 'TreeLoader'))

On to our CategoriesController. We just need an index method that will initially deliver a (rather static) index.html.erb view. The same method can then be used to provide JSON data to be consumed by Ext.tree like this:

class CategoriesController < ApplicationController
  def index(id = params[:node])
    respond_to do |format|
      format.html # render static index.html.erb
      format.json { render :json => Category.find_children(id) }

We are giving the index method a node query parameter (assigned to a id variable) that Ext will later use to dynamically request sub-trees as they are extended in the UI.

Obviously we will have to add more code to our Category model as it currently does not respond to the find_children method. Let's use the following code to provide it - when no id or zero is given, the root node(s) of the tree will be returned:

# add to model/category.rb
  def self.root_nodes
    find(:all, :conditions => 'parent_id IS NULL')

  def self.find_children(start_id = nil)
    start_id.to_i == 0 ? root_nodes : find(start_id).direct_children

After starting script/server we can now fire up our browser and request http://localhost:3000/categories.json. Comparing the generated JSON with the format expected by Ext.tree.TreeLoader reveals that each element should supply a boolean attribute called leaf telling the tree if it can be further expanded or is a final leaf of the tree. To keep our controller skinny as we like it, we will add further methods to the Category model enabling its standard to_json method to also supply the required leaf attribute.

# add to model/category.rb
  def leaf
    unknown? || children_count == 0

  def to_json_with_leaf(options = {})
    self.to_json_without_leaf(options.merge(:methods => :leaf))
  alias_method_chain :to_json, :leaf

Now that we have the complete backend code in place, we just need to put some Ext Javascript into categories/index.html.erb to try the tree live:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="" xml:lang="en" lang="en">
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Ext.tree with Ruby on Rails Example</title>
  <%= stylesheet_link_tag "../ext/resources/css/ext-all.css" %>
  <%= javascript_include_tag :defaults %>
  <%= javascript_include_tag "../ext/adapter/prototype/ext-prototype-adapter.js" %>
  <%= javascript_include_tag "../ext/ext-all.js" %>
  <div id="category-tree" style="padding:20px"></div>
  <% javascript_tag do -%>
      // create initial root node
      root = new Ext.tree.AsyncTreeNode({
        text: 'Invisible Root',
      // create the tree
      new Ext.tree.TreePanel({
        loader: new Ext.tree.TreeLoader({
        root: root,
      // expand invisible root node to trigger load
      // of the first level of actual data
  <% end -%>

Now it is time to look at the final view (http://localhost:3000/categories). If you use Firebug or something similar you can track the AJAX requests made by Ext.tree to populate the tree dynamically when you expand nodes. Nice, huh?

Recommend Martin Rehfeld on Working With RailsIf you like this tutorial, please consider recommending me on Working with Rails. Thank you!

Related Posts