Skip navigation.
Home
That which cannot be rendered in binary is by definition a delusion
 

Using Dojo Grid in Zend Framework (or PHP)

The one component that sold me on Dojo is the grid; pretty much everything else that Dojo has is covered by all other component systems (YUI, jQuery) but the grid is the standout. I say this for these reasons:

  • It allows you to load only part of a vast dataset, loading more and more chunks as you go
  • This means you can scroll through a billion records -- no stupid pagination required
  • You can edit a grid in context with a dynamic form object, making it a perfect analog to the spreadsheet
  • You can "freeze" a set of columns and scroll the other set, again, like Excel "freeze panes"
  • You can "span" columns or rows, load images, or do anything else you'd likely want to do with, say, a table rowset.
  • You can very easily parameterize cell values, enabling sumary columns, filtered display such as image buttons ("X" or whatever)
  • Column sorting with a single click
  • column resizing
  • reloading of data (such as a filtered search) without reloading the page

Essentially all the killer function of Excel in a widget. Since Excel is the "killer app" for so many companies, allowing nontechnical people do to vast amounts of database-like or programming-like tasks, enabling an Excel-like module for a web page is the "killer app" for web 2.0 IMHO.

Dojo's grids do take a little bit of education and care. The best tutorial on Dojo Grids can be found on Sitepen. What I want to focus on here is back-ending the grid in PHP. My example employs Zend Framework but a little work should allow these techniques to be used in any PHP system.

Some technical notes

This tutorial presumes you are at least somewhat fluent in Dojo (and Javascript) and PHP (and hopefully, Zend Framework). It is not nearly as complete and exhaustive as the SitePen's as to the workings of Dojo's Grid system, but it hopefully exposes the server side mechanics that allow you to deliver the data that Grids require and show how easy it is to slap out a grid once the groundwork has been done.

I am using the declarative method of Grid representation -- in other words, I'm using HTML markup to define the grid. You can do much of this using pure Javascript objects, but I find that the declarative notation is much clearer and more accessible.

Elements of a Grid

The Data Store

The Grid component depends on a seperate widget to store its data in. This gives you a "Hook" for updating data on the fly as is necessary for "lazy loading" large sets using the queryReadStore. The Dojo Stores expect to point towards a JSON file; however, they can also be pointed towards a dynamic page that generates JSON on the fly.

In this instance we will be using PHP and the Zend Dojo systems to generate our JSON -- I'd point out though that anything that chucks out JSON would serve as a data store. (heck, you could use Java/Hibernate, Akamai, or anything else that gives you the appropriate markup.)

As I use ZF, I create actions in controller to generate JSON. Because only the JSON chunk is desired, I turn off the Layout and the View subsystem and just echo out the out value of the Zend_Dojo_Data object. I have a grid interface on my domain objects (in Zupal) that returns data from each Domain class that I enable Grids for; the net profile in the controller (in this context, contained in the Users controller) is:

    public function dataAction ()
    {
        $this->_helper->layout->disableLayout();
        $this->_helper->viewRenderer->setNoRender();
        echo Zupal_Users::getUserInstance()->render_data(array(), 'username');
    }

the "getUserInstance" returns a statically stored Zupal_Users object.

Inside the Zuap_Users object, the render_data model is:


    public function render_data(array $pParams, $pStart = 0, $pRows = 30, $pSort = NULL)
    {
        $cache = Zupal_Bootstrap::$registry->cache;

if (!$cache->test('user_data')):
if (!array_key_exists('username', $pParams)):
$pParams['username'] = array('', '!=');
endif;

$select = $this->_select($pParams, $pSort);
$rows = $this->table()->fetchAll($select);
// @var $rows Zend_Db_Table_Rowset
$items = array();

foreach($rows as $row):
$items[] = $row->toArray();
endforeach;
$data = new Zend_Dojo_Data($this->table()->idField(), $items, 'username'); $cache->save($data);
endif;

return $cache->load('user_data');
}

This controller uses a Zend_Cache object to cache the result -- the result being a Zend_Dojo_Data object, whose purpose is to transport data in the format that Dojo expects. Its __toString() method returns JSON, so echoing this object results in the JSON object we are looking to render.

The elements in red are custom to Zupal - my flavor of ZF. They generate a Zend_Data_Select object based on a simple array of param/value filters and a sort field.

the next section reduces the rowset to a simle array of associative row arrays.

Finally(or at least "finally" inside the caching context) the Zend_Dojo_Data object is fed the data.

{"identifier":"person_id",
"items":[
{"person_id":19,
"gender":"M",
"title":"Mr",
"name_first":"Dave",
"name_last":"Edelhart",
"name_middle":null,
"email":"davenospam@wonderlandlabs.com",
"password":"XXXXXX",
"username":"bingomanatee",
"born":null,
"died":null,
"created":"2009-06-24 09:49:45"}
...
]
,"label":"username"}

