Post a Comment on:

Building an Editable Grid with AJAX and ColdFusion Components

Leave this field empty:

Search Altered Pixels.net::

Building an Editable Grid with AJAX and ColdFusion Components

June 23, 2009 · No CommentsComments Feed

Implementation

We'll work on an HTML table showing a list of employees. We have the following properties for each employee: id, name, email and salary

First, let's take a look at our work files:

employee.cfc
ColdFusion Component containing all methods to work with employee table.
list.cfm
Calls getList method and draws the HTML table - our grid.
grid.css
used by LIST.CFM. Style sheet rules that apply to LIST.CFM elements.
grid.js
Used by LIST.CFM. All JavaScript code needed to create form fields and to send requests.

Click here to download zip file. Extract all files in the same directory.

employee.cfc

Contains all methods to access database. In order to keep this tutorial simple and so you can online test the grid, we won't connect to a database. In each method, you'll note commented SQL sentences. This doesn't make this tutorial less general, as we are interested in client-server interaction without page reloads.

getList()
Access: public; returns a query object with all employee table.
add(required id, required name, required email, required salary)
Access : remote; validates arguments and inserts a record in the database. Returns new record id or an error message.
set(required rowId, required id, required name, required email, required salary)
Access : remote; validates arguments and updates a record in the database. Returns record id or an error message.
remove(required rowId)
Access : remote; remove a record from the database. Returns an empty string or an error message.
validate(required struct values)
Access : private; validates arguments for add and set methods and returns proper messages. Three validation rulues are implemented: id cannot be empty; name must contain at least two words; and salary must be a number.

list.cfm

Most important parts of LIST.CFM are shown below.

<br />&amp;lt;head&amp;gt;<br />   ...<br />   &amp;lt;link rel="stylesheet" type="text/css" href="grid.css"&amp;gt;<br />   &amp;lt;script type="text/javascript" src="grid.js"&amp;gt;&amp;lt;/script&amp;gt;<br />   ...<br />&amp;lt;/head&amp;gt;<br /><br />&amp;lt;body&amp;gt;<br /><br /><strong>&amp;lt;cfinvoke component="employee"<br />   method="getList"<br />   returnvariable="qList"&amp;gt;&amp;lt;/cfinvoke&amp;gt;</strong><br /><br />&amp;lt;table <strong>class="grid"</strong> <strong>id="employee"</strong>&amp;gt;<br />&amp;lt;caption&amp;gt;Employees List&amp;lt;/caption&amp;gt;<br />&amp;lt;thead&amp;gt;<br />   &amp;lt;tr&amp;gt;<br />      &amp;lt;th colspan="2" class="buttons"&amp;gt;<br />         &amp;lt;a class="new" href="#" onclick="newRecord(this);" <br />         title="Add a record"&amp;gt;&amp;lt;span&amp;gt;New&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;<br />      &amp;lt;/th&amp;gt;<br />      &amp;lt;th <strong>id="id"</strong>&amp;gt;ID&amp;lt;/th&amp;gt;<br />      &amp;lt;th <strong>id="name"</strong>&amp;gt;Name&amp;lt;/th&amp;gt;<br />      &amp;lt;th <strong>id="email"</strong>&amp;gt;email&amp;lt;/th&amp;gt;<br />      &amp;lt;th <strong>id="salary"</strong>&amp;gt;Salary&amp;lt;/th&amp;gt;<br />   &amp;lt;/tr&amp;gt;<br />&amp;lt;/thead&amp;gt;<br />&amp;lt;tbody&amp;gt;<br />&amp;lt;cfoutput query="qList"&amp;gt;<br />   &amp;lt;tr <strong>id="#qList.id#"</strong>&amp;gt;<br />      &amp;lt;td&amp;gt;&amp;lt;a class="edit" href="##" onclick="editRecord(this);" <br />         title="Edit this record"&amp;gt;&amp;lt;span&amp;gt;Edit&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;<br />      &amp;lt;td&amp;gt;&amp;lt;a class="delete" href="##" onclick="deleteRecord(this);" <br />         title="Delete this record"&amp;gt;&amp;lt;span&amp;gt;Delete&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;<br />      &amp;lt;td&amp;gt;#qList.id#&amp;lt;/td&amp;gt;   <br />      &amp;lt;td&amp;gt;#qList.name#&amp;lt;/td&amp;gt;   <br />      &amp;lt;td&amp;gt;#qList.email#&amp;lt;/td&amp;gt;   <br />      &amp;lt;td&amp;gt;#qList.salary#&amp;lt;/td&amp;gt;   <br />   &amp;lt;/tr&amp;gt;<br />&amp;lt;/cfoutput&amp;gt;<br />&amp;lt;/tbody&amp;gt;<br />&amp;lt;/table&amp;gt;<br />...<br />

