Using Symfony Partials In DHTML and Ajax

Thursday, 21 August 2008

Often times when building dynamic applications with Ajax or DHTML you'll come across the need to use a snippet of html code over and over. On the server side, this is typically accomplished with the use of reusable pieces of code that get stitched together to form a single web page. Wouldn't it be nice to be able to use the same code fragments that are used to render the page on the server side, on client side?

Symfony, for example, allows for several different types of these code fragment components, the most basic being the "partial". Partials are particularly useful when you need to add or update content to a page via Ajax.

For example, here's a simple set of select boxes:

select lists

You can dynamically add a new select box by clicking the "Add another country" link or remove one using the "Remove" link. Pretty standard stuff. To generate one of these rows, we could include a partial from within our main template. Something like this:

<div id="country_lists">
	<?php include_partial('preferences/countrySelect', array('countries' => $countries, 'first' => true)); ?>
</div>

And here's the partial's contents:

<div class="country_list">
	<?php echo select_tag('countries[]', options_for_select($countries, null, array('include_custom' => '-- Select Country --')))?> &nbsp; 
	<?php echo link_to('Add another country', '#', 'class=add') ?>
	<?php if (!isset($first)): ?>&nbsp; | &nbsp; <?php echo link_to('Remove', '#', 'class=remove') ?><?php endif ?>
</div>

… as you can see, I'm doing a check to see if the partial we're rendering is the first in the list and if it is, the link to remove the row is not included. Also, I've named the select_tag countries[] so that PHP automatically collects all of the results for these tags into one array on submission, regardless of how many the user added.

So far this is all really standard stuff. The interesting part comes when you need to dynamically add another row through javascript. For the javascript interactions I'll be using jQuery and some simple event delegation. Here's an example of how it might work:

<script type="text/javascript" charset="utf-8">
    // event delegation
    $('body').click(function(event) {
      var element = $(event.target);

      // "add" link clicked
      if (element.is('a.add')) {
        element.parent('div.country_list').after("<?php echo escape_javascript(get_partial('preferences/countrySelect', array('countries' => $countries))) ?>");
      }
      // "remove" link clicked
      else if (element.is('a.remove')) {
        element.parent('div.country_list').remove();
      }
    });
  });
</script>

Note that since we're writing this jQuery code from within a symfony template, we have access to all of the Symfony goodness. For example, I'm echoing a partial from right within the jQuery after() call. Using simply include_partial directly would most likely result in a javascript parser error as the countrySelect partial contains line-breaks and both single and double quotes, so we're using get_partial instead. The call to get_partial returns the partial as a string instead of echoing it directly. Then I'm wrapping it with a call to escape_javascript which is a standard symfony helper that will escape all of the potentially problematic characters so that the string can be used within a javascript call.

So now, after the page has been rendered, the partial's code is injected right into the jQuery call. This is basic DHTML, but could be further extended with Ajax if we needed to insert a partial with dynamic content. New in Symfony 1.1 is the ability to render a partial directly from within an action so you don't have to create a template that would just hold an include_partial call. This is perfect for Ajax. See the API docs for more information on renderPartial().