Accessing the Data Store with Dojo

All the work above is required to produce the JSON in the format that the Dojo framework expects.

<span dojoType="dojo.data.ItemFileReadStore"
jsId="users_store" url="/people/users/data/rand/51098" />

While there is no great reason to not just type this all out yourself, I have a Grid object (included below) that pops out this bit of markup:

public static function store ( $pStore_ID, $pURL)
{
?>
<span dojoType="dojo.data.ItemFileReadStore"
jsId="<?= $pStore_ID ?>" url="<?= $pURL ?>/rand/<?= rand(0, 100000) ?>" />
<?
}

Note that the /rand/ suffix expects the url to be in SEO format -- all directory/paths, no query params. This is not required by Dojo - I just do this to insulate the data against any caching.

This store is then called by a table that is annotated with Dojo Grid markup.

<table rowsPerPage="10" style=" height: 400px" 
dojoType="dojox.grid.DataGrid" clientSort="true" query="{ person_id : '*' }" store="users_store"> <thead> <tr> <th field="person_id" width="40" >ID</th> <th field="gender" width="20" >G</th> <th field="name_first" width="80" >First Name</th> <th field="name_last" width="100" >Last Name</th> <th field="username" width="120" >User</th> <th field="email" width="200" >eMail</th> </tr> </thead> </table>

Note that this table is a true HTML table that dojo uses as a data definition matrix. It refers to the store widget by name. The initial query polls all rows who have any value in their identity field -- this effectively polls all rows in the store. The head row actually serves as a column definition that Dojo uses as a guide to know which elements of the store we actually want to display. If you look at the JSON above, there's a lot of data in the store -- we don't really want (for this puropose) to show it all, just a few key columns.

All of this is enabled by including the Dojo libraries.

<style type="text/css">
<!--
    @import "/scripts/Dojo/dijit/themes/tundra/tundra.css";
    @import "/scripts/Dojo/dojox/grid/resources/Grid.css";
-->
</style>
<script type="text/javascript">
//<![CDATA[
    var djConfig = {"parseOnLoad":true};
//]]>
</script>
<script type="text/javascript" src="/scripts/Dojo/dojo/dojo.js"></script>

<script type="text/javascript">
//<![CDATA[
dojo.require("dojox.data.QueryReadStore");
    dojo.require("dojox.grid.DataGrid");
    dojo.require("dojo.data.ItemFileReadStore");
    dojo.require("dijit.form.Form");
//]]>

</script> 

Again: while hard-coding this into your file (or view, or layout) is not really much of a chore, I have a method in Zupal_Grid_Maker that accomplishes this. Also as I've compressed the grid module of dojo into a javascript "Layer" -- a single file that subsumes all of the eighty odd javascript files the grid contains into a single file -- my personal files call the grid libraries as such:

<style type="text/css">
<!--
    @import "/scripts/Dojo/dijit/themes/tundra/tundra.css";
    @import "/scripts/Dojo/dojox/grid/resources/Grid.css";
-->
</style>
<script type="text/javascript">
//<![CDATA[
    var djConfig = {"parseOnLoad":true};
//]]>
</script>
<script type="text/javascript" src="/scripts/Dojo/dojo/dojo.js"></script>
<script type="text/javascript" src="/scripts/Dojo/dojo/grid_layer.js"></script>
<script type="text/javascript">
//<![CDATA[
dojo.require("dijit.form.Form");
//]]>

This might not seem that worthwhile but if you use Firebug to see all the secondary calls that Dojo makes when loading the grid library as opposed to loading the layer you'll see the huge savings in transmission and time.

Attached is

  • My Zupal_Grid_Maker class, which programmatically creates these blocks and more, and the layer I use to load Dojo's grid as a single unit.
  • The iGrid file that defines the interface of a client that employs Zupal_Grid_Maker
  • My Dojo Grid Layer. This belongs in the dojo folder in whatever directory you store your scripts in.
  • My Users class - a domain class that employs the iGrid interface. It will not function outside of its context application (Zupal) but it is a useful sample of how to satisfy the iGrid interface
  • My User view class, a Zend Framework script that uses the above to render a list of users using the above. As this is part of the ZF, it also requires its context to run, but its useful as a sample.
AttachmentSize
Maker.php.txt6.53 KB
IGrid.php.txt318 bytes
Layers.zip376.06 KB
Users.php.txt1.66 KB
index.phtml1.05 KB

Post new comment

  • Allowed HTML tags: <a> <p> <span><small> <div> <h1> <h2> <h3> <h4> <h5> <h6> <img> <map> <area> <hr> <br> <br /> <ul> <ol> <li> <dl> <dt> <dd> <table> <tr> <td> <em> <b> <u> <i> <strong> <font> <del> <ins> <sub> <sup> <quote> <blockquote> <pre> <address> <code> <cite> <embed> <object> <param> <strike> <caption>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options