In section HEAD, we load GRID.JS and GRID.CSS files. In section BODY, we call getList method, getting qList query object.

We need to remark some points when creating the HTML table, in order to keep general our JavaScript code:

  1. For the TABLE tag, we must set the class attribute to grid and id with component's name.
  2. THEAD and TBODY tags are required.
  3. TH tags' id must match to methods arguments names inside employee.cfc.
  4. Each row's id must be set to a database record unique identifier.

grid.js

This is the key point in our tutorial. All functions are simple, but they require former JavaScript knowledge. We'll go into these functions one by one:

General functions

createRow()
Adds a row to HTML table.
deleteRow()
Removes a row from HTML table.
wddxGetElement()
A WDDX serialized string is returned by any call to employee.cfc methods. This function extracts desired return message. E.g.: &amp;lt;wddxPacket version='1.0'&amp;gt;&amp;lt;header/&amp;gt;&amp;lt;data&amp;gt;&amp;lt;string&amp;gt;<strong>Salary must be a number!</strong>&amp;lt;/string&amp;gt;&amp;lt;/data&amp;gt;&amp;lt;/wddxPacket&amp;gt; Running function over this WDDX string, we get message 'Salary must be a number!'
httpRequest()
Call methods using XMLHttpRequest().

getColumnNames(tableObj)

Retrieves TH id's so they can be as arguments names when calling add and set methods.

<br />function getColumnNames(tableObj) {<br />   var theadObj = tableObj.getElementsByTagName('THEAD')[0];<br />   var trObj = theadObj.getElementsByTagName('TR')[0];<br />   var thObjs = trObj.getElementsByTagName('TH');<br />   var arrColumnNames = new Array;<br />   for (var i=1;i&amp;lt;thObjs.length;i++)<br />      arrColumnNames.push(thObjs[i].id);<br />   return arrColumnNames;<br />}<br />

A table object is used as a parameter. Function identifies THEAD object inside this table and, for the first TR, retrieves arguments names - TH's id, putting them into an array.

startEditing(trObj)

Transforms each cell (TD) in a row into a form field.

<br />function startEditing(trObj) {<br />   var arrColumnNames = getColumnNames(trObj.parentNode.parentNode);<br />   var tdObjs = trObj.getElementsByTagName('TD');<br />   tdObjs[0].innerHTML = <br />      '&amp;lt;a class="save" href="#" onclick="saveRecord(this);" ' + <br />      'title="Save this record"&amp;gt;&amp;lt;span&amp;gt;Save&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;';<br />   tdObjs[1].innerHTML = <br />      '&amp;lt;a class="cancel" href="#" onclick="cancelEditRecord(this);" ' +<br />      'title="Cancel editing"&amp;gt;&amp;lt;span&amp;gt;Cancel&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;';<br />   for (var i=2;i&amp;lt;tdObjs.length;i++)<br />      tdObjs[i].innerHTML = <br />         '&amp;lt;input type="text" name="'+arrColumnNames[i-2]+<br />         '" id="input-'+   arrColumnNames[i-2]+'" value="'+tdObjs[i].innerHTML+<br />         '" default="'+tdObjs[i].innerHTML+'"&amp;gt;';<br />   return false;<br />}<br />

