Sorting Items

A task list isn't very useful if you can't move things around.  Fortunately jQuery UI includes a nifty sorting behavior that's really easy to use.

You may have noticed that we applied a "sortable" class to our list markup.  We're going to use that as a marker that identifies what can be sorted.

$(".sortable").sortable({
         placeholder: "ui-state-highlight",
});

The children of a ".sortable" element can now be grabbed and moved to a different spot within the ".sortable" element.

This is all well and good, but when you reload the page you'll notice that the cards are right back where they started.  The jQuery UI sortable behavior controls the DOM manipulation aspect of sorting, but it's up to us to update the model accordingly.  Specifically, we must update the Sequence property of the item being moved to reflect its new position in the list.

In order to do this we need to know the ListItem that the card being moved represents.  You could easily do this by adding the item's id to the template markup and using that to retrieve the item by id.  For example:

<div class="board-list-item" sys:itemid="{binding meta.id}">

The approach we've used previously is to search the DOM to determine the "binding context", which we can do here as well.  To do so we must determine the item in question when the sort starts, before it has been removed from its parent.  Otherwise, we wouldn't be able to determine the binding context since we've altered the DOM structure.  Since we're using jQuery for the UI behavior, we can also use jQuery to store the item when the sort starts, and retrieve it later when the sort completes.

$(".sortable").sortable({
	placeholder: "ui-state-highlight",
	start: function(event, ui) {
		// Retrieve the card being moved
		var cardEl = ui.item[0];
		var $cardEl = $(cardEl);
 
		// Find the item
		var item = $parentContextData(cardEl, nullnull, ListItem);
 
		// Cache off this information for later use
		$cardEl.data("item", item);
	}
});

Here we're using jQuery's data functionality to store the item so that we can retrieve it later.  Then, when the sort stops, we can set the Sequence property according to the item's new index in the list.

$(".sortable").sortable({
         placeholder: "ui-state-highlight",
         start: function(event, ui) {
                 // Retrieve the card being moved
                 var cardEl = ui.item[0];
                 var $cardEl = $(cardEl);
 
                 // Find the item
                 var item = $parentContextData(cardEl, nullnull, ListItem);
 
                 // Cache off this information for later use
                 $cardEl.data("item", item);
         },
         beforeStop: function(event, ui) {
                 var $cardEl = $(event.originalEvent.target).closest(".board-list-item").parent();
                 var cardEl = $cardEl.get(0);
 
                 // Determine item, from and to list
                 var item = $cardEl.data("item");
                 var toIndex = $cardEl.index();
 
                 if (item.get_Sequence() != toIndex) {
                          // Move the item to its new place in the list
                          item.set_Sequence(toIndex);
                 }
 
                 // Clear out cached data
                 $cardEl.data("item"null);
         }
});

At this point the card that was moved has the correct sequence, but the other cards' sequences haven't changed.  Let's add some procedural code to update the sequences of the other items.

 
// Moved up in the list
if (fromIndex > toIndex) {
         // Increment every item between the start and end
         item.get_List().get_Items().forEach(function(otherItem) {
                  if (otherItem !== item && otherItem.get_Sequence() >= toIndex && otherItem.get_Sequence() < fromIndex) {
                          otherItem.set_Sequence(otherItem.get_Sequence() + 1);
                 }
         });
}
// Moved down in the list
else if (toIndex > fromIndex) {
         // Decrement every item between the start and end
         item.get_List().get_Items().forEach(function(otherItem) {
                  if (otherItem !== item && otherItem.get_Sequence() > fromIndex && otherItem.get_Sequence() <= toIndex) {
                          otherItem.set_Sequence(otherItem.get_Sequence() - 1);
                 }
         });
}

  1. Create the javascript file board-sorting.js under scripts
  2. Insert the following snippet into scripts/board-sorting.js:
    $(function() {
    
    	function registerSorting() {
    		$(".sortable").sortable({
    			placeholder: "ui-state-highlight",
    			start: function(event, ui) {
    				// Retrieve the card being moved
    				var cardEl = ui.item[0];
    				var $cardEl = $(cardEl);
    
    				// Find the item
    				var item = $parentContextData(cardEl, null, null, ListItem);
    
    				// Cache off this information for later use
    				$cardEl.data("item", item);
    			},
    			beforeStop: function(event, ui) {
    				var $cardEl = $(event.originalEvent.target).closest(".board-list-item").parent();
    				var cardEl = $cardEl.get(0);
    
    				// Determine item, from and to list
    				var item = $cardEl.data("item");
    				var fromIndex = item.get_Sequence();
    				var toIndex = $cardEl.index();
    
    				if (fromIndex != toIndex) {
    					// Move the item to its new place in the list
    					item.set_Sequence(toIndex);
    
    					// Moved up in the list
    					if (fromIndex > toIndex) {
    						// Increment every item between the start and end
    						item.get_List().get_Items().forEach(function(otherItem) {
    							if (otherItem !== item && otherItem.get_Sequence() >= toIndex && otherItem.get_Sequence() < fromIndex) {
    								otherItem.set_Sequence(otherItem.get_Sequence() + 1);
    							}
    						});
    					}
    					// Moved down in the list
    					else if (toIndex > fromIndex) {
    						// Decrement every item between the start and end
    						item.get_List().get_Items().forEach(function(otherItem) {
    							if (otherItem !== item && otherItem.get_Sequence() > fromIndex && otherItem.get_Sequence() <= toIndex) {
    								otherItem.set_Sequence(otherItem.get_Sequence() - 1);
    							}
    						});
    					}
    				}
    
    				// Clear out cached data
    				$cardEl.data("item", null);
    			}
    		});
    	}
    
    	registerSorting();
    
    	$(window).bind("board:listadded", registerSorting);
    
    });
    				
  3. Append the following code snippet in Views\Home\Index.cshtml under the list of script references:
    <script src="@Url.Content("~/scripts/board-sorting.js")"></script>