Let me apologize for the title. I tried like heck but I couldn't find a better title for the issue I'm talking about. Personally I blame the apple pie. Let me describe the problem and how I used ColdFusion 8 Ajax features to solve it. If folks have a better title for the blog entry, I'll be sure to use it in the Spry/jQuery and maybe Ext versions I have planned.
Ok, so what in the heck am I talking about? Imagine a simple form that represents an entity in your database. For example, one project at RIAForge (Yes, it's still up). Projects have many different fields, most of which are simple text based fields like name and description. Some fields are pointers to other database tables. Projects can exist in categories so my form has an HTML select field for choosing related categories. Projects also have owners and this is where my main problem comes in. RIAForge currently has over 4,500 users. If I want to build a 'typical' way to edit the owner for a project I'd have a select drop down with 4,500 options. That probably would work since most of us have cable modems, but it's going to be a bit insane to actually use. That's why I actually don't change owners at RIAForge. I don't think anyone has ever asked and I realized early on that this would be a problem. So what could I do if I actually did want to let people change owners for a project?
I decided my first solution to this problem would use ColdFusion 8's built-in Ajax features only. I decided one simple way to handle it would be to display the current owner as plain text, but to then let you click a link to launch a browser of users. This would make use of cfwindow and have built-in pagination. Here is a screen shot of the final product. This is the form as it initially loads:
When you click Select User, the window control is activated:
And because I love me some gratuitous Flash, here is a screencast of the solution in action:
Note - the embed below is working oddly. I haven't used Jing in a while. Click in the triangle in the upper left hand corner to start the movie.
Ok, so whats going on here? The main page, test_largeds.cfm, is a faked out form. I wanted something folks could download and play with (download link at the bottom) so I didn't use a real database. Most of the code is vanilla form, so I'll dump it here and explain the interesting parts.
<cfparam name="form.name" default="Some Value">
<cfparam name="form.foo" default="Some Other Value">
<cfparam name="form.useridfk" default="1">
<cfdump var="#form#" label="Form" />
<cfoutput>
<form action="#cgi.script_name#" method="post">
<table>
<tr>
<td>Name:</td>
<td><input type="text" name="name" value="#form.name#" /></td>
</tr>
<tr valign="top">
<td>Owner:</td>
<td>
<input type="hidden" id="useridfk" name="useridfk" value="#form.useridfk#" />
<span id="ownerspan">User 1</span><br/>
<a href="" onClick="ColdFusion.Window.show('userlist');return false">Select Owner</a>
</td>
</tr>
<tr>
<td>Foo:</td>
<td><input type="text" name="foo" value="#form.foo#" /></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="save" value="Save" /></td>
</tr>
</table>
</form>
</cfoutput>
<cfwindow center="true" draggable="true" modal="true" name="userlist" source="test_userlist.cfm" title="Select Owner" width="250" height="280" />
The name, foo, and useridfk values are all hard coded. Name and foo are simple text fields just for comparison. The owner field is a bit different. I used a hidden form field for the owner's ID value. Below it I output his name (again, it's only hard coded here because this is a demo). Next is a link that is used to activate the cfwindow defined at the very bottom of the page. The window points to test_userlist.cfm. Let's take a look at that now:
<cfparam name="url.start" default="1">
<cfset total = 92>
<cfset perpage = 10>
<script>
selectUser = function(id,label) {
document.getElementById('ownerspan').innerHTML = label;
document.getElementById('useridfk').value = id;
ColdFusion.Window.hide('userlist');
}
</script>
<cfloop index="x" from="#url.start#" to="#min(url.start+perpage-1, total)#">
<cfoutput>
<a href="" onClick="selectUser(#x#,'Record #x#');return false;">Record #x#</a><br />
</cfoutput>
</cfloop>
<cfoutput>
<cfif url.start gt 1>
<a href="#ajaxLink('#cgi.script_name#?start=#url.start-perpage#')#">Previous</a>
<cfelse>
Previous
</cfif>
/
<cfif (url.start+perpage-1) lt total>
<a href="#ajaxLink('#cgi.script_name#?start=#url.start+perpage#')#">Next</a>
<cfelse>
Next
</cfif>
</cfoutput>
Most of the code was ripped from an earlier blog entry and takes care of outputting a page of hard coded data. There are a few critical things to note here. First, the pagination makes use of ajaxLink. This keeps the linked content within the cfwindow container. Next, notice how each person's record is a link to a function named selectUser.
The selectUser function takes an ID and a label. I then use getElementById to grab the span defined in the previous template as well as the hidden form field. Once the values are updated I simply hide the window.
This is a simple demo and could be improved with a bit more functionality. Using RIAForge as the example, I probably would not want to "Next" through more than 4500 records. Adding a simple name filter on top would help reduce the amount of pages I'd have to flip through.
Thoughts? As I mentioned at the beginning of this post I plan on discussing similar solutions in jQuery and Spry as well.