Introduction
Grid’s are used everywhere and one thing the Zend Framework really lacks is a useful view helper to quickly generate grids and their stores. I saw that Matthew Weier O’Phinney had a proposal for a dojox.grid.DataGrid view helper but no work had been done on it. I send him a tweet and learned that he had intended to finish it but is currently too busy to work on it so I wrote a few view helpers to follow his proposal. The dataStore view helper is fairly simplistic but the dataGrid view helper has quite a bit to it. I plan on refactoring the dataGrid view helper to add support for enhancedGrid and treeGrid but I wanted to share what I had done already. Both view helpers follow the use-cases as presented by Matthew in his dojox.grid.DataGrid view helper proposal.
The dataStore() helper
The dataStore() helper provides support for generating programmatic/declarative data stores and requires an id as well as a dojoType.
Usage
=$this->dataStore('store', 'dojox.data.QueryReadStore', array('url' => '/path/to/json'));?>
Code
dojo->requireModule($dojoType);
// Programmatic
if ($this->_useProgrammatic()) {
if (!$this->_useProgrammaticNoScript()) {
$this->dojo->addJavascript('var ' . $id . ";\n");
$js = $id . ' = ' . 'new ' . $dojoType . '(' . Zend_Json::encode($attribs) . ");";
$this->dojo->_addZendLoad("function(){{$js}}");
}
return '';
}
// Set extra attribs for declarative
if (!array_key_exists('id', $attribs)) {
$attribs['id'] = $id;
}
if (!array_key_exists('jsId', $attribs)) {
$attribs['jsId'] = $id;
}
if (!array_key_exists('dojoType', $attribs)) {
$attribs['dojoType'] = $dojoType;
}
return '_htmlAttribs($attribs) . ">
\n";
}
}
The dataGrid() helper
The dataGrid helper provides support for generating programmatic/declarative dataGrids. Currently, the only supported grid is dojox.grid.DataGrid but I’ll be adding EnhancedGrid and TreeGrid in the future.
Usage
dataStore('store', 'dojox.data.QueryReadStore', array('url' => '/admin/articles/grid.json'));
$grid = $this->dataGrid('myGrid',
array(
'selectionMode' => 'none',
'style' => 'height: 350px; width: 100%;',
'store' => 'store',
'fields' => array(
array('field' => 'title', 'label' => 'Title', 'options' => array('formatter' => 'titleFormatter', 'width' => '60%')),
array('field' => 'published', 'label' => 'Published', 'options' => array('formatter' => 'primaryFormatter', 'width' => '10%')),
array('field' => 'updated_at', 'label' => 'Updated', 'options' => array('width' => '15%')),
array('field' => 'created_at', 'label' => 'Created', 'options' => array('width' => '15%')))));
?>
scriptCaptureStart('dojo/method', 'onMouseOver', array('args' => 'row'));?>
var g = dojo.byId("grid-menu-item-"+row.rowIndex);
if (g) { dojo.toggleClass(g, "hide-text"); }
scriptCaptureEnd();?>
scriptCaptureStart('dojo/method', 'onMouseOut', array('args' => 'row'));?>
var g = dojo.byId("grid-menu-item-"+row.rowIndex);
if (g) { dojo.toggleClass(g, "hide-text"); }
scriptCaptureEnd();?>
Code
render();
}
/**
* DataGrid view helper.
*
* @param string $id JavaScript id for the data store.
* @param array $attribs Attributes for the data store.
*/
public function dataGrid($id = '', array $attribs = array())
{
if (!$id) {
throw new Zend_Exception('Invalid arguments: required jsId.');
}
if (!array_key_exists('id', $attribs)) {
$attribs['id'] = $id;
}
if (array_key_exists('fields', $attribs)) {
foreach ($attribs['fields'] as $f) {
$this->addField($f['field'], $f['label'], isset($f['options']) ? $f['options'] : array());
}
unset($attribs['fields']);
}
$this->_attribs = $attribs;
return $this;
}
/**
* Static setter for theme.
* @param string $theme Name of the current them.
*/
public static function setTheme($theme)
{
self::$_theme = $theme;
}
/**
* Static getter for theme.
* @return string
*/
public static function getTheme()
{
return self::$_theme;
}
/**
* Static setting for script base.
* @param string $scriptBase Path to the base script directory.
*/
public static function setScriptBase($scriptBase)
{
self::$_scriptBase = $scriptBase;
}
/**
* Static getter for script base.
* @return string
*/
public static function getScriptBase()
{
return self::$_scriptBase;
}
/**
* Adds field data.
* @param string $field Field name.
* @param string $label Label of the field.
* $param array $attribs Optional parameters for the field.
*/
public function addField($field, $label, array $attribs = array())
{
$this->_fields[] = array(
'label' => $label,
'attribs' => array_merge(array('field' => $field), $attribs));
return $this;
}
/**
* Adds script captures.
* @param array $data
*/
public function addScriptCapture(array $script = array())
{
if (!array_key_exists('data', $script)) {
throw new Zend_Exception('Script data must include keys data and attribs');
}
$this->_scriptCapture[] = $script;
return $this;
}
/**
* Begins script capturing.
*/
public function scriptCaptureStart($type, $event, array $attribs = array())
{
if ($this->_captureLock) {
throw new Zend_Exception('Cannot nest captures.');
}
$this->_currentScript = array('type' => $type, 'event' => $event, 'attribs' => $attribs);
$this->_captureLock = true;
ob_start();
return;
}
/**
* Ends script capturing.
*/
public function scriptCaptureEnd()
{
$data = ob_get_clean();
$this->_captureLock = false;
$this->_currentScript['data'] = $data;
$this->addScriptCapture($this->_currentScript);
$this->_currentScript = array();
return true;
}
/**
* Renders the grid based on programmatic setting.
*/
public function render()
{
$this->dojo->requireModule('dojox.grid.DataGrid');
// Setup the stylesheet base path
if (null === self::getScriptBase()) {
if ($this->dojo->useLocalPath()) {
self::setScriptBase($this->dojo->getLocalPath());
} else {
self::setScriptBase($this->dojo->getCdnBase() . $this->dojo->getCdnVersion());
}
}
$this->dojo->addStylesheet(self::getScriptBase() . '/dojox/grid/resources/Grid.css');
$this->dojo->addStylesheet(
self::getScriptBase() . '/dojox/grid/resources/' . self::getTheme() . 'Grid.css');
// Programmatic
if ($this->_useProgrammatic()) {
if (!$this->_useProgrammaticNoScript()) {
$this->_renderJavascript();
}
return '';
}
return $this->_renderDeclarative();
}
/**
* Renders a table for declarative grids.
*/
protected function _renderDeclarative()
{
if (!array_key_exists('jsId', $this->_attribs)) {
$this->_attribs['jsId'] = $this->_attribs['id'];
}
$table = '
_htmlAttribs($this->_attribs) . ">\n";
foreach ($this->_scriptCapture as $s) {
$attribs = array_merge($s['attribs'], array('event' => $s['event'], 'type' => $s['type']));
$table .= "\t\n";
}
$table .= "\t\n";
$table .= "\t\t\n";
foreach ($this->_fields as $f) {
$table .= "\t\t\t_htmlAttribs($f['attribs']) . '>' . $f['label'] . " | \n";
}
$table .= "\t\t
\n";
$table .= "\t\n";
$table .= "
\n";
return $table;
}
/**
* Renders javascript for programmatic declaration.
*/
protected function _renderJavascript()
{
$tab = $this->_tab;
// Grid names
$gridName = $this->_attribs['id'] . 'Grid';
$layoutName = $this->_attribs['id'] . 'Layout';
$this->dojo->addJavascript('var ' . $gridName . ";\n");
// Setup layout
$js = $tab . 'var ' . $layoutName . " = [\n";
foreach ($this->_fields as $f) {
$f['attribs']['name'] = $f['label'];
$f['attribs'] = $this->_jsonExpr($f['attribs']);
$js .= "{$tab}{$tab}{$tab}" . Zend_Json::encode($f['attribs'], false,
array('enableJsonExprFinder' => true)) . ",\n";
}
$js = substr($js, 0, -2);
$js .= "];\n\n";
// Use expressions for structure, store, formatter, and get
$this->_attribs = $this->_jsonExpr($this->_attribs);
$this->_attribs['structure'] = new Zend_Json_Expr($layoutName);
// Generate grid
$js .= $tab . $tab . $gridName . ' = ' . 'new dojox.grid.DataGrid(' . Zend_Json::encode(
$this->_attribs, false, array('enableJsonExprFinder' => true)) . "), document.createElement('div');\n";
$js .= $tab . $tab . 'dojo.byId("' . $this->_attribs['id'] . '").appendChild(' . $gridName . '.domNode);' .
"\n";
$js .= $tab . $tab . $gridName . ".startup();\n";
// Generate connects for script captures
foreach ($this->_scriptCapture as $s) {
$s['data'] = trim($s['data'], "\r\n");
$args = isset($s['attribs']['args']) ? $s['attribs']['args'] : '';
$js .= "{$tab}{$tab}dojo.connect({$gridName}, \"{$s['event']}\", function({$args}){{$s['data']}});\n";
}
$this->dojo->_addZendLoad("function(){\n{$tab}{$js}\n{$tab}}");
}
/**
* Parses an array looking for keys that should be Zend_Json_Expr()'s and coverts them.
*/
protected function _jsonExpr(array $data)
{
$jsonExprAttr = array('formatter', 'get', 'query', 'store');
foreach ($jsonExprAttr as $exp) {
if (array_key_exists($exp, $data)) {
$data[$exp] = new Zend_Json_Expr($data[$exp]);
}
}
return $data;
}
}
Final Thoughts
These view helpers are by no means complete but I will say that I’m currently using them in a production environment with no issues. I tested both declarative/programmatic usage and both functioned as expected. I also wanted to note that I’m by no means intending to step on Matthew’s toes but I know how busy he is with Zend Framework 2.0 (among other things). If anyone has any thoughts or use for this please comment below and, who knows, maybe I’ll make a proposal for it (or take over Matthew’s).