Ask a Jedi: Add an edit button to a ColdFusion 8 Ajax Grid

This post is more than 2 years old.

Kyle asks:

In my application I currently have a table that is created from a cfoutput query, and for each row I have an edit button that links to an edit page passing an id as a url variable. I would like to change this table to a coldfusion 8 cfgrid (html format), but I'm not seeing how you can do this in html format (from what U've read, it can only be done in flash format). Have you come across a way to do this in html?

This is something I've mentioned before, but don't forget you can include arbitrary HTML in your data passed to the grid. I'm not sure how far you can push it, but consider this sample: <cfquery name="entries" datasource="blogdev"> select * from tblblogentries limit 0,10 </cfquery>

<cfset queryAddColumn(entries,"editlink","varchar",arrayNew(1))>

<cfloop query="entries"> <cfsavecontent variable="edittext"> <form action="test4.cfm" method="post"> <cfoutput><input type="hidden" name="id" value="#id#"></cfoutput> <input type="submit" value="Edit"> </form> </cfsavecontent> <cfset querySetCell(entries, "editlink", edittext, currentRow)> </cfloop>

<cfform name="test"> <cfgrid autowidth="true" name="entries" format="html" query="entries" width="600"> <cfgridcolumn name="id" display="false"> <cfgridcolumn name="body" display="false">

<cfgridcolumn name="title" header="Title"> <cfgridcolumn name="posted" header="Posted"> <cfgridcolumn name="editlink" header=""> </cfgrid> </cfform>

<cfdump var="#form#">

I begin by doing a normal query. This grid isn't Ajax based, but that doesn't really matter. Note that I add a custom column named editlink. I then loop over the query. For each row, I create a form that simply has a hidden ID variable and a submit button. I then take this form (using the handy cfsavecontent tag) and store it into the query.

I added the query column to the display, and that was it. I added a dump to the page so I could confirm the right ID was being passed. That's it really. Here is how the grid now renders:

Again, I'm not quite sure this is "proper" usage of the grid, but it works!

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 todd sharp posted on 5/16/2008 at 6:30 PM

I'm not sure on the 'proper' part either, but you could _probably_ also do it by rolling your own renderer.

For inspiration see Ray's post on renderers or mine:

http://cfsilence.com/blog/c...

Comment 2 by Michael White posted on 5/16/2008 at 8:49 PM

when you switch from html tables to ajax grids, sometimes you have to rethink the way you do things. You could have just one edit button (that only appears when a grid item is selected) and pass the id of the selected grid item.

Comment 3 by Andy Sandefer posted on 5/16/2008 at 11:11 PM

I personally feel that having an edit button on each row is redundant from a display point of view and I have add, edit and delete buttons beneath my cfgrid. When the user clicks on the edit button it invokes a javascript function to get the primary key of the selected grid record and then it creates a cfwindow with the edit form for that record. The user then makes their edits and clicks a Save button on the cfwindow's cfform which handles the database changes, closes the cfwindow and refreshes the underlying cfgrid.

Comment 4 by Scott Emery posted on 5/22/2008 at 12:31 AM

How about using a double-click event to launch a popup or cfwindow to do the edit. After the edit, how would you close the popup and do a refresh of the grid to show the change?

Comment 5 by Andy Sandefer posted on 5/23/2008 at 7:08 PM

Scott,
First off, sorry it took me a while to get back to you on this - I've been really busy with some client projects. At any rate, here's how I would handle this - say that productgroups.cfm is the page with the cfgrid...

<cfinclude template="AppHeader.cfm">

<cfajaximport tags="cfwindow, cfform">
<cfajaxproxy cfc="cfc.product" jsclassname="proxyProduct">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title><cfoutput>#REQUEST.titleText#</cfoutput></title>
<link href="../css/main.css" rel="stylesheet" type="text/css">

<script>