Steps:

  1. Gets arguments names
  2. Finds all cells (TD's) inside TR object (function argument)
  3. Changes links inside two first cells. From edit and delete to save and cancel
  4. For all other cells, puts its content inside a form field

stopEditing(trObj,reset)

Transforms each form field into its primary or current value, according to boolean argument reset

<br />function stopEditing(trObj,reset) {<br />   var tdObjs = trObj.getElementsByTagName('TD');<br />   tdObjs[0].innerHTML = <br />      '&amp;lt;a class="edit" href="#" onclick="editRecord(this);" ' +<br />      'title="Edit this record"&amp;gt;&amp;lt;span&amp;gt;Edit&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;';<br />   tdObjs[1].innerHTML = <br />      '&amp;lt;a class="delete" href="#" onclick="deleteRecord(this);" ' +<br />      'title="Delete this record"&amp;gt;&amp;lt;span&amp;gt;Delete&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;';<br />   var inputObjs = trObj.getElementsByTagName('INPUT');<br />   for (var i=tdObjs.length-1;i&amp;gt;1;i--)<br />      if (reset) tdObjs[i].innerHTML = inputObjs[i-2].defaultValue;<br />      else tdObjs[i].innerHTML = inputObjs[i-2].value;<br />   return false;<br />}<br />

Steps:

  1. Finds all cells (TD's) inside TR object (function argument)
  2. Changes links inside two first cells. From save and cancel to edit and delete
  3. For all other cells, sets its content to form fields properties value/defaultValue, according to reset value

newRecord(obj)

Used to create a new row at the end of the table and to make it editable. Code is quite simple, once it just calls createRow() and startEditing() functions.

<br />function newRecord(obj) {<br />   var trObj = createRow(obj);<br />   startEditing(trObj);<br />}<br />

editRecord(obj)

Used to make a row editable. Code is also quite simple, once it just gets TR oobject and calls startEditing() function.

<br />function editRecord(obj) {<br />   <span class="comments">//     A      TD         TR</span><br />   var trObj = obj.parentNode.parentNode;<br />   startEditing(trObj);<br />}<br />

cancelEditRecord(obj)

Used when user wnats to cancel all modifications he did to a row. If current row has an empty id, it menas it is a new row, so it should be removed calling deleteRow() function. Otherwise, stopEditing() function is called.

<br />function cancelEditRecord(obj) {<br />   <span class="comments">»//           A      TD         TR</span>    <br />var trObj = obj.parentNode.parentNode;<br />   var rowId = trObj.id;<br />   if ((rowId) &amp;amp;&amp;amp; (rowId != ""))  <span class="comments">»// reset all values</span><br />   stopEditing(trObj,true);<br />   else <span class="comments">»// remove TR from TABLE</span><br />   deleteRow(trObj);<br />}<br />

deleteRecord(obj)

Used to delete a row from the database.

<br />function deleteRecord(obj) {<br />   if (!confirm('Are you sure you want to delete this record?')) return;<br />   <span class="comments">//     A      TD         TR</span><br />   var trObj = obj.parentNode.parentNode;<br />   var rowId = trObj.id;<br />   var tableObj = trObj.parentNode.parentNode;<br />   <span class="comments">»// Call REMOVE method to delete record from database</span><br />var url = tableObj.id + '.cfc?method=remove&amp;amp;rowId=' + rowId;<br />   var response = wddxGetElement(httpRequest(url),'string');<br />   if (response!="") alert(response);<br />   else deleteRow(trObj);<br />}<br />

Steps:

  1. Calls component's remove method, providing current row's id as argument
  2. If a non empty string is returned, shows the error message. Otherwise, removes current row from HTML table calling deleteRow() function

saveRecord(obj)

Used to send all modifications to the database.

<br />function saveRecord(obj) {<br />   <span class="comments">»//           A      TD         TR</span>    <br />var trObj = obj.parentNode.parentNode;<br />   <span class="comments">»//              TR     TBODY      TABLE</span><br />var tableObj = trObj.parentNode.parentNode;<br />   var inputObjs = trObj.getElementsByTagName('INPUT');<br />   var rowId = trObj.id;<br />   var url = tableObj.id;<br />   <span class="comments">»// Call SET/ADD method to save record to the database</span><br />if ((rowId) &amp;amp;&amp;amp; (rowId != "")) <br />      url += '.cfc?method=set&amp;amp;rowId=' + rowId;<br />   else <br />      url += '.cfc?method=add';<br />   for (var i=0;i&amp;lt;inputObjs.length;i++)<br />      url += '&amp;amp;' + inputObjs[i].name + '=' + inputObjs[i].value;<br />   var response = wddxGetElement(httpRequest(url),'string');<br />   if (response.substr(0,2) != "id") alert(response);<br />   else {<br />      trObj.id = response.substr(3);<br />      stopEditing(trObj,false);<br />   }<br />}<br />

Steps:

  1. Finds all form fields at current row
  2. Defines which method, set or add - according to TR's id
  3. Gets key-value pairs from form fields and sets up method calling url
  4. If returned string is like "id:[id]", sets TR's id to returned id and calls stopEditing() function. Otherwise, shows error message
Tags: AJAX Tutorials