Make Table Rows Sortable Using jQuery UI Sortable

So you want to make table rows sortable using jQuery UI? Luckily, the Sortable interaction does most of the work for you.

But there’s a catch: one problem that I ran into when implementing this (with UI version 1.7) was the cell widths of the row would collapse once I started dragging it.

Suppose you have a table of data, like this one:

<table id="sort" class="grid" title="Kurt Vonnegut novels">
	<thead>
		<tr><th>Year</th><th>Title</th><th>Grade</th></tr>
	</thead>
	<tbody>
		<tr><td>1969</td><td>Slaughterhouse-Five</td><td>A+</td></tr>
		<tr><td>1952</td><td>Player Piano</td><td>B</td></tr>
		<tr><td>1963</td><td>Cat's Cradle</td><td>A+</td></tr>
		<tr><td>1973</td><td>Breakfast of Champions</td><td>C</td></tr>
		<tr><td>1965</td><td>God Bless You, Mr. Rosewater</td><td>A</td></tr>
	</tbody>
</table>

Your first attempt to make it sortable might look like this:

$("#sort tbody").sortable().disableSelection();

And it actually works, but there is a bit of a problem. The cell widths seem to be collapsing once you start dragging a row (notice how close the “C” cell is to the “Breakfast of Champions” cell). It looks like this:

Sortable row collapsed widths

The problem has to do with the helper object. The helper object is basically the DOM element that follows the cursor during the drag event. When it is created by default, the cells collapse to the size of the content inside of them.

You can specify a function that returns a jQuery object to create a custom helper object. By creating a function that will keep the cell widths consistent, this problem can be fixed.

// Return a helper with preserved width of cells
var fixHelper = function(e, ui) {
	ui.children().each(function() {
		$(this).width($(this).width());
	});
	return ui;
};

$("#sort tbody").sortable({
	helper: fixHelper
}).disableSelection();

Now it works as expected:
Sortable row fixed

Keep Original Variable State Between Event Binding and Execution – JavaScript

Also known as: Binding Events Inside of a Loop with jQuery

Sometimes I want to create a closure that accesses a variable in the state that it was in when the closure was created. I don’t want to know what the current value is, I want to know what the value was when I created the function. I may want to do this when binding an event to a DOM object, or when executing a function inside of setTimeout().

The Problem

Here is an example of the problem using the jQuery library (consider what will happen when you click on any of the li elements):

	$(function() {
		$("body").append("<ul id='container'></ul>");

		for (var i = 0; i < 5; i++)
		{
			var $item = $("<li />").text("Item " + i);
			$("#container").append($item);

			$item.click(function() {
				alert("You clicked number " + i);  // always "You clicked number 5"
			});
		}
	});

The event handler will always use the final value of the variable “i”, which will be 5 in this case. One way to fix this problem is to create a function and pass “i” as a formal parameter:

Solution 1 – Traditional Function

	function bindItem($item, ind) {
		$item.click(function() {
			alert("You clicked number " + ind);  // Works as expected
		});
	}

	$(function() {
		$("body").append("<ul id='container'></ul>");

		for (var i = 0; i < 5; i++)
		{
			var $item = $("<li />").text("Item " + i);
			$("#container").append($item);
			bindItem($item, i);
		}
	});

Since this passes the variable as a parameter to a function, the variable “ind” will always remain the same, even if “i” changes after the function finishes execution. The function bindItem() creates a new scope. Any variables (passed as parameters or declared inside the function) will reserve their own space in memory, and will not be affected by the state of the variable ‘i’. Thanks to reddit user Mych for correcting me.

This is what I wanted in the first place, but we may not want to have a named function here – if we used a closure we would still have access to variables in the same scope (like $item) without having to pass them as parameters to bindItem().

This does the trick:

Solution 2 – Self Executing Function

	$(function() {
		$("body").append("<ul id='container'></ul>");

		for (var i = 0; i < 5; i++)
		{
			var $item = $("<li />").text("Item " + i);
			$("#container").append($item);

			(function() { // Closure here here instead of "bindItem()"
				var ind = i;
				$item.click(function() {
					alert("You clicked number " + ind); // Works as expected
				});
			})(); // Execute immediately
		}
	});

This is a trick I first saw in the jQuery plugin development tutorial, in the custom alias section.

Declaring the closure creates a new scope in the loop. Any variables (such as ‘ind’) passed as parameters or declared inside of this scope will reserve a new space in memory that is not a reference to ‘i’.

The extra parentheses at the end of a block like this “(function() {…})()” cause the closure to execute immediately .

These are just a couple ways to solve the problem of variable state changing between event binding and execution. Closures are a powerful language feature that makes JavaScript very flexible, but when execution of them is being delayed via a timeout or event handler, it is important to keep track of the variables that they are closed over.

Extending jQuery to Select ASP Controls

If you have worked with JavaScript in an ASP.NET Web Forms environment, you almost certainly have been frustrated that this markup:

	<asp:TextBox runat="server" ID="txtPhoneNumber" />

renders out as something like:

	<input type="text" id="ctl00_ctl00_ctl00_main_Content_txtPhoneNumber"
		name="ctl00$ctl00$ctl00$main$Content$txtPhoneNumber" />

