Ajaxified Drag Drop Tree in RoR
CASE STUDY
I m providing a very generalized use case where the tree fits in a good position. Here it is…
Consider a model Item, a controller Items. Item model is using a fabulous acts_as_tree and we are going to put a seed for Item to grow it in an ajax tree
… Ok no more non-code talk. So, lets start the code now…
==========================================================
I have also incorporated the code into a sample application which you can directly check out and try the tree yourself if you find it a
headache to add the following code in a number of described files.
So, here is the Sample Tree Application
or you can try to code yourself as…
Create a sample rails application say treeapp by running
rails treeapp
from the command prompt.
Now simply change your directiry into just created treeapp and make sure that you are in the directory treeapp
Now configure the database settings for this application by modifying the file /config/database.yml as …
development: adapter: mysql database: tree_dev username: root password: root host: localhost
Here it simply shows that you have a mysql database named tree_dev and a user root with password root can access this database. So make sure about these settings.
From the command prompt in application root(i.e. you are in the directory treeapp) run this command to generate the model Item…
treeapp> ruby script/generate model item
Add the following code to the file app/models/itme.rb
class Item < ActiveRecord::Base
acts_as_tree
validates_presence_of :name
attr_accessor :style
def self.roots
self.find(:all, :conditions=>["parent_id = ?", 0])
end
def level
self.ancestors.size
end
end
This simply shows that you should have a table named tems in your database…
so why we havnt mentioned it earlier ?
Thats the thing which will make you feel an agile web development.
Now look at the directory db/migrateand a you will find a file named as db/migrate/001_create_items.rb
Add the following code to this file 001_create_items.rb
Here we are creating our database table and also adding some initial data to work with.
class CreateItems < ActiveRecord::Migration
def self.up
create_table "items", :force => true do |t|
t.column "name", :string
t.column "created_at", :datetime
t.column "parent_id", :integer, :default => 0, :null => false
end
%w(item1 item2 item3 item4 item5).each do |name|
parent = Item.new(:name=>name)
parent.save
Item.create(:name=>name+".1", :parent_id=>parent.id)
Item.create(:name=>name+".2", :parent_id=>parent.id)
Item.create(:name=>name+".3", :parent_id=>parent.id)
end
end
def self.down
drop_table :items
end
end
Now from the command line from the root of your application run the following command to have a table named Item in your database with some initial data.
treeapp> rake db:migrate
Before we start handling our views and controller part just have a smart small image named as drag.gif in your public/images directory that we will use as a handle to drag the nodes. So, now you can see a small image at public/images/drag.gif, cool !.
Now from the command line from the root of your application run the following command to create a controller …
treeapp> ruby script/generate controller items show
Make sure that now you have the files app/controllers/items_controller.rb and app/views/items/show.rhtml.
Add the following code in the file app/controllers/items_controller.rb
class ItemsController < ApplicationController
def show
@items = Item.find(:all)
@item = Item.find(:first)
# select according to your choice...
#this item will be selected node by default in the tree when it will first be loaded.
end
def display_clicked_item
# this action will handle the two way syncronization...all the tree nodes(items) will be linked
# to this action to show the detailed item on the left of the tree when the item is clicked
# from the tree
if request.xhr?
@item = Item.find(params[:id]) rescue nil
if @item
# the code below will render all your RJS code inline and
# u need not to have any .rjs file, isnt this interesting
render :update do |page|
page.hide “selected_item”
page.replace_html “selected_item”, :partial=>”items/item”, :object=>@item
page.visual_effect ‘toggle_appear’, “selected_item”
end
else
return render :nothing => true
end
end
end
def sort_ajax_tree
if request.xhr?
if @item = Item.find(params[:id].split(”_”).first) rescue nil
parent_item = Item.find(params[:parent_id])
render :update do |page|
@item.parent_id = parent_item.id
@item.save
@items=Item.find(:all)
page.replace_html “ajaxtree”, :partial=>”items/ajax_tree”, :object=>[@item,@items]
page.hide “selected_item”
page.replace_html “selected_item”, :partial=>”items/item”, :object=>@item
page.visual_effect ‘toggle_appear’, “selected_item”
end
else
return render :nothing => true
end
end
end
end
Add the following code in the file app/views/items/show.rhtml
<h2>Ajax Tree Application</h2> <div id=”ajaxtree” style=”width:40%;float:left;”> <%= render :partial=>’items/ajax_tree’, :object=>[@item,@items] %> </div> <div id=”selected_item”> <%= render :partial=>’items/item’, :object=>@item %> </div>
Add the following code in the file app/views/items/_item.rhtml
<% if @item %> <h2>Selected Item is <%=h @item.name%> </h2> <% else %> Item not found <% end %>
Add the following code in the file app/views/items/_ajax_tree.rhtml
<script type="text/javascript">
function toggleDiv()
{
Element.toggle('mytree');
Element.toggle('expanded');
Element.toggle('collapsed');
return false;
}
function showDrag()
{
var drag_images = $$('img.drag_image');
drag_images.all(function(value,index){return value.style.display='inline';});
Element.toggle('done');
Element.toggle('reorder');
return false;
}
function hideDrag()
{
var drag_images = $$('img.drag_image');
drag_images.all(function(value,index){return value.style.display='none';});
Element.toggle('done');
Element.toggle('reorder');
return false;
}
</script>
<style>
.mytree{padding:0 0 0 0px;}
.mytree li {padding:2 0 0 3px;}
.outer_tree_element{margin:0 0 0 10px;}
.inner_tree_element{margin:5px 0 0 10px;}
.mytree a{text-decoration:none; font-size:13px; color:black;}
.mytree a:hover{background-color:lightblue;}
.mytree label{font-weight:normal;}
.highlighted{background-color:lightblue;}
.normal{background-color:white;}
.drag_image{border:0px;}
</style>
<div id="mytree" class="mytree">
<% @ancestors = @item.ancestors.collect{|parent| parent.id} if @item.has_parent? %>
<% @items = Item.find(:all) %>
<%= get_tree_data(@items, 0){|n|
link_to_remote(n.name,
:url=>{:controller=>'items', :action=>'display_clicked_item', :id=>n.id},
:loading=>"Element.show('tree_indicator')",
:complete=>"Element.hide('tree_indicator')"
)}
%>
<% @items.each do |node| %>
<%= draggable_element node.id.to_s+'_tree_div',:revert=>true,:snap=>false, :handle=>"'#{node.id.to_s}_drag_image'" %>
<%= drop_receiving_element node.id.to_s+'_tree_div',
:accept=>'inner_tree_element',
:url=>{:controller=>'items',:action=>'sort_ajax_tree', :parent_id=>node.id,:id=>nil},
:loading=>"Element.show('sort_tree_indicator')",
:complete=>"Element.hide('sort_tree_indicator')"
%>
<% end %>
<%= image_tag 'indicator.gif', :id=>'tree_indicator', :style=>'display:none' %>
<%= image_tag 'indicator.gif', :id=>'sort_tree_indicator', :style=>'display:none' %>
</div>
<script type="text/javascript">
var selected_el = document.getElementById('<%=@item.id%>_tree_item');
selected_el.className='highlighted';
function toggleMyTree(id)
{
Element.toggle(id+'collapsed');
Element.toggle(id+'expanded');
Element.toggle(id+'children');
return false;
}
function toggleBackground(el)
{
// using collection proxies to change the background
var highlighted_el = $$("span.highlighted");
highlighted_el.all(function(value,index){return value.className='normal'});
el.className='highlighted';
selected_el = el;
return false;
}
function openMyTree(id)
{
Element.hide(id+'collapsed');
Element.show(id+'expanded');
Element.show(id+'children');
return false;
}
</script>
As you can see in the above file we have used some indicator and toggle images. So you will be required to have three more images in the directory public/images/.
Here is the small description about these images…
- An indicator image that will be displayed at the bottom of the tree whenever a tree node is clicked. You can select from a number of Ajax Inidicatorsavailable on the web. Select one indicator image and save in your app with the name indicator.gif. So, now make sure that you can see the image at public/images/indicator.gif
- Second, we need to have a small image with + sign. That will be used to toggle the tree. save it as public/images/collapsed.gif
- Similarly, an image with - sign. Save it as public/images/expanded.gif
We have to include the prototype and scriptaculous javascript libraries in the application.
So just manually create a layout file app/views/layouts/application.rhtml and add the following code in the file application.rhtml
<html>
<head>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= @content_for_layout %>
</body>
</html>
Now the last but the most importatnt…The recursion to obtain the tree.
Add the following code in the file app/helpers/application_helper.rb
module ApplicationHelper
def get_tree_data(tree, parent_id)
ret = "<div class='outer_tree_element' >"
tree.each do |node|
if node.parent_id == parent_id
node.style = (@ancestors and @ancestors.include?(node.id))? 'display:inline' : 'display:none'
display_expanded = (@ancestors and @ancestors.include?(node.id))? 'inline' : 'none'
display_collapsed = (@ancestors and @ancestors.include?(node.id))? 'none' : 'inline'
ret += "<div class='inner_tree_element' id='#{node.id}_tree_div'>"
if node.has_children?
ret += "<img id='#{node.id.to_s}expanded' src='/images/expanded.gif' onclick='javascript: return toggleMyTree(\"#{node.id}\"); ' style='display:#{display_expanded}; cursor:pointer;' /> "
ret += "<img style='display:#{display_collapsed}; cursor:pointer;' id='#{node.id.to_s}collapsed' src='/images/collapsed.gif' onclick='javascript: return toggleMyTree(\"#{node.id.to_s}\"); ' /> "
end
ret += " <img src='/images/drag.gif' style='cursor:move' id='#{node.id}_drag_image' align='absmiddle' class='drag_image' /> "
ret += "<span id='#{node.id}_tree_item'>"
ret += yield node
ret += "</span>"
ret += "<span id='#{node.id}children' style='#{node.style}' >"
ret += get_tree_data(node.children, node.id){|n| yield n}
ret += "</span>"
ret += "</div>"
end
end
ret += "</div>"
return ret
end
end
Now you can check the tree functionality at http://localhost:3000/items/show.. assuming that you are running your server on port 3000. njoy!!
26 Comments to "Source Code For Ajax Based Drag Drop Navigation Tree in Ruby on Rails - the tree works well with firefox and IE-6"
Please share your thoughts
Filed in: ajax, ajax tree, drag drop tree, navigation tree, rails, tree


