NOTE: As of July 12, 2009, this blog has been discontinued and replaced by the new Thought Delimited blog. All of the entries in this blog can be found there, along with new posts.

Using jQuery UI Sortables To Move Items From One List To Another

During my most recent project, my clients asked me to build a web-based tool that would help them place volunteers into various standing committees. Placements would be made based on the preferences of the volunteers (who were asked to choose up to three committees they would like to serve on), the vacancies created in each committee by outgoing members, and the desire to have a diversity of units and divisions represented in each committee.

I decided pretty quickly that the most natural way to represent this placement process on a web page would be to let them "physically" move a volunteer into a committee. I had built similar tools before (even before I started using jQuery), but never with more than two lists or collections, so I went to the jQuery UI site to see what my options were.

I started with the most obvious place to start, the Droppable interaction, but soon realized that a better choice for this task was the Sortables interaction.

The primary focus of the Sortable interaction is to let you reorder a collection of DOM elements by dragging them up-and-down through the collection. It's extremely easy to implement in its most basic form. If you wanted to make all of the <li> elements in a <ul> with an "id" attribute "listA" sortable, you can do it in one line:

$(document).ready() {
  $("#listA").sortable();
});

...you can see that code in action on the home page of the Sortables interaction.

What I discovered was that it was almost as easy to connect one Sortable list to another, so that in addition to being able to move reorder items within each list, you could drag items from one list to the other, simply by using the "connectWith" option:

$(document).ready() {
  $("#listA").sortable({
    connectWith: ['#listB']
  });

  $("#listB").sortable({
    connectWith: ['#listA']
  });
});

...In the code above, the first sortable() function call makes listA sortable and uses the "connectWith" option to allow items from listA to be dragged into listB. The second sortable() function call lets you sort items in listB and drag items from listB over to listA (even items that originally belonged to listA). If you wanted the movement to only go in one direction (from listA to listB but not back again), you could leave out the "connectWith" option for listB.

Again, the jQuery UI site has a nice ready-made demo of this.

All well and good, but right now all this code does is create the visual effect of moving an item from one list to another. Actually recording the fact that a particular item was moved from one list to the other requires more code, code that is invoked whenever such a move takes place. Being the smart guys and gals that they are, the jQuery UI team built a couple of custom events into the Sortables interaction so you can run additional functions when a certain event has taken place. For my purposes, I only needed to utilize two of these events: receive and remove:

$(document).ready({
  $("#listB").sortable({
    connectWith: ['#listA'],
    receive: function(event, ui) {
      //Run this code whenever an item is dragged and dropped into this list
      var itemText= ui.item.text();
      alert(itemText + " just joined this list");
    },
    remove: function(event, ui){
      //Run this code whenever an item is dragged and dropped out of this list
      var itemText= ui.item.text();
      alert(itemText + " just left this list");
    }
  });
});

...The code is, for the most part, self-explanatory, save for one line (repeated twice):

var itemText= ui.item.text()

The ui object is a "prepared" object created by jQuery UI that holds a number of objects and data associated with the event that just took place. The item object within the ui object represents the item that was moved in or out of the list, so I can treat it as a jQuery object and retrieve the text of the item using the standard text() function. You can find a full list of the data contained in the ui object by clicking on the "Overview" tab at the bottom of the main Sortables interaction web page.

I created my own demo page to illustrate how this code works (with a homage to the Fox TV show "Fringe"):

http://www.swartzfager.org/blog/demoFiles/connectedSortables/connectedSortableEvents.cfm

Two things worth mentioning at this point:

  • If one of your lists starts off empty, or if there's any chance that a user might remove all of the items from a list and then try to put items back into the now-empty list, you need to set a minimum height for that <ul>, so that even when empty, the <ul> is large enough to accomodate a single list element. In Firefox and Safari, you can set the minimum height using the min-height CSS style, but if you have to support IE 7, you'll have to add two additional height styles (like so):
    • min-height:50px;
      height:auto !important;
      height:50px;
  • In my demo, you'll notice that as you drag a person out of the Candidates list, the list item's width gets shrunk to the width of the longest unbroken string of text in the list item. I'm not sure why it does that, but you can negate that effect by either defining a set width for the <li> elements, or by defining a "helper" with a set width (a helper is a visual representation of the item being moved, using something graphical like an icon).

