We had a need at work this week to create an interface that allows users to change the sort order of items using a drag-n-drop interface. Drag-n-Drop is one thing that Adobe’s Spry library is missing. But with the help of the Scriptaculous JavaScript library, this is a pretty easy task.
Scriptaculous is an add-on to the Prototype JavaScript framework. Start by downloading Scriptaculous and linking to it on your page. You don’t need to download Prototype separately, its included.
<script language="JavaScript" src="/javascript/prototype.js"></script>
<script language="JavaScript" src="/javascript/scriptaculous.js"></script>
Create your list of items you want sorted. I’m using list items here, but you could use DIVs or table data elements.
<ul id="sortable_list" style="cursor: move">
<li>The first item</li>
<li>Then the second one</li>
<li>Of course then comes the third</li>
<li>And finally, the fourth</li>
</ul>
Then, one important line of JavaScript. Note, this must come after your sortable element.
<script language="JavaScript">
Sortable.create("sortable_list");
</script>
“sortable_list” is the ID of your element containing your sortable items. There are many options you can give to the create() method, see the Scriptaculous documentation for full details. Go ahead and try it out below.
|
Click and drag these |
But how do we keep track of the new order? This could be a control to allow users to order pages on their website, headlines on their homepage, etc. After they sort them into the desired order, we need to do something with that information. There are many ways to do this. I’ll show two.
The first involves using DOM methods to find out what nodes are inside an element.
orderedNodes = document.getElementById("sortable_list").getElementsByTagName("li");
orderedNodes is now an array of LI elements inside the “sortable_list” element, in the order displayed. All we need to do with that is loop through it and write the data to a hidden field that will be sent to the server when the form is submitted. I would put the looping code into a function that gets called using the onSubmit event handler.
But what do we put in the hidden field? The text of the LI element? Thats not very useful. So how about we add the recordids to the LI elements:
<ul id="sortable_list">
<li recordid="1">The first item</li>
<li recordid="2">Then the second one</li>
<li recordid="3">Of course then comes the third</li>
<li recordid="4">And finally, the fourth</li>
</ul>
Then in our JavaScript loop, we can use the getAttribute method to pull back the recordids. If you are concerned about your HTML not validating because of the non standard LI attribute, you could put it into its own namespace, as in mysite:recordid=”n” (correct me if I’m wrong).
So here’s what the function would look like. I’m alert()ing the value instead of writing it to a hidden field here.
function getOrder() {
var orderList = '';
orderedNodes = document.getElementById("sortable_list").getElementsByTagName("li");
for (var i=0;i < orderedNodes.length;i++) {
orderList += orderedNodes[i].getAttribute('recordid') + ', ';
}
alert(orderList);
}
You should get the recordids of the example elements, in the order you sorted them.
So after I went through all that work, I found the Sortable.serialize() method. This Scriptaculous method turns a Sortable object into a list of name[]=value pairs separated by ampersands. The catch is you’ll have to store the recordids in the ID attribute, and it needs to be in a specific format: “string_identifier”, where identifier is the ‘recordid’ we have been using. The ‘string’ portion is ignored. Example:
<ul id="sortable_list">
<li id="item_1">The first item</li>
<li id="item_2">Then the second one</li>
<li id="item_3">Of course then comes the third</li>
<li id="item_4">And finally, the fourth</li>
</ul>
To see an example of what the serialize() method returns,
The format may seem strange to those unfamiliar with PHP.
sortable_list[]=1&sortable_list[]=2&sortable_list[]=3&sortable_list[]=4
In PHP (and a few other languages) you can pass this string directly on the URL and end up with sortable_list being an array. You could also pass the whole string in one variable, then run that variable through the parse_str function to turn that string into an array of values.
parse_str($_GET['sortorder'],$newSortorderArray);
In ColdFusion you’ll just have to be aware that the square brackets are going to be on there when you go to parse out your values. I prefer the first method of getting the data if you are using ColdFusion, rather than messing with the PHP-like format that serialize() returns. I also like it because I’m not sticking the recordids into the ID attribute, which just feels weird.
You don’t have to wait until the form is submitted to get the new sort order, Scriptaculous offers two different event handlers you can use – onChange and onUpdate. You could recalculate your hidden field each time the sort order is changed, or even make an AJAX call to update your database immediately. There’s really quite a bit more you can do – add effects when the element is picked up and dropped, specify a hover class to be used when the user mouses over an item, and more.
Jason says:
Nice post! I’ll keep this in mind next time I need this. 🙂
1 January 2007, 6:38 amWalt Stoneburner says:
Nicely documented — I was going through the Scriptaculous source code and working this out, only to stumble upon your page. You’ve saved me a lot of time.
27 March 2007, 1:52 pmantoine says:
Hi. I am a trying to do the ajax callback to update my db with the new order but I realy stuck here. Do you have an example that you could send me? Thanks.
27 March 2007, 2:26 pmfederico says:
Thanks for the post!
A little correction.
You can’t use tables as sortable items
Important: You can use Sortable.create on any container element that contains Block Elements, with the exception of TABLE, THEAD, TBODY and TR. This is a technical restriction with current browsers. [1]
[1] Quoted from: http://wiki.script.aculo.us/scriptaculous/show/Sortable.create
16 April 2007, 11:58 pmJason says:
Does this work with multiple lists on one screen?
29 April 2007, 10:32 amb says:
hey, thanks for that!
6 June 2007, 8:51 amChris says:
Nice Tut!
Is it possible to store the sortorder in a cookie and read it onLoad?
23 June 2007, 5:12 pmZeal says:
Hey Chris, did you manage to figure anything out regarding storing sort order into a cookie? This would be helpful if someone wants to create a startpage for their users and dont require login on the same computer. I think most of us have pageflakes and IG on mind here for our own sites 🙂
25 June 2007, 9:54 pmChewie says:
Great few tips here, i was struggling to get the the array into php so that i could save the sort order, this has done just the trick.
9 August 2007, 7:56 amMobile Phones says:
Zeal, this may help you with saving to a cookie…
http://blog.tool-man.org/saving-a-reordered-list/14
9 August 2007, 8:00 amBee Kay says:
Thanks
I am looking for exactly the same thing and i like this.
14 September 2007, 4:30 amKeep on posting good thing.
A random person says:
This is great, I just use the getOrder function with the onmouseover event in the and save it to a hidden field (instead of the alert), then just make an array on the next page.
24 October 2007, 4:09 pmA random person says:
I lied – i used the onmouseup event in the UL to run the getOrder.
24 October 2007, 4:11 pm.jonah says:
@antoine: To do a callback, you need to use the Sortable.serialize() and Ajax.updater().
Here’s an example of one way to do it:
Sortable.create(‘sortableList’,
{
onUpdate:function() {
new Ajax.Updater(‘ajaxResultArea’, ‘urlToPostTo.cfm’, {
onComplete:function(request) {
new Effect.Highlight(‘ajaxResult’,{});
},
parameters:Sortable.serialize(‘sortableList’),
evalScripts:true,
asynchronous:true
})
}
}
)
Read more here: http://wiki.script.aculo.us/scriptaculous/show/Sortables
25 October 2007, 6:56 pmLethal says:
.jonah, i’m having trouble with the call back writing to the “urlToPostTo.cfm” what string will Ajax send to the URL?
Thanks for the head start though, almost their..
Any clues?
3 November 2007, 10:48 pmLethal says:
.jonah – my Ajax request is not showing up in Firebug either, does that mean i need to update my lib files as prototype may not be functioning correctly?
Thanks.
3 November 2007, 10:56 pmRon West says:
Dude,
Thanks a lot – totally kicks ass! I love fast copy paste JS to get you started in a direction. You helped a ton!
11 February 2008, 10:07 pmphalanx says:
Awesome! You saved me a ton of time.
On a funny note, I was trying to get the getOrder function to work when I clicked a submit button. But for some reason, it wasn’t running. After spending 15 minutes troubleshooting postback issues (.NET) I realized that the I had misspelled onClick as onlick.
If I had only licked the monitor, I could have figured this out sooner!
10 March 2008, 11:57 amKevin says:
Does anyone know of a tutorial or a way to use drag and drop and sorting to carry list items between two different ULs? Think of WordPress 2.3’s widget functionality… like that. I have been trying to do it with jquery UI , but it doesnt seem like its capable of handling this. Any thoughts?
12 March 2008, 12:17 pmEnfopedia says:
Does anyone know where I can find an amazing easy to config Drag and Drop Shopping Cart?
13 March 2008, 12:54 amEnfopedia says:
I forgot to mention a Ajax or PHP drag and drop shopping cart?
13 March 2008, 12:55 amFrederik says:
I so needed this for a backend system, thanks a lot!
3 May 2008, 6:04 pmDan says:
Thanks… just what i was looking for 🙂
14 June 2008, 3:58 amHow do you write the values it to a hidden field ?
Artan says:
Thanks a lot… Very helpful tutorial. I was looking to do just this.
23 July 2008, 1:48 pmJess says:
This drag and drop sort order has a problem if the list of items is very long and you want to drag one item down through the list but your window doesn’t automatically scroll down. Does anyone have a solution to fix this problem? Even better, if someone can show me how i can put those items within a DIV with an overflow setting. I want the items to stay within the DIV box and if i drag an item down through the list, i’d like the scroll bar in my DIV to automatically scroll down as I drag the item.
8 August 2008, 7:16 pmThank you.
PJ says:
I am a little new to javascript. I find 1001 versions of how to do this breezily with PHP. I’m delighted you gave an example with CFML. But I wish you had a cut&paste example of the full code because I’m struggling with how to write this to a ‘hidden form field’ for CFML submission, instead of your alert, and it doesn’t seem to be working (I’d give a code example but I’ve tried half a dozen things. I obviously just don’t know how to do it, and many of the things I find via google must be leaving out something critical). If you had that little tip included it would sure make it helpful. Thanks for a neat article anyway, the sorting bit is great. PJ
19 August 2008, 9:49 amJonathan Marsh says:
Hi,
I’m looking to create a drag and drop form. Any ideas on how this can be done? The idea would be that I can drag between 1 and 10 sub forms onto a droppable object, edit each of the forms, and then post the information from each form.
Thanks.
Jonathan
14 January 2009, 7:18 amRichard Sweet says:
Great easy to follow tutorial, thanks.
One thing. – When you use your own propriety attribute (recordid), why not just use id instead?
21 January 2009, 12:25 pmRyan Stille says:
Richard you certainly could use the ID. I didn't because it just didn't feel right. The ID is for being referenced in CSS and to get a handle on from the DOM. Here I just wanted to store my database record key.
21 January 2009, 1:26 pmTrevor Lettman says:
Great tutorial! This probably saved me a whole day of mucking around. Just in case there are other php/mysql developers looking for a solution to do this in multiple lists, I thought I would post some code… hopefully this will work as I don't see a "preview" option here.
I needed a solution to sort multiple categories of items from the same table, so I created a script that creates the list via php and assigns each a unique id and calls the Sortable.create() function as well. The js function is passed the list id as an argument – and subsequently passes it on to the server-side php script that sets the new sort_order for each row.
The php that creates the list from the mysql database:
<?php
/* connection script removed – connect to your db here */
$r = mysql_query ("SELECT * FROM sometable ORDER BY sort_order ASC");
$listname = 'list_' . rand('111111','999999');
print '<ul id="' . $listname . '" onMouseUp="getOrder(\'' . $listname . '\')">';
while ($row = mysql_fetch_array($r))
{
extract($row);
print '<li id="item_' . $id . '">$title</li>";
}
print '</ul>';
#create the sortable list via scriptaculous
print '<script>Sortable.create("' . $listname . '");</script>';
?>
The js function – note the listname arg:
<script language="JavaScript">
var url = '/thepathtomybackendscript/script.php';
function getOrder(listname) {
var orderList = Sortable.serialize(listname);
sendRequest(url + '?listname=' + listname + '&' + orderList);
}
makeRequest(url);
</script>
The server-side script called via AJAX:
<?php
/* connection script removed – connect to your db here */
if ($array = $_SERVER['QUERY_STRING'])
{
$listname = $_GET['listname'];
parse_str($_SERVER['QUERY_STRING'],$newSortorderArray);
foreach ($newSortorderArray[$listname] as $key => $value)
{
mysql_query("UPDATE sometable SET sort_order='$key' WHERE id='$value'");
}
/* string to be returned, if any: print_r ($newSortorderArray[$listname); */
}
?>
The only component missing here is the actual AJAX javascript, which is pretty basic. If you're new to this here is a good place to start: https://developer.mozilla.org/En/AJAX:Getting_Started
Hope this helps someone. Thanks again for the tute!
27 January 2009, 4:57 pmaxel f. says:
hey there,
is it possible to make two lists sortable?
i'm having a left and a right ul (so two columns) and now want to be able to drag the elements in between those lists…
Is this possible?
Thx, axel
24 February 2009, 12:34 pmJohnny Five says:
I am a newbie in Ajax type stuff so this may be a stupid question. That aside here goes.
I have a div populated with available images from the database, and i can drag them into a selected images div. but i cannot rearrange the selected images div after they are dropped.
also if i come back to this particular image selection the availble models are only populated with the images that are available minus the ones that are already in the selected images div.
i keep going around in circles.
i have the php code to load the images into their appropriate divs based on wheter they were previously added to the selected images div or not, and then i can make them sort individually but as soon as i drag an available image into the selected images div the sorting stops working, and the already selected images cannot be removed from that div.
help me, am i trying to do something that cannot be done.
5 May 2009, 12:58 pmdave says:
Great article, and very informative.
One issue I encountered was using a cfloop to go over the list returned by javascript. The space added after the comma (code: getAttribute('recordid') + ', ';) induces an error in the cfloop process (it loops over the list the appropriate number of times, but it only uses the first item in the list each time). Thought I would point that out for any one else having problems with it.
Also replace "alert(orderList);" with "document.formname.hiddenField.value = orderList;" (where formname is the form's name, and hiddenField is the name of the hidden form field) to input the data into a hidden form field. (For those asking the question so long ago.)
17 June 2009, 9:43 amPleski says:
I'm afraid it doesn't work for me. I'm on IE 7
21 April 2010, 7:14 pm