Pages

Showing posts with label AJAX. Show all posts
Showing posts with label AJAX. Show all posts

Friday, October 16, 2009

Using jQuery's Flexigrid Plugin with Zend

There is no ready grid component in Zend Framework. Most of the forums recommend implementation using Dojo's grid tool, but since I'm new to both Zend Framework and Dojo, this didn't seem like all that great of an idea.

However, I have made frequent use of Flexigrid in the past and after making a half-hearted attempt at using Dojo, I decided that I might as well use Flexigrid -- at least I'm already familiar with how it works.

Now, none of this was obvious at first. Sure, I understand all of the principals of Zend Framework's MVC implementation, but figuring out where to put stuff is a real challenge.

This demonstration is meant to show how to get started. You'll want to make this more programmatic later, but hopefully it gets you to a working implementation.

AJAX Action

First, I would recommend creating the action handler for the AJAX call. You can test this to make sure it's the output expected by Flexigrid.

public function ajaxAction()
{
    $Table = new Model_User_Table();
    $total_rows = $Table->count(); # this is a custom method
    
    // Request parameters received via GET from flexigrid.
    $sort_column = $this->_getParam('sortname','name'); # this will default to undefined
    $sort_order = $this->_getParam('sortorder','asc'); # this will default to undefined
    $page = $this->_getParam('page',1);
    $limit = $this->_getParam('rp',10);
    $offset = (($page - 1) * $limit);
    $search_column = $this->_getParam('qtype');
    $search_for = $this->_getParam('query');
    
    // Build a query to get the results.
    $Select = $Table->select()->order("$sort_column $sort_order")->limit($limit,$offset);
    if (!empty($search_column) && !empty($search_for))
    {
        $Select->where($search_column.' LIKE ?','%'.$search_for.'%');
    }

    // Get the result rows using Zend's paginator.
    $Pager = Zend_Paginator::factory($Select);
    $Pager->setCurrentPageNumber($page);
    $Pager->setItemCountPerPage($limit);
    $rows = $Pager->getIterator();

    // Send headers.
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT" );
    header("Last-Modified: " . gmdate( "D, d M Y H:i:s" ) . "GMT" );
    header("Cache-Control: no-cache, must-revalidate" );
    header("Pragma: no-cache" );
    header("Content-type: text/xml");

    // Prep the XML.
    $xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?";
    $xml .= ">\n";
    $xml .= "<rows>";
    $xml .= "<page>$page</page>";
    $xml .= "<total>$total_rows</total<";
    foreach ($rows as $row)
    {   
        $xml .= sprintf('<row id="%s">',$row['id']);
        $xml .= sprintf('<cell<<![CDATA[%s]]<</cell<',$row['id']);
        $xml .= sprintf('<cell<<![CDATA[%s]]<</cell<',$row['name']);
        $xml .= sprintf('<cell<<![CDATA[%s]]<</cell<',$row['email']);
    }

    // Disable the default layout and output the XML.
    $this->getHelper('layout')->disableLayout();
    $this->getHelper('ViewRenderer')->setNoRender();
    $this->getResponse()->setBody($xml);
}

View Script

Next we'll look at the view script. In my app, the default action displays users in a grid, so in this case the view script was located at view/scripts/user/index.phtml and all this is where I put my javascript and table HTML (such as it is). The CSS and JavaScript should probably be abstracted into a Grid class along with the specification of rows and tables.

<style type="text/css">
<!--
.flexigrid div.fbutton .add { background: url(/eve/icons/small/plus.png) no-repeat center left; }
.flexigrid div.fbutton .delete { background: url(/eve/icons/small/subtract.png) no-repeat center left; }
.flexigrid div.fbutton .edit { background: url(/eve/icons/small/pencil.png) no-repeat center left; }
-->
</style>
<script type="text/javascript">
$(document).ready(function(){
    function add_record() { window.location='/user/add'; }
    
    function delete_record(command,grid)
    {
        var record_count = $('.trSelected',grid).length;
        if (0 == record_count)
        {
            alert('Please select a record first by clicking on the appropriate row.');
            return;
         }
         $('.trSelected',grid).each(function(){
             var id = this.id.substr(3);
             window.location = '/user/delete/id/'+id;
         });
     } // delete_record

     function edit_record(command,grid)
     {
         var record_count = $('.trSelected',grid).length;
         if (0 == record_count)
         {
             alert('Select a record first by clicking on the appropriate row.');
             return;
         }
         $('.trSelected',grid).each(function(){
             var id = this.id.substr(3);
             window.location = '/user/edit/id/'+id;
         });
     } // edit_record

    $('#users').flexigrid({
        url: '/user/ajax',
        dataType: "xml",
        usepager: true,
        useRp: true,
        rp: 10,
        sortname: 'name',
        sortorder: 'asc',
        buttons: [
            {name: 'Add', bclass: 'add', onpress: add_record},
            {name: 'Edit', bclass: 'edit', onpress: edit_record},
            {name: 'Delete', bclass: 'delete', onpress: delete_record}
        ],
        colModel: [
            {display: 'ID', name: 'id', width: 30, sortable: true, align: 'left'},
            {display: 'Name', name: 'name', width: 120, sortable: true, align: 'left'},
            {display: 'Email', name: 'email', width: 150, sortable: true, align: 'left'}
        ]
     });
});
</script>
<table id="users"></table>

Main Action

Last, change the indexAction() method of the controller.

$this->view->headScript()->appendFile('/scripts/jquery.js');
$this->view->headScript()->appendFile('/scripts/flexigrid/flexigrid.pack.js');
$this->view->appendStylesheet('/scripts/flexigrid/css/flexigrid/flexigrid.css');

Thursday, July 2, 2009

The Log File is Your Friend

So, I was working with the flexigrid plugin for jquery, which is pretty cool but not very well documented, and I encountered this strange problem where the grid was failing to display the output.

Now, with most of the grid tools you

  1. Create only the most basic HTML on your page.
  2. Write some javascript to make the AJAX call, also included on your page.
  3. Create a PHP (or whatever) script to generate the output which is typically JSON or XML.

In this case, I choose to generate XML for use by the flexigrid plugin. But my grid wasn't working. It would just sit there and spin, saying "processing".

  • The jquery javascript was working with no errors (confirmed this in the browser's debugger).
  • The PHP script was generating the correct XML (confirmed by manually entering the script address in the browser).

But it wasn't working when the PHP script was called from the javascript on the page.

The solution? When the PHP script was being called from the javascript on the page, the flexigrid plugin was sending sortname and sortorder values that were "undefined" in javascript. So the query against the database was something like "SELECT * FROM sometable ORDER BY undefined". This of course was generating SQL errors and the PHP script was failing to output the XML. But only when called from the page. It was fine when called manually (and flexigrid was bypassed). A simple check for undefined values in the PHP script and all is well.

This brings me to one of the great canons of programming for the Web (and perhaps other types of programming). The Log File is Your Friend. I discovered this problem by tailing the PHP error log. Otherwise, the failure is very puzzling because everything appears to be working, just not together. When using AJAX or creating Web services, things going on unseen may only be exposed by consulting the log(s).