Good
[...] This tree works very fine in my application and hope it will help u also. Check out the Source Code of the tree. [...]
Give me the source
Hi Source Required !!
Check out the Source Code
Hello
Thanks for the code
although i am still having problems adding it to my project
Showing app/views/items/_ajax_tree.rhtml where line #70 raised:
can’t convert Array into String
70:
Hi Rana !!
I am figuring it out where the problem is exactly by trying it in a new test application. I will post the corrected one soon.
can’t convert Array into String
Yes,I got the error too.
and:
Add the following code in the file app/views/items/_item.rhtml
Selected Item is
Item not found
should be:
Add the following code in the file app/views/items/_item.rhtml
Selected Item is
Item not found
Add the following code in the file app/views/items/show.rhtml
Ajax Tree Application
\’items/ajaxtree\’, :object=>[@item,@items] %>
should be:
Add the following code in the file app/views/items/show.rhtml
Ajax Tree Application
\’items/ajax_tree\’, :object=>[@item,@items] %>
_ajax_tree.rhtml
{:controller=>\’items\’,:action=>\’display_clicked_item\’,:id=>n.id}
:loading=>\”Element.show(\’tree_indicator\’)\”,
:complete=>\”Element.hide(\’tree_indicator\’)\”,
}
should be:
{:controller=>\’items\’,:action=>\’display_clicked_item\’,:id=>n.id},
:loading=>\”Element.show(\’tree_indicator\’)\”,
:complete=>\”Element.hide(\’tree_indicator\’)\”
}
Hello
Thanks for the code
but i am also have
can’t convert Array into String
Showing app/views/items/_ajax_tree.rhtml where line #70
in just created, clear project
Hello everyone !!
I am correcting the code and will upload it by tomorrow and will post a comment thereby.
Hello Everyone !!
Sorry for the delay…
Hi Alex, Eastviking, Rana, Eric…
I was through with the code this weekend and i found some of my stupid mistakes, sorry for that… anwaz
I have uploaded the modified corrected code. I have also tested it in a fresh newly created application and it is working fine.
Thanks.
Programmers inhumanity to man
Its amazing how such a simple code fragment has spanned 3-months to rectify the sample code.
Could you please direct me to the latest source code.
thanks
schmii
Hi schmii !!
sorry to say but i am disappointed by ur invalid perception.
The code was running fine before November, it was broken after it when i make it a bit generalized… so my maths says that it has taken around 15 days not 3 months and that too coz i was busy in my commercial projects.
Anyways.. the published code in this post is now working.
[...] My friend sur wrote and shares his code for Ajax based drag drop and sortable tree for rails. He is also trying to pluginize this, and soon it will be publicly available. Find more detail here. [...]
Hi Sur thank you for sending me the zip files for ‘testapp’. I followed your instructions and it works great.
I’m new to ruby and rails and I’m attempting to develop my first application. Your sample code has given me a working example from which I can apply to my application.
thanks
schmii
[...] I have provided the source code of the ajax based drag drop tree in rubyonrails in one of my previous posts. I found some of the people are getting problems to incorporate the code into their running applications so i am providing a sample rails application in which all the code for tree is already been placed well. [...]
Cool,
Nice code.
Now all I need is an web application to make use of this code.
[...] My friend sur wrote and shares his code for Ajax based drag drop sortable tree for rails. He is also trying to pluginize this, and soon it will be publicly available. Find more detail here. [...]
hello!
your code looks very interesting! i would greatly appreciate the sample poject to play with. thank you for sharing your hard work with all of us!
much appreciated,
cheers,
andy
Thanks Andy !!
Sur,
Great project you are working on. I’ve implemented your code in the way you describe, but am still running into an RJS error. When I click on a parent group on the ’show’ page, my browser shows a javascript error:
RJS Error:
TypeError: Effect.toggle is not a function
This happens to me on the Mac (Safari & Firefox) as well as on a PC (IE).
I am fairly certain I implemented your code correctly (I did it twice, just to be sure and named all files and DB table the same as your example). Any ideas what I may be doing wrong?
Thanks,
Tim
Hi TimN !!
Well, before i figure out if there is any problem, could you try the Sample Application in which you need not to code a single line but just need to follow 4 steps described Here.
I will look forward if the problem still persists, let me know in any case whether or not the application is running fine.
Thanks.
Sur,
I created my own “items” table with the fields you had in your schema, but other than that, I did follow the steps you described… I think
Do you know of a publicly available URL where your example app is running so that I could check it out?
Best,
Tim
How do I display the tree upto 2 or more Levels? Currently it is being displayed till only 1 level..
Hi Kunjan !!
Drag any element and drop it onto an element of second level, and the dropped element will become child and become a third level element.
How you need not to explicitly specify any level for nodes, but just add any element having parent_id as the id of second level… third level… and so on.
Thanks for submitting this code. I have been looking at Javascript versions, but yours is much simpler and RoR native! It seemed the toggleBackground function was never called, so the selected item would never highlight. There may be a better way, but it can be fixed by adding
; toggleBackground($('#{n.id}_tree_item'));
to the loading or complete portion of
{...},
:loading=>"Element.show('tree_indicator')",
:complete=>"Element.hide('tree_indicator'); "
)}
%>
in _ajax_tree.rhtml. In other words, you want
:loading=>"Element.show('tree_indicator');
toggleBackground($('#{n.id}_tree_item'));",
Thanks Steven,
I guess i have missed that while extracting it from my application.
I will add it now.
Thanks.