function showProductGroup(recordAction) {
if (recordAction == 'Update') {
var selectedProductGroupID = ColdFusion.getElementValue('gridProductGroups','productGroups','ProductGroupID');
}
else {
var selectedProductGroupID = '';
}
//alert('ID is ' + selectedProductGroupID);
day = new Date();
productGroupWindowID = 'productGroup' + day.getTime();
var windowOptions = new Object();
windowOptions.width = 550;
windowOptions.height = 270;
windowOptions.center = true;
windowOptions.modal = true;
windowOptions.resizeable = true;
windowOptions.initshow = true;
windowOptions.draggable = true;
windowOptions.closeable = true;
windowOptions.headerstyle = 'font-family: verdana; background-color: #0066FF;';

ColdFusion.Window.create(productGroupWindowID,'Product Group','editproductgroup.cfm?formName=' + productGroupWindowID + '&productGroupID=' + selectedProductGroupID + '&runMode=' + recordAction, windowOptions);
}

function closeEditWindow() {
ColdFusion.Window.hide(productGroupWindowID);
ColdFusion.Grid.refresh('gridProductGroups', true);
}

function productGroupDeleteCallback(callbackMsg) {
alert('The selected Product Group record has been Deleted.');
ColdFusion.Grid.refresh('gridProductGroups', true);
}

function errorHandler(code,msg) {
alert('Error ' + code + ': ' + msg);
}

function deleteProductGroup() {
var proxyDeleteProductGroup = new proxyProduct();
proxyDeleteProductGroup.setErrorHandler(errorHandler);
proxyDeleteProductGroup.setCallbackHandler(fileCollectionGroupDeleteCallback);

var selectedProductGroupID = ColdFusion.getElementValue('gridProductGroups','productGroups','ProductGroupID');
//alert(selectedProductGroupID);
confirmDelete = confirm('Are you absolutely certain that you want to Delete the selected Product Group record?');
if (confirmDelete == true) {
var recDeleted = proxyDeleteProductGroup.deleteProductGroup(selectedProductGroupID);
}
}

</script>

</head>

<body>
<cfform name="productGroups" format="html">
<table>
<tr><td><h1>Product Groups</h1></td></tr>
<tr>
<td>
<cfgrid name="gridProductGroups" format="html" width="600" height="220" pageSize="10" colheaderfont="Verdana" font="Verdana" fontsize="11"
bind="cfc:cfc.product.getProductGroupsForGrid(
page={cfgridpage},
pageSize={cfgridpagesize},
gridSortColumn={cfgridsortcolumn},
gridSortDir={cfgridsortdirection})">
<cfgridcolumn name="ProductGroupID" header="File Collection Group ID" width="190">
<cfgridcolumn name="LongDesc" header="Description" width="390">
</cfgrid>
</td>
</tr>
<tr>
<td align="right">
<cfinput type="button" name="btnAdd" value="Add" onclick="showProductGroup('Create')">
<cfinput type="button" name="btnEdit" value="Edit" onclick="showProductGroup('Update')">
<cfinput type="button" name="btnDelete" value="Delete" onclick="deleteProductGroup()">
</td>
</tr>
</table>
</cfform>

</body>
</html>

Now you've got the edit page that the cfwindow loads to handle adding new records and updating existing records. You'll notice that I have to give the edit page (editproductgroup.cfm) a dynamically named unique form id. If you built this app without doing so you'd notice that once you edit, close the cfwindow and return to the grid and then try to pick a new record and edit again, the cfwindow seems to hold on to form variables and you'll actually update the previous record you were working with when you call the form submitter. You get around this by making the edit page's form have a unique id that get's generated when the cfwindow is invoked. Here's code for the edit page...

<cfparam name="productGroupID" default="">
<cfparam name="longDesc" default="">

<cfset formName = URL.formName>
<cfset runMode = URL.runMode>
<cfif runMode EQ "Update">
<cfinvoke component="cfc.product" method="getProductGroups" productGroupID="#URL.productGroupID#" returnvariable="qryProductGroup">
<cfset productGroupID = qryProductGroup.ProductGroupID>
<cfset longDesc = qryProductGroup.LongDesc>
</cfif>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title><cfoutput>#REQUEST.titleText#</cfoutput></title>
<link href="../css/main.css" rel="stylesheet" type="text/css">

