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
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
Add the following code to the view file app/views/my_controller/myaction.rhtml
Add the following code to app/controllers/my_controller.rb
Add the following code in the file app/views/my_controller/_myitem.rhtml
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
Add the following code in app/helper/application_helper.rb
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