Category: "Dojo"

Zend Framework dijit.FilteringSelect Configure

To configure a FilteringSelect dijit, including client and server-side validation, with a Zend Framework .ini file, use the following syntax:

; status element
; These MUST match the definitions in the user table
elements.status.type = "FilteringSelect"
elements.status.options.label = "Status"
elements.status.options.dijitParams.searchAttr = "name"
elements.status.options.autoComplete = true
elements.status.options.required = "true"
elements.status.options.validators.inarray.validator = "InArray"
elements.status.options.validators.inarray.options.haystack[] = "pending"
elements.status.options.validators.inarray.options.haystack[] = "activated"
elements.status.options.validators.inarray.options.haystack[] = "disabled"
elements.status.options.validators.inarray.options.haystack[] = "locked"
elements.status.options.validators.inarray.options.messages.notInArray = "Invalid status"
elements.status.options.multioptions.pending = "pending"
elements.status.options.multioptions.activated = "activated"
elements.status.options.multioptions.disabled = "disabled"
elements.status.options.multioptions.locked = "locked"

Sourcing dojo from AOL's CDN under Zend Framework

This implementation uses a default layout. To set the path and name of the layout (default.phtml), the application.ini file was configured like so:

configs/application.ini

resources.layout[] =
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
resources.layout.layout = "default"

Bootstrap.php adds dojo to thel helper path for the view in _initView. The locale is set up as well as the translation.

Bootstrap.php

protected function _initView()
    {
        Zend_Session::start();
        $session=new Zend_Session_Namespace('global');
        $options=$this->getOptions();

        $locale = new Zend_Locale(Zend_Locale::BROWSER,TRUE);
        $session->strLocale=$locale->toString();
        Zend_Registry::set('Zend_Locale', $locale);

        $language_path=$options['resources']['locale']['translation']['path'];
        $translate = new Zend_Translate(array('adapter'=>'gettext',
                'content'=>$language_path,
                'scan' => Zend_Translate::LOCALE_DIRECTORY,
                'locale'=>$locale->toString()));
        Zend_Registry::set('Zend_Translate', $translate);

        $view = new Zend_View();
        $view->doctype('XHTML1_STRICT');
        $view->headLink()->appendStylesheet('/css/base.css')
            ->appendStylesheet('/css/color.css');

        $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
        $viewRenderer->setView($view);

        $view->addHelperPath('Zend/Dojo/View/Helper/', 'Zend_Dojo_View_Helper');
        Zend_Dojo_View_Helper_Dojo::setUseDeclarative();

        $view->strLocale=$session->strLocale;

        return $view;
    }

In the controller for the form, dojo is enabled. This way, the layout only includes dojo if the form or page requires it.

        $this->view->form = $form;
        $this->view->dojo()->enable();
        if ($this->getRequest()->isPost())

In the layout, the final dojo configuration is done, if dojo is enabled.

application/layouts/scripts/default.phtml


<?php 
// application/layouts/scripts/default.phtml

echo $this->doctype() ?>
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head>  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
    <?php echo $this->headTitle() ?>
    <?php echo $this->headMeta() ?>
    <?php echo $this->headLink() ?>
    <?php echo $this->headStyle() ?>
<?php if ($this->dojo()->isEnabled()){
        $this->dojo()
            ->setCdnBase(Zend_Dojo::CDN_BASE_AOL)
            ->setCdnVersion('1.5')
            ->setCdnDojoPath(Zend_Dojo::CDN_DOJO_PATH_AOL);
        $this->dojo()
            ->setDjConfigOption('parseOnLoad', true)
            ->setDjConfigOption('locale',$this->strLocale)
            ->setDjConfigOption('cdnbase',Zend_Dojo::CDN_BASE_AOL)
            ->setDjConfigOption('cdnversion','1.5')
            ->setDjConfigOption('cdndojopath',Zend_Dojo::CDN_DOJO_PATH_AOL)
            ->addStyleSheetModule('dijit.themes.tundra')
            ->requireModule('dijit.form.Form')
            ->requireModule('dijit.form.ValidationTextBox')
            ->requireModule('dijit.form.TextBox');
            echo $this->dojo();
   }
?>

dijit ValidationTextBox Custom Validator

The dijit ValidationTextBox widget is an excellent way to help users enter valid data. Recently, I had a very long and complex regular expression that caused performance issues when the data was typed into the input. To improve performance, I used a custom validator (http://docs.dojocampus.org/dijit/form/ValidationTextBox-tricks).


var dijits=Array('sInboundFormat','sOutboundFormat');
var regexps=Array(/^sip:[\w]+@[\w\.]$/,/^sip:[\w]+@[\w\.]$/);

dojo.addOnLoad(function()
{
        var i,ld=dijits.length;
        for (i=0;i<ld;i++)
        {
                /* Store the regular expression for the input */
                dijit.byId(dijits[i]).cRegExp=regexps[i];
                /* The bValid flag stores the current state of the input */
                dijit.byId(dijits[i]).bValid=true;
                dijit.byId(dijits[i]).validator=function (value,constraints) {
                        /* Determine where the focus is */
                        var d=dijit.getFocus();
                        /* If the focus is on the save button */
                        if ((d!=null) && (d.node!=null) && (d.node.id=='btnSave'))
                        {
                            if (dojo.trim(this.value)!='')
                            {
                                /* Set the bValid flag */
                                this.bValid=this.cRegExp.test(this.value);
                                /* Update the error icon on the input */
                                if (!this.bValid)
                                        dojo.addClass(this.domNode,'dijitError');
                                else
                                        dojo.removeClass(this.domNode,'dijitError');
                            }
                            else
                                this.bValid=!this.required;
                        }
                        /* Return the input state (valid or not) */
                        return this.bValid;
                };
        }
});

This code adds an attribute to the widget, bValid. bValid is initialized to true, and updated after the input is validated. The performance gain is realized by testing the focus and only running the test if the user has just clicked ‘Save’.

Since the validation is not performed as the data is entered, it’s not a real-time validation, however, the input does include the warning icon after the validation cycle, to help the user quickly find errors.

This post courtesy of Lyrix, Inc. / Mobiso

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

dojox/charting - notes

Notes on using dojox Charting.

  • Read the documention at the link above.
  • Assemble the page as AJAX from the beginning. Don’t bother building an HTML version first, the number of requests required for charting is large enough that these pages should be AJAX pages. Zend Framework’s Dojo Data (http://framework.zend.com/manual/en/zend.dojo.data.html) component is an excellent way to encode the data for transport. Zend Framework can be used as a library.
  • Consider using a pulldown to allow the viewer to select the theme used. This is nice for appearance and fun, but may also be valuable for people that have difficulty distinguising colors. A simple loop that lists the available themes, uses a dojo.require to fetch the code, and then applies them to the charts and legends with chart.SetTheme, chart.render, and legend.refresh is a nice feature.
  • Think carefully about what charts you’d like to use. This link
    http://archive.dojotoolkit.org/nightly/dojotoolkit/dojox/charting/tests/test_chart2d.html has great sample code. Take the time to look at the charts available. Moving up a level or two in the directory tree yields additional, valuable code.
  • Use updateSeries if possible to redraw charts, if necessary, use destroy, then recreate them.
  • Offer good explanatory text to help people interpret the displays.
  • Write the queries carefully, it’s often faster to query, then use arithmetic to find out other portions of the data. Don’t forget to fill in gaps of missing data for series that illustrate usage or events across time spans.
  • Be sure to sanitize and validate the data. Always.