Source Code For Ajax Based Drag Drop Navigation Tree in Ruby on Rails – the tree works well with firefox and IE-6

August 18, 2006 at 12:02 pm 28 comments

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!!

Advertisements

Entry filed under: ajax, ajax tree, drag drop tree, navigation tree, Ruby on Rails, tree.

Fully Ajax Based Drag Drop Navigation Tree For Ruby on Rails AN INSTANIA OF FRUSTRA

28 Comments Add your own

  • 1. abap  |  August 22, 2006 at 6:32 am

    Good

    Reply
  • […] This tree works very fine in my application and hope it will help u also. Check out the Source Code of the tree. […]

    Reply
  • 3. Source Required  |  September 26, 2006 at 8:18 pm

    Give me the source

    Reply
  • 4. SUR  |  September 27, 2006 at 5:37 am

    Hi Source Required !!
    Check out the Source Code

    Reply
  • 5. Rana  |  November 5, 2006 at 2:51 pm

    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:

    Reply
  • 6. Sur Max  |  November 8, 2006 at 2:44 am

    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.

    Reply
  • 7. eastviking  |  November 13, 2006 at 9:29 am

    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\’)\”
    }

    Reply
  • 8. Alex  |  November 16, 2006 at 9:23 am

    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

    Reply
  • 9. Sur Max  |  November 16, 2006 at 10:36 am

    Hello everyone !!
    I am correcting the code and will upload it by tomorrow and will post a comment thereby.

    Reply
  • 10. Sur Max  |  November 21, 2006 at 7:50 pm

    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.

    Reply
  • 11. schmii  |  November 22, 2006 at 5:44 am

    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

    Reply
  • 12. Sur Max  |  November 22, 2006 at 5:55 am

    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.

    Reply
  • […] 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. […]

    Reply
  • 14. schmii  |  November 25, 2006 at 5:59 pm

    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

    Reply
  • […] 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. […]

    Reply
  • 16. Gaurav  |  November 28, 2006 at 1:31 pm

    Cool,
    Nice code.
    Now all I need is an web application to make use of this code.

    Reply
  • […] 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. […]

    Reply
  • 18. andy  |  December 5, 2006 at 4:41 pm

    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

    Reply
  • 19. Sur Max  |  December 5, 2006 at 5:45 pm

    Thanks Andy !!

    Reply
  • 20. TimN  |  December 10, 2006 at 10:40 pm

    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

    Reply
  • 21. Sur Max  |  December 11, 2006 at 3:36 am

    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.

    Reply
  • 22. TimN  |  December 11, 2006 at 4:38 am

    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

    Reply
  • 23. Kunjan  |  December 13, 2006 at 1:43 pm

    How do I display the tree upto 2 or more Levels? Currently it is being displayed till only 1 level..

    Reply
  • 24. Sur Max  |  December 13, 2006 at 2:34 pm

    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.

    Reply
  • 25. StevenG  |  December 26, 2006 at 6:16 am

    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'));",

    Reply
  • 26. Sur Max  |  December 26, 2006 at 7:47 am

    Thanks Steven,
    I guess i have missed that while extracting it from my application.
    I will add it now.

    Thanks.

    Reply
  • 27. Susan  |  April 5, 2007 at 3:11 am

    Cool! Well done! And thanks for sharing!!
    I am slightly frustrated that I can’t drag a formerly root-item out from a leaf position. I can make 2 a branch of 4, but I can’t place it back in it’s original position.
    I have been looking for some code to dragndrop images into order, and I think I will be able to learn from your sample. THANKS!!

    Reply
  • […] best I’ve found is … AjaxTree of Sur Max the creator of the SimpleCaptcha Rails […]

    Reply

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Trackback this post  |  Subscribe to the comments via RSS Feed


Contact

sur.max(at)gmail.com
August 2006
M T W T F S S
« Mar   Sep »
 123456
78910111213
14151617181920
21222324252627
28293031  

Power Shots

Tokyo

Moke Lake

Lower Kentucky Falls (Mapleton, OR)

Glorious Morning

Three of a kind

Peace, quiet and tranquility

Stag Beetle

In het gras/Into the grass

Detached.



More Photos

Subscribe


%d bloggers like this: