dataStore and dataGrid view helpers.

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

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_htmlAttribs($attribs) . ">\n"; $table .= "\t\t" . $s['data']; $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).

Tags: BSD licenses, , , , , , Source code,