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.


  • The first item
  • Then the second one
  • Of course then comes the third
  • And finally, the fourth
Click and drag these

left arrow

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.

34 Comments

  1. Jason says:

    Nice post! I’ll keep this in mind next time I need this. 🙂

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

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

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

  5. Jason says:

    Does this work with multiple lists on one screen?

  6. b says:

    hey, thanks for that!

  7. Chris says:

    Nice Tut!

    Is it possible to store the sortorder in a cookie and read it onLoad?

  8. Zeal 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 🙂

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

  10. Mobile Phones says:

    Zeal, this may help you with saving to a cookie…

    http://blog.tool-man.org/saving-a-reordered-list/14

  11. Bee Kay says:

    Thanks

    I am looking for exactly the same thing and i like this.
    Keep on posting good thing.

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

  13. A random person says:

    I lied – i used the onmouseup event in the UL to run the getOrder.

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

  15. Lethal 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?

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

  17. Ron 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!

  18. phalanx 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!

  19. Kevin 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?

  20. Enfopedia says:

    Does anyone know where I can find an amazing easy to config Drag and Drop Shopping Cart?

  21. Enfopedia says:

    I forgot to mention a Ajax or PHP drag and drop shopping cart?

  22. Frederik says:

    I so needed this for a backend system, thanks a lot!

  23. Dan says:

    Thanks… just what i was looking for 🙂
    How do you write the values it to a hidden field ?

  24. Artan says:

    Thanks a lot… Very helpful tutorial. I was looking to do just this.

  25. Jess 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.
    Thank you.

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

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

  28. Richard Sweet says:

    Great easy to follow tutorial, thanks.

    One thing. – When you use your own propriety attribute (recordid), why not just use id instead?

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

  30. Trevor 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!

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

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

  33. dave 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.)

  34. Pleski says:

    I'm afraid it doesn't work for me. I'm on IE 7