Drag and drop sort order with Scriptaculous
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.
January 1st, 2007 at 6:38 am
Nice post! I’ll keep this in mind next time I need this.
March 27th, 2007 at 1:52 pm
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.
March 27th, 2007 at 2:26 pm
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.
April 16th, 2007 at 11:58 pm
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
April 29th, 2007 at 10:32 am
Does this work with multiple lists on one screen?
June 6th, 2007 at 8:51 am
hey, thanks for that!
June 23rd, 2007 at 5:12 pm
Nice Tut!
Is it possible to store the sortorder in a cookie and read it onLoad?
June 25th, 2007 at 9:54 pm
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
August 9th, 2007 at 7:56 am
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.
August 9th, 2007 at 8:00 am
Zeal, this may help you with saving to a cookie…
http://blog.tool-man.org/saving-a-reordered-list/14
September 14th, 2007 at 4:30 am
Thanks
I am looking for exactly the same thing and i like this.
Keep on posting good thing.
October 24th, 2007 at 4:09 pm
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.
October 24th, 2007 at 4:11 pm
I lied - i used the onmouseup event in the UL to run the getOrder.
October 25th, 2007 at 6:56 pm
@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
November 3rd, 2007 at 10:48 pm
.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?
November 3rd, 2007 at 10:56 pm
.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.
February 11th, 2008 at 10:07 pm
Dude,
Thanks a lot - totally kicks ass! I love fast copy paste JS to get you started in a direction. You helped a ton!
March 10th, 2008 at 11:57 am
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!
March 12th, 2008 at 12:17 pm
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?
March 13th, 2008 at 12:54 am
Does anyone know where I can find an amazing easy to config Drag and Drop Shopping Cart?
March 13th, 2008 at 12:55 am
I forgot to mention a Ajax or PHP drag and drop shopping cart?
May 3rd, 2008 at 6:04 pm
I so needed this for a backend system, thanks a lot!
June 14th, 2008 at 3:58 am
Thanks… just what i was looking for
How do you write the values it to a hidden field ?