Sei sulla pagina 1di 20

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

Entry Filed under: ajax, rails, tree, ajax tree, drag drop tree, navigation tree

26 Comments Add your own


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

Good

• 2. Ajax on Rails » Ful&hellip | August 22nd, 2006 at 4:49 pm

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

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

Give me the source

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

Hi Source Required !!
Check out the Source Code

• 5. Rana | November 5th, 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:

• 6. Sur Max | November 8th, 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.

• 7. eastviking | November 13th, 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\’)\”
}

• 8. Alex | November 16th, 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

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

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

• 10. Sur Max | November 21st, 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.

• 11. schmii | November 22nd, 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
• 12. Sur Max | November 22nd, 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.

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

• 14. schmii | November 25th, 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

• 15. Sample Rails Application &hellip | November 26th, 2006 at 9:27 am

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

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

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

• 17. Web On Rails » Blog&hellip | December 1st, 2006 at 6:35 am

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

• 18. andy | December 5th, 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

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

Thanks Andy !!

• 20. TimN | December 10th, 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

• 21. Sur Max | December 11th, 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.

• 22. TimN | December 11th, 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

• 23. Kunjan | December 13th, 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..

• 24. Sur Max | December 13th, 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.

• 25. StevenG | December 26th, 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'));",

• 26. Sur Max | December 26th, 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.
Creating sortable lists with PHP and AJAX
By Quentin Zervaas, 24 February 2006

Adding drag and drop functionality to our list


We will now add the drag/drop functionality to our list, as well as applying CSS styles to the
list. At this point the ordering of the list will not be saved, as we will do this in the next step.

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.

• Scriptaculous download page

This example uses Scriptaculous 1.5.3.

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.

Styling the list – styles.css

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

The Scriptaculous drag sort code

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

The name movies_list refers to the ID of our unordered 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.

Our new index.php

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" src="scriptaculous-js-


1.5.3/lib/prototype.js"></script>
<script type="text/javascript" src="scriptaculous-js-
1.5.3/src/scriptaculous.js"></script>
</head>
<body>
<h1>phpRiot Sortable Lists</h1>

<ul id="movies_list" class="sortable-list">


<?php foreach ($movies as $movie_id => $title) { ?>
<li id="movie_<?= $movie_id ?>"><?= $title ?></li>
<?php } ?>
</ul>

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

Creating sortable lists with PHP and AJAX


By Quentin Zervaas, 24 February 2006

Creating the order processing script


Now we need to write the script that processes any ordering changes to the list. Once this is
done, we’ll add the functionality to our list to actually call this script.

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.

To achieve this, we create a new function in our movies.php, called processMoviesOrder().


Add this function after the getMovies() function in movies.php.

processMoviesOrder() for MySQL

Highlight: PHP
<?php
function processMoviesOrder($key)
{
if (!isset($_POST[$key]) || !is_array($_POST[$key]))
return;

$movies = getMovies();
$queries = array();
$ranking = 1;

foreach ($_POST[$key] as $movie_id) {


if (!array_key_exists($movie_id, $movies))
continue;

$query = sprintf('update movies set ranking = %d where movie_id


= %d',
$ranking,
$movie_id);

mysql_query($query);
$ranking++;
}
}
?>

processMoviesOrder() for PostgreSQL

Highlight: PHP
<?php
function processMoviesOrder($key)
{
if (!isset($_POST[$key]) || !is_array($_POST[$key]))
return;

$movies = getMovies();
$queries = array();
$ranking = 1;

foreach ($_POST[$key] as $movie_id) {


if (!array_key_exists($movie_id, $movies))
continue;

$query = sprintf('update movies set ranking = %d where movie_id


= %d',
$ranking,
$movie_id);

pg_query($query);
$ranking++;
}
}
?>

processor.php for MySQL and PostgreSQL

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');
?>

Adding the JavaScript sorting callback


The final item we must add is the JavaScript code to invoke processor.php when the list is
updated. This involves creating a function that makes the Ajax update request, as well as
telling the Scriptaculous Sortable.create() method about it.

Here’s the callback function:

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.

index.php for MySQL and PostgreSQL in full

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" src="scriptaculous-js-


1.5.3/lib/prototype.js"></script>
<script type="text/javascript" src="scriptaculous-js-
1.5.3/src/scriptaculous.js"></script>
</head>
<body>
<h1>phpRiot Sortable Lists</h1>

<ul id="movies_list" class="sortable-list">


<?php foreach ($movies as $movie_id => $title) { ?>
<li id="movie_<?= $movie_id ?>"><?= $title ?></li>
<?php } ?>
</ul>

<script type="text/javascript">
function updateOrder()
{
var options = {
method : 'post',
parameters :
Sortable.serialize('movies_list')
};

new Ajax.Request('processor.php', options);


}

Sortable.create('movies_list', { onUpdate : updateOrder });


</script>
</body>
</html>

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

new Ajax.Request('processor.php', options);


}

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.

Sortable Lists Demo

Discuss (48 comments)


Print
E-mail

This is the first list

• Item 2 from first list.


• Item 3 from first list.

And now the second list

• DRAG HEREItem 1 from second list.


• DRAG HERE Item 2 from second list.
• Item 1 from first list.
• DRAG HERE Item 3 from second list.

Note: you can drag-and-drop between lists as well.


<div style="height:200px;">
<div style="float:left;">
<h3>This is the first list</h3>
<ul class="sortabledemo" id="firstlist" style="height:150px;width:200px;">
<li class="green" id="firstlist_firstlist1">Item 1 from first list.</li>
<li class="green" id="firstlist_firstlist2">Item 2 from first list.</li>
<li class="green" id="firstlist_firstlist3">Item 3 from first list.</li>
</ul>
</div>
<div style="float:left;">
<h3>And now the second list</h3>
<ul class="sortabledemo" id="secondlist"
style="height:150px;width:200px;">
<li class="orange" id="secondlist_secondlist1">
<span class="handle">DRAG HERE</span> Item 1 from second list.
</li>
<li class="orange" id="secondlist_secondlist2">
<span class="handle">DRAG HERE</span> Item 2 from second list.
</li>
<li class="orange" id="secondlist_secondlist3">
<span class="handle">DRAG HERE</span> Item 3 from second list.
</li>
</ul>
</div>
</div>

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

Ruby on Rails Single List M[V]C

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

<%= sortable_element 'list',


:update => 'list-info',
:complete => visual_effect(:highlight, 'list'),
:url => { :action => "order" } %>

# controller
def order
params[:list].each_with_index { |id,idx| Model.update(id, :position =>
idx) }
render :text => 'Updated sort order'
end

Potrebbero piacerti anche