jQuery Sortable with ColdFusion

This post is more than 2 years old.

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.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Bill posted on 2/26/2009 at 9:18 AM

Hi Ray,

You'll run into trouble if you setup more than one list of items to sort between as the serialize method will not pick up the parent 'ul' in it's output.

Just a tip if you get more advanced with your lists..

Comment 2 by Raymond Camden posted on 2/26/2009 at 9:20 AM

I may be slow - but what do you mean more than one list of items to sort? You mean 2 sortable lists? Wouldn't they have different IDs?

Comment 3 by Scott Stroz posted on 2/26/2009 at 5:16 PM

I have been playing aroudn with sortables too.

I like that you can also format your HTML like this

<div id="beers">
<div>Yuengling</div>
<div>Tenentt's</div>
<div>JW Dundee's Homey Brown</div>
</div>

And still have the innder <div>s be the items that can be sorted.

Comment 4 by Jon Alsbury posted on 2/26/2009 at 6:07 PM

Ray, just a quick note to say how useful I've found your series of posts concerning jQuery - I've tried a few AJAX/JS libs over the years before settling on jQuery. In recent weeks, perhaps spurred on by what I've been reading here, it's become an obsession! :-) Please keep up the good work. J.

Comment 5 by OTS posted on 2/26/2009 at 8:21 PM

Ray,
I think Bill is saying that if you have a nested lists, there may be a problem, just a guess

Comment 6 by Raymond Camden posted on 2/26/2009 at 8:24 PM

I didn't play with nested lists. If that did work fine - I could see it being a bit confusing though. May be a bit too much for the casual user.

p.s. Working on a Progress Bar demo today.

Comment 7 by Brian Swartzfager posted on 2/26/2009 at 9:15 PM

Having taken a stab at sorting nested lists in a production app, I can tell you that while it's possible to do under the right conditions, it's a pain to do and it was hard for my users to tell where they needed to drag the list item in order to drop it into another nesting level.

For that situation, I ended up replacing the nested sort with a technique (also powered by jQuery) I originally developed for reordering items on a web page when using an iPhone or iPod Touch (since drag-and-drop isn't really an option there) that lets my users move an item with just two mouse clicks. You can find the original blog post about the technique here:

http://www.swartzfager.org/...

Comment 8 by Joel Cox posted on 2/26/2009 at 10:56 PM

While you're on the 'sortable' topic, there's a nifty tablesorter plugin at http://tablesorter.com/docs/ that adds clickable sort headers to standard HTML tables. It will even zebra-stripe the tables for you in the process.

I've been using jQuery a lot in my applications, also, I find it very easy to use, even insanely simple at times. I did some earlier work with Spry, but jQuery just seems so much easier to code and maintain.

Comment 9 by Jonny Shaw posted on 2/27/2009 at 1:33 AM

I wonder is you could use this to re-order the links on my fisheye menu?
http://home.cfproject.co.uk

Jonny

Comment 10 by Raymond Camden posted on 2/27/2009 at 2:01 AM

@Joel Cox: Just checked out the table sorter - that is pretty hot.

Comment 11 by larry c. lyons posted on 2/27/2009 at 9:57 PM

Hey Ray,

Just thought I'd point out that this is a fairly sweet table sorter:
http://kryogenix.org/code/b...

using what the author calls unobtrusive javascript. its very customizable and fairly fast.

regards,
larry

Comment 12 by Kamil posted on 1/5/2010 at 4:33 PM

Very nice script!

I have a problem with database. Could you please provide a little CF code, which would save reordered list into database. I can't figur eit out.

Plaese help! Many thanks!

Comment 13 by Raymond Camden posted on 1/5/2010 at 7:27 PM

You would modify line 11 in the last code listing to be an insert query.

Comment 14 by GKnight posted on 3/12/2010 at 10:36 PM

OK, first off, I am a javascript dummy. I get your comment about changing line 11 to an insert query, but what if I want it to be an update query?

Comment 15 by Raymond Camden posted on 3/13/2010 at 12:08 AM

Unless I'm misreading you, wouldn't you just put in a cfquery?

Comment 16 by GKnight posted on 3/13/2010 at 1:02 AM

My apologies, I wasn't very detailed in my question. I thought maybe you were going to use a Jedi mind trick to know what I was thinking :) My initial thought on this scenario is having a database with a ID, title and sortValue. Once the database call is made I would assign list id with the value of the sortValue or the ID? When the data is returned to the cfc and the UPDATE query executes, what would you use in the WHERE clause, the ID of the list to the sortValue? I also get a 'java.lang.String cannot be cast to java.util.Map' error with my current attempt....like I said, I am a javascript dummy.

Comment 17 by Raymond Camden posted on 3/13/2010 at 2:52 AM

Well remember you are passed the ID the record and the new sort, right? So your update would simply do something like so

update tblFoo
set sortorder = #NEWORDER#
where id = #THEID#

neworder would be the new sort order, and THEID the primary key. In my example above, it's #id# and #x# in the cflog. Oh, and you would use cfqueryparam of course.

Comment 18 by John W posted on 4/9/2010 at 9:17 PM

very cool - I had 500 errors not passing order using jQuery 1.4.2 but no issues with 1.3.1 (from your download)... any thoughts?

Comment 19 by Raymond Camden posted on 4/10/2010 at 8:24 PM

500 errors? I assume you mean HTTP Status Code 500. That's an error. As to why, it depends on what the root error is. :) If you tried jQuery 1.4.2,, also ensure you are using the latest jQuery UI library.

Comment 20 by Nando posted on 5/26/2010 at 6:39 PM

I'm also having trouble getting sortable to work using jQuery 1.4.2 with the latest jQuery UI library and it seems to come down to how "toArray" passes the ids.

In jQuery 1.3.1, toArray produces the parameters

sortedIdArr 2504
sortedIdArr 2471
sortedIdArr 2472

Source
sortedIdArr=2504&sortedIdArr=2471&sortedIdArr=2472

(From Firebug)

and now with 1.4.2 it's change to

sortedIdArr[] 27
sortedIdArr[] 26
sortedIdArr[] 28

Source
sortedIdArr%5B%5D=27&sortedIdArr%5B%5D=26&sortedIdArr%5B%5D=28

The brackets seem to break my coldfusion code that loops through the list of values. I get an error that says "Element SORTEDIDARR is undefined"

Comment 21 by Joel Cox posted on 5/26/2010 at 6:46 PM

This was an "improvement" to jQuery 1.4.2 to work well with "modern" scripting languages. They call it "deep serialization". I call it "breaking things that weren't broke."

add

jQuery.ajaxSettings.traditional = true;

to your document ready function.

Comment 22 by Nando posted on 5/26/2010 at 7:17 PM

Joel!

YES!

Thank you. You just made my day.

Nando

Comment 23 by Brittany Hunter posted on 5/28/2010 at 6:12 PM

JOEL COX YOU ARE MY HERO
i fought with this problem for hours yesterday. Thanks for sharing your solution.

And thanks Raymond, for this excellent tutorial (and the rest of your blog as well).

Comment 24 by jLynn posted on 9/29/2010 at 10:32 PM

This traditional ajaxSettings saved my butt too! Raymond, I love the sortable feature but was having to use old version of jQuery to implement. Thanks for the tip Joel!!