Remote Synthesis
Search my blog:
Viewing By Entry / Main
May 01, 2009

Working with Related Sortable Lists in jQuery UI

The next in my continuing series of jQuery tutorials works again with the that is a part of jQuery UI. In my previous post, I discussed working with this effect associated with table rows. In this tutorial, I will show how to use it with two related lists that allow the user not only to sort items in each list but drag them to the other list as well. We'll cover a couple of nifty items such as ensuring that the dragged item matches the styles on the new list as well as allowing you to switch lists via double-clicking. Lastly, we'll even deal with the issue that arises when you have no items in one of the lists and you can no longer add items back.

Setting up Sortable Lists
As noted in the prior posts, setting up sortable lists is extremely easy in jQuery UI. You simply set the selector for the itrms you want to be made sortable and then call the sortable custructor like so: $("#mySortableList").sortable(). If you want to connect your list with another list or lists, you simply supply the selector for the associated items with the "connectWith" option as you can see in the code below. You can see this at work as well on the connected lists example on the jQuery UI site.

Matching Classes on Received Items
There are some events specifically associated with connected lists, such as the "receive" event which is announced when an item from an associated list is received by a new list. We will use this event to address an issue that will come up when your lists have differing styles (as in the jQuery UI example). If that is the case, the style of the prior list items remains in place even when you drag it to a new list. We can use the "receive" event to call a method (called listChanged() in our example) which toggle the classes of the received item like in the snippet below, where ui.item is the item that was moved (note: the event and ui arguments are passed naturally by the receive event):

function listChanged(e,ui) {
   ui.item.toggleClass("ui-state-default");
   ui.item.toggleClass("ui-state-highlight");
}

Switching Lists by Double-Clicking
When you have two related lists, you may not want to force someone to actually drag them across, especially when the lists get long. In this example I actually allow the user to double-click an item to append it to the end of the other list, at which point then can choose to reorder it as they want.

To do this, I first need to add a listener for the double-click event to the items on both lists like in the following line from the example below: $(".ui-state-default").dblclick(switchLists);. Since I only have two lists, I created a more generic switch lists function that determines what list the item came from and adds it to the other list. Finally, it ensures that it is styled the same as its siblings by adding and removing the necessary classes.

This took a little bit of traversing and selector trickery by first selecting the parent of the target item (i.e. the ul block) and then getting its "connectWith" option to get an array of associated items (in this case there are 2) and then filters that by the one that is not the current items parent (i.e. the other list). I then only need to append the item to the "other list" using the appendTo method and then remove its current class while adding the class that matches its siblings (i.e. the other li items).

Removing All Items from a Sortable List Some issues arise when you remove all items from a sortable list. You can see this at play in the jQuery UI example. If yuo drag all the items out of one list, you can't ever add items back. This is resolved by my double-click method but it caused a problem in that I determine the style to add based upon its siblings which, in this case, there are none

To resolve this problem, you can see some logic in my switchLists() method. What it does is, if the current target (i.e. the li item double clicked) has no siblings, it makes a clone of it and sets it to not display (i.e. display:none). This ensures that any list always has at least one item, even if it may not be visible, from which I can duplicate the proper style. Finally, once we are done adding items to the "other list" I clear out any hidden items to ensure that extraneous items don't appear in my lists when not necessary using otherList.children(":hidden").remove();. The only caveat to this method is that if you need to clear any hidden items before using serialize on these lists and saving (this example doesn't save so I don't directly address that issue).

Conclusion
Hopefully you find this useful if you end up working with related lists in jQuery. Look for more jQuery tutorials coming soon or feel free to send me ideas for future tutorials.

<html>
<head>
<title>Related Sortable Lists with JQuery</title>
<link type="text/css" href="css/smoothness/jquery-ui-1.7.1.custom.css" rel="stylesheet" />
<style type="text/css">
   .sortable {
      list-style-type: none;
      margin: 0;
      padding: 0;
      width: 300px;
   }

   .sortable li {
      margin: 0 3px 3px 3px;
      padding: 0.4em;
      padding-left: 1.5em;
      font-size: 12px;
      height: 14px;
   }

   .sortable li span {
      position: absolute;
      margin-left: -1.3em;
   }
</style>

<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="jquery-ui-1.7.1.custom.min.js"></script>
<script type="text/javascript">
function init() {
   $("[id^=sortable-]").sortable({
      connectWith: ".sortable",
      receive: listChanged
   });
   $(".ui-state-default").dblclick(switchLists);
   $(".ui-state-highlight").dblclick(switchLists);
}

function listChanged(e,ui) {
   ui.item.toggleClass("ui-state-default");
   ui.item.toggleClass("ui-state-highlight");
}

function switchLists(e) {
   // determine which list they are in
   // this works if you only have 2 related lists.
   // otherwise you will need to specify the target list
   // the other list is one that has the connect with property but isn't
   // the current target's parent
   var otherList = $($(e.currentTarget).parent().sortable("option","connectWith")).not($(e.currentTarget).parent());

   // if the current list has no items, add a hidden one to keep style in place
   // when saving you will need to filter out items that have
   // display set to none to accommodate this scenario
   if ($(e.currentTarget).siblings().length == 0) {
      $(e.currentTarget).clone().appendTo($(e.currentTarget).parent()).css("display","none");
   }
   otherList.append(e.currentTarget);
   otherList.children().removeClass($(e.currentTarget).attr("class"));
   otherList.children().addClass(otherList.children().attr("class"));

   // remove any hidden siblings perhaps left over
   otherList.children(":hidden").remove();
}

$(document).ready(init);
</script>
</head>
<body>
<div style="width:50%;float:left;">
   <ul id="sortable-list1" class="sortable">
      <li class="ui-state-highlight" id="list1-item1"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Brian Rinaldi</li>
      <li class="ui-state-highlight" id="list1-item2"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Ray Camden</li>
      <li class="ui-state-highlight" id="list1-item3"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Ben Forta</li>
   </ul>
</div>
<div style="width:50%;float:right;">
   <ul id="sortable-list2" class="sortable">
      <li class="ui-state-default" id="list2-item1"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Sean Corfield</li>
      <li class="ui-state-default" id="list2-item2"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Peter Bell</li>
      <li class="ui-state-default" id="list2-item3"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Mark Drew</li>
   </ul>
</div>
</body>
</html>

Comments
Brian Swartzfager
Another way to ensure that you can add items back into an empty list is to set a minimum height for the ul element so that (even while empty) it's large enough to receive a dragged list element. The CSS code for doing that (you can't set a "min-height" in IE) is covered in my blog post on the topic:

http://www.swartzfager.org/blog/index.cfm/2009/3/20/Using-jQuery-UI-Sortables-To-Move-Items-From-One-List-To-Another

Your double-click event for moving items between lists is indeed a good one.


Write your comment



(it will not be displayed)