Multiple dojox grids on a single page

I’ve been working on a page that allows an administrator to configure mobile device reimbursement expenses.  The page offers three types of expenses (voice, messages, and data), and five different reimbursement options for each (no reimbursement, flat rate, per unit, per unit with a maximum, and a tiered approach).  There’s also a maximum amount setting to limit the total reimbursement.  For more information about mobile expense reimbursement, visit http://mobiso.com The remainder of this post describes the technical implementation.

I used dojox DataGrids to allow the administrator to set the tiered values.  Each tier has a minimum value and an amount.  The minimum value is the lowest number of units the device must have in order to receive the corresponding amount.  The tier values, both minimums and amounts, must ascend.

To simplify the page submission and display logic, I used hidden textareas to submit the grid data to the server and return it to the client.

The following code is the XHTML  It includes Smarty template code, because it’s inside a loop. I left the Smarty code because it’s used in the javascript to add and remove rows, as well as to move the data between the textareas.  If you only have one or two grids, you could just hard code the indexes or identifiers.

<div class="divAltButtons">
<a href="javascript:AddRow({$smarty.foreach.type.index})">
<img src="images/cp/16x16/actions/edit_add.png" />
</a>
<a href="javascript:RemoveRows({$smarty.foreach.type.index})">
<img src="images/cp/16x16/actions/cancel.png" />
</a>
</div>
<div class="break"></div>
<div dojoType="dojo.data.ItemFileWriteStore" data="storeData_{$sUT}"
jsId="store_{$smarty.foreach.type.index}" id="store_{$smarty.foreach.type.index}">
</div>
<table  dojoType="dojox.grid.DataGrid" id="grid_{$smarty.foreach.type.index}"
store="store_{$smarty.foreach.type.index}" jsId="grid_{$smarty.foreach.type.index}"
clientSort="true"
singleClickEdit="true"
rowSelector="24px"
style="width:200px; height: 117px;"
query="{ldelim}{rdelim}">
<thead>
<tr>
<th field="row" width="40px" hidden="true">Row</th>
<th width="auto" field="minimum" editable="true">Minimum</th>
<th width="auto" field="amount" editable="true">Amount</th>
</tr>
</thead>
</table>
<textarea name="tGridData[{$sUT}]" id="tGridData{$smarty.foreach.type.index}" style="display:none">{$tGridData[$sUT]}
</textarea>

Much of the code came from http://www.dojotoolkit.org/reference-guide/dojo/data/ItemFileWriteStore.html, including itemToJS, which is not listed here.

These functions takes the code from the grid and puts it into the texarea, and gets it back out.

/* Prior to submitting the data to the server, call putGridDataInTextAreas(); to copy the grid data into the text area */
/* When the response is received, use getGridDataFromTextAreas(response); to get the data into the grid */ 

function getGridDataFromTextAreas(response)
{
var i,grid,store,txtArea;
for (i=0;i<iGridCount;i++)
{
txtArea=dojo.byId('tGridData'+i);
txtArea.value=response[txtArea.name];
grid=dijit.byId('grid_'+i); store = new dojo.data.ItemFileWriteStore({jsId: 'store_'+i,data:dojo.fromJson(txtArea.value), urlPreventCache: true});
RefreshGrid(store,grid);
GridFocus(grid);
}
}

function putGridDataInTextAreas()
{
var i,grid,txtArea;
for (i=0;i<iGridCount;i++)
{
grid=dijit.byId('grid_'+i);
GridFocus(grid);
txtArea=dojo.byId('tGridData'+i);
txtArea.value=makeData(grid.store);
}
}

function AddRow(i)
{
grid=dijit.byId('grid_'+i);store=grid.store;
oDefaultItem.row=grid.rowCount+1;
// Blur the current cell to preserve the value
GridFocus(grid);
// set the properties for the new item:
// Insert the new item into the store:
store.newItem(oDefaultItem);
// Set the focus on the new row
grid.focus.setFocusIndex(grid.rowCount-1,0);
RefreshGrid(store,grid);
setDataChanged();
}

function RemoveRows(i)
{
grid=dijit.byId('grid_'+i);store=grid.store;
// Get all selected items from the Grid:
var items = grid.selection.getSelected();
if(items.length){
//if (!confirm(C_confirm_remove)) return;
bRemoving=true;
// Iterate through the list of selected items.
// The current item is available in the variable
// "selectedItem" within the following function:
dojo.forEach(items, function(selectedItem) {
if(selectedItem !== null) {
// Delete the item from the datastore:
store.deleteItem(selectedItem);
} // end if
}); // end forEach
} // end if
RefreshGrid(store,grid);
setDataChanged();
}

function GridFocus(grid)
{
if (!bRemoving)
{
try
{grid.focus.setFocusIndex(0,0);}
catch(err)
{var i=0; /* nop */}
}
bRemoving=false;
}

function RefreshGrid(store,grid)
{
// Refresh the data in the grid
grid.setStore(store);
grid.update();
grid.selection.deselectAll();
}
function makeData(store)
{
var data = [];
for (var i in store._arrayOfAllItems) {
var item=null;
item = store._arrayOfAllItems[i];
data.push(itemToJS(store, item));
}
return dojo.toJson(data, true);
}

/* Create empty grid data */
$aGridData=array();
foreach ($aUsageType as $k => $v)
$aGridData[$v]['items']=array();
/* If there is data in the database, place it in the grid data. Note that there may be several rows of data */
case 'tiered':
$aGridData[$sUsageType]['items'][$row]['row']=$row;
$aGridData[$sUsageType]['items'][$row]['minimum']=$v['usage_minimum_value'];
$aGridData[$sUsageType]['items'][$row]['amount']=number_format($v['method_amount'],2);
$row++;
break;
/* Convert the array to JSON */
require_once 'Zend/Json.php';
foreach ($aGridData as $k => $v)
$aReturn['tGridData['.$k.']']=Zend_Json::encode($v);
/* The code to decode and store the data in the database is application specific, and is not included, it begins with a JSON decode. */

As stated above, the code to extract the data from the textarea and store it is left to the reader.

Key development strategies:

  • Hard code the JSON data to get the grid code running.
  • Use FireBug (FireFox plugin) to examine the data and store.
  • Use var_dump on the server side, frequently.
  • Be sure to sanitize and validate the data, as well as filter it prior to sending it to the client.
  • Deleted rows are submitted as empty braces, so as you loop through the grid rows, discard any empty rows or rows with zero cells of data.
  • Keep an eye on the page load time, it may be good to delay creation of the grid until the page has finished rendering other elements.

This post is courtesy of Lyrix, Inc. http://lyrix.com