Archive for August, 2006

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

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

August 18, 2006 at 12:02 pm 28 comments

Fully Ajax Based Drag Drop Navigation Tree For Ruby on Rails

Ajaxified tree comes with the following features

1.) Containing the ajax links for the tree items using link_to_remote

2.) Highlighting the selected node of the tree and other custom effects.

3.) Two way syncronization between the tree nodes and the currently displayed item on the web-page.

4.) Supports the tree architecture provided by the acts_as_tree.

5.) Clear and self traversing upto the selected node and all remaining nodes remained closed.

6.) Drag-n-Drop sorting of the tree nodes, including the functionality of even changing the parent of the node.

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

August 9, 2006 at 5:10 am 3 comments


Contact

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

Power Shots

Subscribe