The fastest and easiest way to get a reference to a DOM element in JavaScript is using the ID attribute and document.getElementById(). Unfortunately, the ID attribute generated by the server is unpredictable and based on the server naming containers. There are a couple ways of that I have previously dealt with that problem, but both have problems.

Old Solutions

  1. Accessing the ClientID of the server control

    If you use inline code inside <% %> tags, you can access the ClientID directly from the .aspx page.

    	var goodID = '<% txtPhoneNumber.ClientID %>';  // = 'ctl00_ctl00_ctl00_main_HeaderTabs_txtPhoneNumber'
    	var badID = 'txtPhoneNumber'; // The text box does not have this ID, it will not work
    	var uglyID = 'ctl00_ctl00_ctl00_main_Content_txtPhoneNumber'; // DO NOT hardcode the generated ID into your code!
    

    This is not ideal because you cannot reference the ClientID from outside of the page, so you cannot keep your JavaScript in external files.

  2. Setting attributes on control and accessing with jQuery selectors

    jQuery has an excellent selector API that can easily grab an element if you know attributes on it. So, if there were a couple controls defined as:

    	<asp:TextBox runat="server" ID="txtPhoneNumber" CssClass="txtPhoneNumber" />
    	<asp:TextBox runat="server" ID="txtAddress" ClientID="txtAddress" />
    

    You could access them with jQuery:

    	$(".txtPhoneNumber").keyup(...);   // This works
    	$("[ClientID='txtAddress']").keyup(...);   // This works
    
    	$("#txtPhoneNumber").keyup(...);   // This still DOESN'T work
    	$("#txtAddress").keyup(...);   // This still DOESN'T work
    

    This is not ideal because it requires adding extra attributes onto any server control that you want to access with JavaScript.

Original jQuery Solution

I first happened upon a solution to the same problem over on John Sheehan’s blog. This looked promising, but did not work the current latest release of jQuery (1.3). Another contributer to this blog, Tim Banks, updated the code in to work with the newer version.

However, I found an error in Internet Explorer and a more reliable way to get the ClientID would be to use the jQuery attribute selector and match based on an “id” attribute that ends with the Server ID. So, I wrote a JavaScript function called $asp that returned a jQuery collection. This function worked well and was implemented in the latest Foliotek release.

	function $asp(serverID) {
		return $("[id$='" + serverID+ "']");
	}

	// Once this function is included, you can call it and get back a jQuery collection
	$asp("txtPhoneNumber").keyup(...);

Updated jQuery and Sizzle Solution

It seemed like it would be better if the solution actually extended the selector engine rather than using a function to select objects. This would fit better into a jQuery development paradigm, allow more complex selectors, and also allow the selector to be used in other library functions, like “filter” or “find”. Also, it would be nice to be able to use the tag name in the selector to give a performance improvement, since getElementsByTagName is a fast operation that will narrow the element collection.

So, I returned to the selector, fixed the IE bug and made sure it worked with the now latest version of jQuery (1.3.2). This short function extends the Sizzle selector engine to return an element that has an ID that ends with the ID that is passed in the parenthesis after the “:asp()” selector.

	// Include this function before you use any selectors that rely on it
	jQuery.expr[':'].asp = function(elem, i, match) {
		return (elem.id && elem.id.match(match[3] + "$"));
	};

	// Now all of these are valid selectors
	// They show why this method has more functionality than the previous $asp() function.
	$(":asp(txtPhoneNumber)")
	$("input:asp(txtPhoneNumber):visible")
	$(":asp(txtPhoneNumber), :asp(txtAddress)")
	$("ul:asp(listTodos) li")
	$("#content").find("ul:asp(listTodos)")

This function allows access to server controls without adding additional markup and without the JavaScript existing on the .aspx page.


Note, there is a potential limitation if you had one control with the ID=”txtPhoneNumber” and another with ID=”mytxtPhoneNumber”. Both elements end with “txtPhoneNumber” and the selector would not necessarily return the correct value. This solution is not perfect in that sense, but the benefits it provides over other methods (cleaner markup and ability to use external JavaScript files) make it a good alternative.

New In Foliotek: Inline File Editing

Foliotek - Zoho

In Foliotek, we have some users who do a lot of file editing. Basically, they download, edit, and reupload files while evaluating student work.  The logistics of this can turn into a problem if there are many files to edit, and can be made worse if the files are very large and they share similar file names.

It would be a much more efficient workflow if they could edit the files and add comments directly in the browser.  In addition to that, in-browser document editing would make the basic file editing easier for students and faculty who are using the system to store and manage their files.

So, we decided to look into some JavaScript based document editors. Zoho stood out because of the APIs provided for remote document editing, in which the files are never saved on their servers. We have been working to incorporate an instance of the Zoho editor into Foliotek.  This will allow editing spreadsheets, word documents, and plain text files using their remote API. Hopefully this will help boost the productivity of our users and improve their experience using our software.  We are looking forward to feedback!

Also, I did a write up about one of the technical challenges that was encountered when adding this feature: generating a multipart form post in C#.