Revisiting Table Drag and Drop with jQuery and ColdFusion

Last April, I posted an article on how I implemented table row drag and drop functionality using jQuery and ColdFusion. While it was relatively simple, since the time of my original article, I ran into a number of issues with my solution and rethought the whole thing.

The Problems

  1. My solution was based on a jQuery plug-in called TableDnD, which seems to have lost any further development. I found the source up on Google Code, but there's been absolutely no activity since the project was created in June 2008.
  2. In the original solution, after each drop of a table row, an ajax call was made to update the database. While this worked great for sorting a few things, if you wanted sort a bunch rows, the numerous ajax calls just got plain annoying, and (truthfully) probably didn't scale very well.
  3. Later implementations of my project included dynamically adding rows to the table. This pretty much broke the ability of TableDnD to do its thing most of the time. At best, you couldn't move new elements above existing rows.
  4. Finally, it was hard to visualize what you were dragging – it always stayed confined to the table. Additionally, because it was a table, it wasn't always obvious you could drag and drop.

The Solution

As it turned out, the best solution for what I wanted to do didn't involve tables at all, but rather an unordered list and the jQuery UI library. Granted, this really isn't table drag and drop (per se), but just bear with me and I think you'll see what we've done is much cooler than the previous solution. The jQuery UI library includes some basic drag and drop functionality though the use of sortable lists. A simple example of how this works can be found on the jQuery UI website.

What we can do then is create a sortable list, each list item containing a hidden field with the necessary information we need to sort everything. When the form is submitted, it is submitted along with all those hidden fields. The key lies in form.FieldNames, which as most CFers know is a comma-delimited list of all the form fields submitted. It just so happens they are submitted in the order in which they appear on the form.

<form action="index.cfm" method="post">
	<cfoutput>
		<!--- This UL will contain the items we want to sort --->
		<ul id="SortItems" class="sortable">
			<!--- Counter to ensure each hidden field has a unique name --->
			<cfset intCounter = 0 />
			<!--- Loop over list of items to sort -- in real life this is probably a query --->
			<cfloop list="#lstSortItems#" index="strIndex">
				<cfset intCounter++ />
				<li style="cursor: move;">#strIndex#<input type="hidden" name="SortItem_#intCounter#" value="#strIndex#" /></li>
			</cfloop>
		</ul>
	</cfoutput>
	<input type="submit" value="Submit" />
</form>
<!--- This JS enables jQuery UI sorting (see included JS above, they're required) --->
<script type="text/javascript">
	$(function() {
		$("#SortItems").sortable({
			revert: true
		});
	});
</script>

Finally, we loop over the contents of form.FieldNames, looking for our hidden fields using each to update the order of our list. I've attached a very simple example of how this works. Presently, the value of the hidden field is a simple text label, but in real-life would probably be a primary key of whatever it is you're sorting.

The best part these lists can be styled however you like. For example a bit from application I've been working on. See how it looks like a table?

An even better part is because we're using unordered lists and jQuery, it's super easy to add and remove elements on-the-fly. Check out .append() and .remove().

Download attachment

No Comments

Two Types of Developers

There are two types of developers:
[1] those who think arrays should be one-based
[1] those who think arrays should be zero-based

No Comments

How much do I love Windows Update? Let me count the ways...

Yea, this took some time.

1 Comment

Multiple Column Output

Displaying a query result in one or more columns is a pretty common task for the CF developer. However, for some reason, this is one of those things I can never remember how to do. So, in lieu of committing this to memory, I'm posting this here.

<cfset intColumns = 3 />
<table>
    <tr>
        <cfoutput query="qryData">
            <td style="width: #int(100/intColumns)#%;">
                --output goes here--
            </td>
            <cfif qryData.CurrentRow mod intColumns is 0></tr><tr></cfif>
        </cfoutput>
    </tr>
</table>

No Comments

To catch a shooting entityDelete()

I’ve been using ColdFusion 9’s ORM features on a couple projects recently. That coupled with Sean Corfield’s excellent FW/1 framework has made short work of a lot of time-consuming tasks. Anyway, I’ve ran into a little bit of a snag around referential integrity.

If I use entityDelete() function to delete a record and there’s a constraint violation (in this case, a song  category that’s associated with a song), I get the following error:

coldfusion.orm.hibernate.HibernateSessionException: Cannot delete or update a parent row: a foreign key constraint fails (`databasename`.`songs`, CONSTRAINT `songs_ibfk_3` FOREIGN KEY (`SongCategoryID`) REFERENCES `songcategories` (`SongCategoryID`)). (Database)

That’s obviously to be expected. Constraints are put in place to prevent such things. However, for whatever reason I can’t trap this error using try/catch.

<cfscript>
	transaction action="begin" {
		try {
			entityDelete(local.objSongCategory);
		} catch (Any e) {
			transaction action="rollback";
		}
	}
</cfscript>

I can't think of any reason why this cannot be caught. Anyone have any thoughts?

1 Comment