Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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
rails 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…
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.
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.
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 …
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
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
<div id=”selected_item”>
<%= render :partial=>’items/item’, :object=>@item %>
</div>
<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;}
.outer_tree_element{margin:0 0 0 10px;}
.inner_tree_element{margin:5px 0 0 10px;}
.mytree a:hover{background-color:lightblue;}
.mytree label{font-weight:normal;}
.highlighted{background-color:lightblue;}
.normal{background-color:white;}
.drag_image{border:0px;}
</style>
<script type="text/javascript">
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
Now you can check the tree functionality at http://localhost:3000/items/show.. assuming that
you are running your server on port 3000. njoy!!
Entry Filed under: ajax, rails, tree, ajax tree, drag drop tree, navigation 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. […]
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:
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.
and:
Selected Item is
should be:
Add the following code in the file app/views/items/_item.rhtml
Selected Item is
should be:
Add the following code in the file app/views/items/show.rhtml
_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
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.
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.
• 13. Ajax based drag drop and &hellip | November 25th, 2006 at 1:06 pm
[…] 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
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'));
{...},
:loading=>"Element.show('tree_indicator')",
:complete=>"Element.hide('tree_indicator'); "
)}
%>
Thanks Steven,
I guess i have missed that while extracting it from my application.
I will add it now.
Thanks.
Creating sortable lists with PHP and AJAX
By Quentin Zervaas, 24 February 2006
Installing Scriptaculous
Since we are using Scriptaculous to create the drag/drop effect, we must now download and
install it. Note that we also need the Prototype library, however, this is included with the
Scriptaculous download.
Once downloaded, extract the library in the directory where you saved index.php. You may
save this elsewhere, but we will assume this is where you have saved it.
Before we add the drag/drop, we will style the list. Below is a generic CSS class we will save
to a file called styles.css.
Highlight: CSS
.sortable-list {
list-style-type : none;
margin : 0;
}
.sortable-list li {
border : 1px solid #000;
cursor : move;
margin : 2px 0 2px 0;
padding : 3px;
background : #f7f7f7;
border : #ccc;
width : 400px;
}
It’s really simple to make our list drag-sortable. At this point we’re not actually saving the
drag changes, but to make the list sortable, the following code is used:
Highlight: JavaScript
Sortable.create('movies_list');
There are many more options and effects that can be applied, but the default options work just
fine for what we’re doing. You can always read the Scriptaculous documentation for more
options.
So here is the new version of index.php, with styles added, Scriptaculous and Prototype
loaded, and our draggable list created:
Highlight: PHP
<?php
require_once('database.php');
require_once('movies.php');
if (!dbConnect()) {
echo 'Error connecting to database';
exit;
}
$movies = getMovies();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-
strict.dtd">
<html>
<head>
<title>phpRiot Sortable Lists</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
<script type="text/javascript">
Sortable.create('movies_list');
</script>
</body>
</html>
Note that we also added an ID to each list item, as these are the values that will be passed to
the form. Note that these IDs—and the ID of the list—should use underscores as separators,
not hyphens.
So at this point, if you view this page, you should be able to drag the items in your list up and
down! Cool eh?
When a change to the list occurs, an array of the movie ID’s in their new order is generated,
so our processor needs to take this array, and then update the ranking field in the database
accordingly.
Highlight: PHP
<?php
function processMoviesOrder($key)
{
if (!isset($_POST[$key]) || !is_array($_POST[$key]))
return;
$movies = getMovies();
$queries = array();
$ranking = 1;
mysql_query($query);
$ranking++;
}
}
?>
Highlight: PHP
<?php
function processMoviesOrder($key)
{
if (!isset($_POST[$key]) || !is_array($_POST[$key]))
return;
$movies = getMovies();
$queries = array();
$ranking = 1;
pg_query($query);
$ranking++;
}
}
?>
Now here is the script that calls the processMoviesOrder script. Note that we pass the form
index that holds the ordering values. There’s no great reason for doing this other than if you
change the form key then you only have to change it here (note that this is the unordered list
ID from index.php).
Highlight: PHP
<?php
require_once('database.php');
require_once('movies.php');
if (!dbConnect())
exit;
processMoviesOrder('movies_list');
?>
Highlight: JavaScript
function updateOrder()
{
var options = {
method : 'post',
parameters : Sortable.serialize('movies_list')
};
new Ajax.Request('processor.php', options);
}
Here we invoke the Prototype library’s Ajax request handler to call processor.php.
Additionally, we use the serialize() method on the Scriptaculous Sortable object to create the
POST variable we access in processor.php.
Finally, we modify our list creation to tell it about this updateOrder() callback:
Highlight: JavaScript
Sortable.create('movies_list', { onUpdate : updateOrder });
The second parameter to Sortable.create() is an optional list of extra parameters. In this case
we are just specifying the onUpdate parameter, which tells Sortable which function to call
when the list is changed.
Highlight: PHP
<?php
require_once('database.php');
require_once('movies.php');
if (!dbConnect()) {
echo 'Error connecting to database';
exit;
}
$movies = getMovies();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-
strict.dtd">
<html>
<head>
<title>phpRiot Sortable Lists</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
<script type="text/javascript">
function updateOrder()
{
var options = {
method : 'post',
parameters :
Sortable.serialize('movies_list')
};
Now, when you visit this page, you will see the list just as you did before, but now when you
drag an item to a new location, it will be saved in the database. If you don’t believe me, try
dragging an item, closing your browser, then reloading the page. The order will be just as you
left it after dragging the item.
Summary
In this article we learned how to create a sortable list using PHP and Ajax. We used
Scriptaculous and Prototype libraries to make light work of our JavaScript requirements (the
sorting and Ajax requests), as these libraries provide a very powerful and simple interface to
advanced features and effects.
Error handling
We didn’t deal with error handling at all in this article, for the sake of simplicity. Specifically,
we didn’t specify what would happen if the update didn’t work. If the update failed, the list
would appear to be updated, but when you refreshed the list it would be the old state.
One possible way to handle this would be to send a success/failure indication from
processor.php, and then to read this response in index.php, rolling back the drag and drop if
failure was returned.
Extra features
When you update the list, the saving of the new ordering is a very quick process, but it is
possible that sometimes it could take longer due to latency or server load. As such, you might
think about showing then hiding a message while performing the update.
To do this, you would make the message appear when updateOrder() is called, and then create
another function to hide the message once complete. This is achieved by specifying the
onComplete parameter in the options array for the Ajax request.
Here’s an example:
Highlight: JavaScript
function updateOrder()
{
// turn on update message here
var options = {
method : 'post',
parameters : Sortable.serialize('movies_list'),
onComplete : function(request) {
// turn off update message here
}
};
I’ll leave this as an exercise for you to complete. Hint: create a div which you initially set the
CSS display property to ‘none’. Then set it to ‘block’ to show the div, and set it back to ‘none’
to hide it again.
<script type="text/javascript">
// <![CDATA[
Sortable.create("firstlist",
{dropOnEmpty:true,containment:["firstlist","secondlist"],constraint:fa
lse});
Sortable.create("secondlist",
{dropOnEmpty:true,handle:'handle',containment:["firstlist","secondlist
"],constraint:false});
// ]]>
</script>
note:
The script tag that encloses the Sortable.create function calls needs to occur after all of the
lists that you intend to use. IE.. in this example if you were place the
“Sortable.create(“firstlist”...);” call immediately after that list (and before the second list) you
would only be able to drag from list 1 to list 2 and not from list 2 to list 1.
The easiest way avoid this is just to call all of your Sortable.creates in the same script tag near
the end of your page.
# view
<ul id="list">
<% 6.times do |i| -%>
<li id="item_<%= i+1 %>">I'm number <%= i+1 %></li>
<% end -%>
</ul>
<p id="list-info"></p>
# controller
def order
params[:list].each_with_index { |id,idx| Model.update(id, :position =>
idx) }
render :text => 'Updated sort order'
end