sur says ajax :on => rails

Thursday, August 17, 2006

Source Code For Ajax Based Drag Drop Navigation Tree For Rails.

Hello Everyone!
for some CSS issues i was not able to publish the source code properly on my wordpress's blog so i am publishing all the code here only. Check this out...

CASE STUDY

I m providing a very generalized use case where the tree fits in a good position. Here it is...
Consider a model MyItem, a controller MyController and a myaction.rhtml as a view. MyItem model is using acts_as_tree and we are going to put a seed for MyItem to grow it in an ajax tree ;-) ... So, lets start the code now...

==========================================

From the command prompt run this command to generate the migration file to create the table in database



and a file name db/migrate/00x_create_my_item.rb will be created where 00x represents the version of your schema info incremented by 1.

Add the following code to this file 00X_create_my_item.rb


class CreateMyItem < force =""> true do |t|
t.column "name", :string
t.column "created_at", :datetime
t.column "parent_id", :integer, :default => 0, :null => false
end
end
def self.down
drop_table :my_items
end
end



As we are finished with the migration file, run this migration to create the
table in databse by this command.


Now create the model for this table by executing the follwing command


and this will create a file app/models/my_item.rb file will be created.

Add the following code to this file my_item.rb


class MyItem < order =""> "created_by"
HIERARCHY_LEVEL = 3
validates_presence_of :name
attr_accessor :style

def self.roots
self.find_all_by_parent_id(0)
end

def level
self.ancestors.size
end
end



Add the following code to the view file app/views/my_controller/myaction.rhtml



<% @myitem = @myitems[0] %>
<!-- here @myitems[0] reflects the first node item that will be selected by default -->
<div id="navtree">
<%= render :partial=>'my_controller/mytree', :object=>[@myitem,@myitems] %>
</div>

<div id="selected_item">
<%= render :partial=>'my_controller/myitem', :object=>@myitem %>
</div>



Add the following code to app/controllers/my_controller.rb




def myaction
@myitems = MyItem.find(:all)
end

def show_selected_item
if request.xhr?
@myitem = MyItem.find(params[:id])
if @myitem
# 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=>"my_controller/myitem", :object=>@myitem
page.visual_effect 'toggle_appear', "selected_item"
end
end
end
end

def sort_my_tree
if request.xhr?
if @myitem = MyItem.find(param[:id].split("_")[0])
parent_myitem = MyItem.find(params[:parent_id])
render :update do |page|
@myitem.parent_id = parent_myitem.id
@myitem.save
@myitems=MyItem.find(:all)
@sort = 'inline'
page.replace_html "navtree", :partial=>"my_controller/mytree", :object=>[@myitem,@myitems,@sort]
end
end
end
end



Add the following code in the file app/views/my_controller/_myitem.rhtml



<% if @myitem %>
Selected Item is <%=h @myitem.name%>
<% else %>
Item not found
<% end %>



I have include all the style and the javascript methods here in this file only.
You can make CSS and JS files accordingly. JS also includes collection proxies (fundo it is ).
Add the following code in the file app/views/my_controller/_mytree.rhtml



<% @sort ||= 'none' %>
<% reorder_style = (@sort=='none')? 'inline' : 'none' %>
<% done_style = (@sort=='none')? 'none' : 'inline' %>

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

<p>
<a href="#" id="reorder" style="display:<%=reorder_style%>" onclick="javascript: return showDrag();">Reorder</a>
<a href="#" id="done" style="display:<%=done_style%>" onclick="javascript: return hideDrag();">Done Reordering</a>
<br/><br/>
</p>

<a id="drop_at_collection" href="/collection" onclick="javascript: return toggleDiv(); ">
<img src="/images/expanded.gif" id='expanded' />
<img src="/images/collapsed.gif" style="display:none" id='collapsed'/>
<b><%= "Displayed Root" %></b>
</a>
<%= drop_receiving_element("drop_at_collection",:accept=>'inner_tree_element',
:url=>{:controller=>'my_controller',:action=>'sort_my_tree',:id=>nil},
:loading=>"Element.show('sort_tree_indicator')",
:complete=>"Element.hide('sort_tree_indicator');"
)%>

<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:2px 0 0 8px;
}

.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
{
/* this class is mandatory for the collection proxies...
u can avoid this class if u need ur tree not to work well :-) */
border:1px;
}
</style>

<div id="mytree" class="mytree">
<% @ancestors = @myitem.ancestors.collect{|parent| parent.id} unless !@myitem.has_parent? %>
<% @myitems = MyItem.find(:all) %>
<%= get_tree_data(@myitems,0){|n|
link_to_remote n.name,
:url=>{:controller=>'my_controller',:action=>'show_selected_item',:id=>n.id}
:loading=>"Element.show('tree_indicator')",
:complete=>"Element.hide('tree_indicator')",
}
<% @myitems.each do |node| %>
<% if node.has_parent? %>
<%= draggable_element node.id.to_s+'_tree_div',:revert=>true, :snap=>false, :handle=>"'#{node.id.to_s}_drag_image'" %>
<% end %>
<% if node.level < MyItem::HIERARCHY_LEVEL %>
<%= drop_receiving_element(node.id.to_s+'_tree_div',:accept=>'inner_tree_element',
:url=>{:controller=>'collection',:action=>'sort_my_tree',:parent_id=>node.id,:id=>nil},
:loading=>"Element.show('sort_tree_indicator')",
:complete=>"Element.hide('sort_tree_indicator');"
)%>
<% end %>
<% end %>

<%= image_tag 'indicator.gif', :id=>'sort_tree_indicator', :style=>'display:none' %>

</div>


<script type="text/javascript">

var selected_el = document.getElementById('<%=@myitem.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>




Add the following code in app/helper/application_helper.rb



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='display:#{@sort}; 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(tree, node.id){|n| yield n}
ret += "</span>"
ret += "</div>"
end
end
ret += "</div>"
return ret
end


Wednesday, August 09, 2006

Fully Ajax Based Drag Drop Navigation Tree For Ruby on Rails

Ajaxified tree comes with the following features

1.) Containig 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 ny the acts_as_tree.

5.) C lear 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 this tree.