<script>
submitForm = function() {
var dataIsValid = _CF_check<cfoutput>#formName#</cfoutput>(<cfoutput>#formName#</cfoutput>);
if (dataIsValid == true) {
ColdFusion.Ajax.submitForm(<cfoutput>#formName#</cfoutput>, 'formprocs/proceditproductgroup.cfm', callback, errorHandler);
}
}

function callback(callbackMsg) {
alert('The Product Group record has been Saved.');
closeEditWindow();
}

function errorHandler(code,msg) {
alert('Error ' + code + ': ' + msg);
}
</script>

</head>

<body>
<cfoutput>
<cfform name="#formName#" format="html">
<cfinput type="hidden" name="runMode" value="#runMode#">
<cfif runMode EQ "Update">
<cfinput type="hidden" name="originalProductGroupID" value="#productGroupID#">
</cfif>
<table>
<tr>
<td class="formFieldLabel">Product Group ID</td>
<td>
<cfinput type="text" name="productGroupID" maxlength="20" value="#productGroupID#" required="true" message="You must enter a valid Product Group ID.">
</td>
</tr>
<tr>
<td class="formFieldLabel">Description</td>
<td>
<cfinput type="text" name="longDesc" maxlength="255" size="56" value="#longDesc#" required="true" message="You must enter a valid Description.">
</td>
</tr>
<tr>
<td colspan="2" align="right">
<cfinput type="button" name="btnClose" value="Close" onclick="closeEditWindow()">
<cfinput type="button" name="btnSave" value="Save" onclick="submitForm()">
</td>
</tr>
</table>
</cfform>
</cfoutput>
</body>

</html>

The drill down page, editproductgroup.cfm, which inhabits the cfwindow submits to a page named proceditproductgroup.cfm which can handle either an update or insert into the database. You'll notice that the first page uses a remote call to a function held in a cfc to handle a delete.

Good luck!
-Andy Sandefer

Comment 6 by Gavy posted on 10/28/2008 at 10:14 AM

Hi Andy, I was following you cfgrid code, very cool enough and easy.

But I have problem when i try to delete the record. The following code creates Problem:

function productGroupDeleteCallback(callbackMsg) {
alert('The selected Product Group record has been Deleted.');
ColdFusion.Grid.refresh('gridProductGroups', true);
}
function deleteProductGroup() {
var proxyDeleteProductGroup = new proxyProduct();
proxyDeleteProductGroup.setErrorHandler(errorHandler);
proxyDeleteProductGroup.setCallbackHandler(fileCollectionGroupDeleteCallback);

var selectedProductGroupID = ColdFusion.getElementValue('gridProductGroups','productGroups','ProductGroupID');
//alert(selectedProductGroupID);
confirmDelete = confirm('Are you absolutely certain that you want to Delete the selected Product Group record?');
if (confirmDelete == true) {
var recDeleted = proxyDeleteProductGroup.deleteProductGroup(selectedProductGroupID);
}
}

Also function productGroupDeleteCallback is Different from what you called in fileCollectionGroupDeleteCallback, when i made there name same the javascript popup appared then.

when i clicked ok to delete, i encountered an error;

proxyDeleteProductGroup.deleteProductGroup is not a function
deleteProductGroup()modules....fm?part=3 (line 462)
onclick(click clientX=387, clientY=409)modules....Xmg%3D%3D (line 2)
[Break on this error] var recDeleted = proxyDeletePok...up.deleteProductGroup(selectedProductGroupID);

So well i think something is wrong in the code as i am trying to figure what could be the cause?

Comment 7 by Andy Sandefer posted on 10/28/2008 at 5:13 PM

Well I modified code from an existing project for this example so the callback handler should've been set to productGroupDeleteCallback, the other reference was a find and replace mishap so sorry about the confusion.
Also this line is wrong...

var recDeleted = proxyDeleteProductGroup.deleteProductGroup(selectedProductGroupID);

You don't need recDeleted because the result is actually going to go to the callback.