Friday, April 16, 2010

Zend Framework context switching for HTML content

I tried to stay away from using javascript as much as I could, but even I could not escape from developing AJAX feature rich applications. I have chosen jQuery as my poison.

But I had already build my apps using static HTML output generated by Zend Framework, so how could I add this richness to my apps without refactoring most of my code ? Simple, by using Zend_Controller_Action_Helper_ContextSwitch, ZendX_JQuery and some minor adjustments to my view scripts.

If you have downloaded the latest Zend Framework package, you'll notice in the extras/library directory the extra jQuery component, so make sure you also include that library path to your application's include path.

The following code was tested with Zend Framework version 1.10.3.

In your controller, you have actions that displays a form and one that processes it. So, to have it processed without refreshing the page in the browser, put the following lines in your controller:

public function init()
{
    $this->_helper->contextSwitch()
         ->setContext(
             'html', array(
                 'suffix'    => 'html',
                 'headers'   => array(
                     'Content-Type' => 'text/html; Charset=UTF-8',
                 ),
             )
         )
         ->addActionContext('index', array('html','xml', 'json'))
         ->setAutoJsonSerialization(true)
         ->initContext();
}

This context switch routine allows me to use my data in HTML, XML and JSON format. Both XML and JSON are default output formats supported by Zend Framework, but the documentation didn't contain a proper example on how to output HTML.

Consider you already have put a lot of time in rendering a table according to the specific form parameters, you don't want to re-do this work to display it again to use XML or JSON formatted data. Easiest (not best) way is to have that data returned in your view without the rest of the layout spoiling your AJAX requests.

Using the context switcher defined above, we only need to add a view template using the "actionname.html.phtml" naming convention. So for indexAction it would have index.phtml for normal displaying, index.xml.phtml for XML generated output, index.json.phtml for json-formated data and index.html.phtml to have your content returned without layout.

In our requesting view template we now only have to add a container where to put that data. Since I return the full generated table, I wrap my "table" tag in a "div" block.

<div id="tableContainer">
<table>...</table>
</div>

In jQuery I use the following function to load data inside that container:

function loadData(link, placeholder)
{
    if (-1 === link.indexOf('/format/html') && -1 === link.indexOf('format=html')) {
        link += '/format/html';
    }
    jQuery.get(link, function(data) {
        var container = '#' + placeholder;
        jQuery(container).html(data);
    });
    return false;
}


A common link like
<a href="/path/to/next/page">next page</a>
becomes
<a onClick="loadData(this.href, 'tableContainer');" href="/path/to/next/page">next page</a>

And with ZendX_JQuery you don't even need to load all the jQuery scripts, Zend Framework does this for you.

First modify your bootstrapper so your view uses the jQuery components:

protected function _initView()
{
    // Initialize view
    $view = new Zend_View();
       
    // Adding jQuery view helpers
    $view->addHelperPath("ZendX/JQuery/View/Helper", "ZendX_JQuery_View_Helper");

    // Add it to the ViewRenderer
    $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
        'ViewRenderer'
    );
       
    $view->doctype('XHTML1_STRICT');
    $view->headMeta()->appendHttpEquiv('Content-Type', 'text/html; Charset=UTF-8');



    // Add a stylesheet for JQuery parts, only when it's required
    $view->jQuery()->addStyleSheet(

        $view->baseUrl('/css/ui-lightness/jquery-ui-1.7.2.custom.css')
    );
 

    $viewRenderer->setView($view);
    Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

    // Return it, so that it can be stored by the bootstrap
    return $view;
}


In your layout template, you can add between the head tags the following call:

<head>
...
    <?php echo $this->JQuery() ?>
...
</head>

That's all there is to it. Now you can add nice date pickers and other awesome jQuery and jQuery UI elments to your apps.

I know it's not the best way to AJAX-ify your apps, but it's the easiest way to convert a non-javascripted app into a more user friendly application. A nice side-effect is your app remains working for non-javascript browsers and web spiders (like those for search engines).

If you want to learn more about ZendX_JQuery, be sure to check out a presentation given by Dennis De Cock at one of the PHPBenelux UG meetings. His session has inspired me to take a deeper look at jQuery as a javascript library and ZendX_JQuery as the tool to master it.
Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License.