Earlier today my coworker mentioned the need for a way to easily move items up and down on a web page. In this case the idea was to sort a list of documents. We've probably all done this before. You list out each item and the one on top has a down arrow, the one on the bottom has an up arrow, and all the rest have both up and down controls. Turns out - and no big surprise here - there is actually a cool little jQuery utility to make this a bit simpler - the Sortable control.

If you follow the link to the demo (and please do, it's rather slick!) you will see how nice this control works. Instead of slowly clicking items up and down, a user can simply drag and drop the order they want. A big +1 for usability here (in my opinion). What isn't so obvious though is how you persist these changes. Let's dig a bit into the control and I'll show you how you can tie it to ColdFusion.

First, let's look at a simple version that just does the sorting:


<html>

<head>
<script src="jquery-1.3.1.js"></script>
<script src="jquery-ui-personalized-1.6rc6.js"></script>
<script>
$(document).ready(function(){
    $("#sortable").sortable();
  });
</script>
</head>

<body>

<h1>Test</h1>

<ul id="sortable">
	<li>First</li>
	<li>Second</li>
	<li>Third</li>
</ul>

</body>

</html>

I absolutely love this. Seriously. I want to form a domestic partnership with jQuery and never look at another framework again. You can see a demo of this here. But this is just a front end demo. Simple and sexy, but we need something real, and a lot of times that is where things can break down. I began by making the data dynamic.


<cfset data = queryNew("id,title","integer,varchar")>
<cfloop index="x" from="1" to="5">
	<cfset queryAddRow(data)>
	<cfset querySetCell(data, "id", x)>
	<cfset querySetCell(data, "title", "Title #x#")>
</cfloop>

Normally that would be set in my controller code or a CFC at least, but you get the idea. I then changed my UL/LI:


<ul id="sortable">
	<cfoutput query="data">
		<li id="item_#id#">#title#</li>
	</cfoutput>
</ul>

Ok, not rocket science, but you get the idea. I then added a button:


<input type="button" id="saveBtn" value="Persist">

and modified my document.ready:


$(document).ready(function(){
    $("#sortable").sortable();
    $("#saveBtn").click(persist)
  });

My persist function will take care of saving the data. The docs for Sortable are pretty extensive. What we want is the serialize function. As you can guess, it will serialize the sortable data. It does this by grabbing the id values from the items you had marked as sortable. My persist function looks like so:


function persist() {
	console.log('running persist....')
	var data = $("#sortable").sortable('serialize')
	console.log(data)
}

When run, the console reports:

item[]=3&item[]=2&item[]=1&item[]=4&item[]=5

Kind of an odd format. You can change it up a bit by passing additional parameters to the sortable call, but you get the basic idea. A demo of this version may be found here. Obviously you need Firebug installed and open to see the console messages.

Alright, so let's take it a step further. As I said, I thought that format was a bit odd. Sortable supports serializing to arrays as well:


var data = $("#sortable").sortable('toArray')

and we can tie this to an Ajax call


$.post('data.cfc?method=saveData',{order:data},function(res,txtStatus) {
		console.log(txtStatus)
	})

At this point I ran into a bit of trouble. I forgot that you can't send a complex data structure 'as is' over the wire. jQuery was smarter than me in this case and simply converted the data back into a list, much like the first serialize example. This time though it was just a list of values:

item_N,item_X,item_Z

My CFC would have to parse the list and note both the position and the ID value from each item:


<cffunction name="saveData" access="remote" returnType="void" output="false">
	<cfargument name="order" type="any" required="true">
	<cfset var x = "">
	<cfset var id = "">
	<cfset var item = "">
	
	<!--- loop through and make a new order --->
	<cfloop index="x" from="1" to="#listLen(arguments.order)#">
		<cfset item = listGetAt(arguments.order, x)>
		<cfset id = listGetAt(item,2,"_")>
		<cflog file="ajax" text="setting id #id# to position #x#">
	</cfloop>

</cffunction>

Normally this would actually run a query, but I think you get the idea. I've included my sample code as an attachment.

I have to admit - I thought the 'interactions' section of jQuery UI wasn't that exciting, but I'm beginning to see some real benefit here.

Download attached file.