So, armed with this knowledge about the Sortables interaction, I was able to build the tool required by my clients. Every time a volunteer was moved into or out of a committee, the receive or remove event would make an AJAX call to update the volunteer's record (either providing the id of the committee they were placed in or removing it), and it would run a function that updated the vacancy count for that committee specific to that type of person and counted the overall number of vacancies for that committee (to determine if the committee had been "filled"). I also added a few toggles allowing them to hide extraneous information when they only wanted to see the data pertinent to doing placements for a particular committee. The final challenge was to scale everything so that the tool could be viewed with a projector, so that the members of the group responsible for making the placements could work on it collaboratively.

I wasn't comfortable sharing an exact copy of the tool I created, but I have posted a facsimile that's fairly close to it, minus any real-life data and any AJAX calls to save the placements between page reloads. You can view it here (Note: it doesn't work in IE 7):

http://www.swartzfager.org/blog/demoFiles/connectedSortables/volunteerPlacement.cfm

...I provided instructions at the top, but quite honestly I think most IT-inclined folks could figure it out without them.

It's just one example of some of the really cool (yet practical) user interfaces made possible with jQuery and jQuery UI.

Related Blog Entries

Comments
Great demo. I'll definitely have to play around with this sometime.
# Posted By Todd Rafferty | 3/21/09 7:50 AM
Could this work as a heiarchy somehow?
# Posted By Jon | 3/31/09 9:08 AM
@Jon: I just did a REALLY quick test of that (where you have a parent <ul> with an <li> element that contains another <ul> list, and use the connectWith setting to connect the <ul> lists to each other). It let me move items from the parent list to the child list, but not from the child list to the parent list.
# Posted By Brian Swartzfager | 3/31/09 5:02 PM
That's a shame. I am looking for something where my clients can decide what items to have in their front end menus and I thought that would be a good approach allowing them to create heirarchies. Not sure how else to do it.
# Posted By Jon | 4/1/09 4:10 AM
@Jon: It didn't seem right to me that it wouldn't work, so I tried it again this morning.

Turns out I was wrong: it will let you move items from the child list to the parent list...but I had to drag the child item around in the parent list for a while before jQuery would react and shift the parent items to make room for the child element (which is why I thought it wasn't working before).

So while it does work, it doesn't seem to work too well: I don't think a user is going to be willing to wait to be allowed to drop that child item.

But I'd say try it out for yourself, and see if maybe there's some trick to getting it to work right: maybe see if adding borders around the list elements will help provide a visual cue for where an item needs to be dragged to get it to drop.
# Posted By Brian Swartzfager | 4/2/09 8:59 AM
Oh, man, thanks for this tip. I was trying to figure out a way to allow users to create an album tracklist by dragging songs from a master list to an album list, and then arrange the tracks by sorting them in the album list. I was banging my head for like four hours last night. Your solution works perfectly.

A useful trick, by the way, if you're trying to send a list of items via POST as an array to PHP: give each list-item a checkbox input with the value of the item, like so:

<li><input type='checkbox' name='items[]' value='1' /> The Item Name</li>

Then, use this receive option in your .sortable declaration for your original list:

   receive: function(event, ui) {
      
      ui.item.find('input').attr('checked',false);
      
      }

and this one for your target list:

   receive: function(event, ui) {
      
      ui.item.find('input').attr('checked',true);
      
      }


Then give the input a style of 'display:none'. When you submit your form, only the items in the target list will be returned as an array...sorted into the proper order!

I hope that makes sense, and might be useful. Thanks again for the great tip!
# Posted By Joshua Ellis | 1/25/10 12:08 AM
@Joshua: Glad to hear my post was useful. And thanks for the tip.
# Posted By Brian Swartzfager | 1/25/10 8:09 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.1.004.