Tobiasz Cudnik

Events in CSS selector-driven markup templates make them highly reusable

In Ideas, QueryTemplates on 25.01.2009 at 16:36

One of the biggest adventage of DOM oriented template engines such as QueryTemplates is events support. Using Event Delegation and some Mutation Events you can set global handlers for all templates. Purpose of such handlers may be varient. I’m presenting 6 of them as examples.

Let’s start from events table

Each event can easily have many handlers. One of them is a mutation event – DOMNodeInserted.

$globalHandlers = array(
  array('unload', 'everyTableHaveTbody'),
  array('load', 'insertNavigation'),
  array('DOMNodeInserted', 'newListsAlwaysOrdered'),
  array('unload', 'noAds'),
  array('unload', 'clearEmptyClasses'),
  array('load', 'stripTables'),
);

Here’s our global handlers

// global handlers
// can be functions, methods, create_function and closures (in PHP 5.3)

function everyTableHaveTbody($e) {
  pq($e->target->ownerDocument)
    ->find('table')
      // nested closure should be here
      ->each('everyTableHaveTbodyCallback');
}
function everyTableHaveTbodyCallback($table) {
  if (pq('> tbody', $table)->length == 0)
    pq($table)->contents()->wrapAll('
<tbody>');
}
function noAds($e) {
  pq($e->target->ownerDocument)
    ->find('.ads')->remove();
}
function newListsAlwaysOrdered($e) {
  if (pq($e->target)->is('ul'))
    pq($e->target)->replaceWith(
      pq('
<ol>')->append(
        pq($e->target)->contents()
      )
    );
}
function insertNavigation($e) {
  $nav = pq($e->target->ownerDocument)
    ->find('.navigation')
      ->empty()
      // nested closure should be here
      ->each('insertNavigationCallback');
}
function insertNavigationCallback($nav) {
  pq($nav)->append(file_get_contents('navigation.html'));
}
function stripTables($e) {
  pq($e->target)->find('table.strip-me')
    ->removeClass('strip-me')
    ->find('> tr:odd, > tbody > tr:odd')
      ->addClass('odd');
}
function clearEmptyClasses($e) {
  $nav = pq($e->target->ownerDocument)
    ->find('*[class=""]')
      ->removeAttr('class');
}

Now let’s quickly extend phpQuery with method for batch event attaching.

function bindMulti($self, $handlers) {
  foreach($handlers as $handler)
    $self->bind($handler[0], $handler[1]);
  return $self;
}
phpQuery::extend('phpQueryObject', array(
  'bindMulti' => 'bindMulti',
));

The testing code

This is out testing code with some data and template that will be modified by global event handlers.

// this is out data
$rows = array(
  array(
    'field-1' => 'foo1',
    'field-2' => 'foo2',
  ),
  array(
    'field-1' => 'bar1',
    'field-2' => 'bar2',
  ),
);
// this is out file (page fragment)
$source = dirname(__FILE__).'/table.html';
require template('table')->parse($source)
  // bind all delegation events
  ->bindMulti($globalHandlers)
  // fire up manually
  ->trigger('load')
  // here some template processing takes place
  ->find('table:first > tr, table:first > * > tr')
    ->loopOne('rows', 'i', 'row')
      ->varsToSelector('row', $rows[0])
    ->end()
  ->end()
  // we're testing newListsAlwaysOrdered
  ->append('
<ul>
	<li>1</li>
	<li>2</li>
	<li>3</li>
</ul>
')
  // fire up manually again
  ->trigger('unload')
;

Result

The result is as expected. The template logic is shorter than 10 lines but many rules are applied during those lines.

<table>
<tbody>
<?php  foreach($rows as $i => $row):  ?>
<tr>
<td class="field-1"><?php  print is_object($row) ? $row->{'field-1'} : $row['field-1']  ?></td>
<td class="field-2"><?php  print is_object($row) ? $row->{'field-2'} : $row['field-2']  ?></td>
</tr>
<?php  endforeach;  ?></tbody></table>
<table>
<tbody>
<tr>
<td>row1</td>
</tr>
<tr class="odd">
<td>row2</td>
</tr>
<tr>
<td>row3</td>
</tr>
</tbody></table>
<ol>
	<li>1</li>
	<li>2</li>
	<li>3</li>
</ol>

If your concerned about speed in which this will execute and how long it delay your page request – don’t worry anymore. Not counting file inclusions and with template’s caching turned on it’s about… ~0.0005 of the second :) Why ? Because it just includes plain PHP file, which is generated one time, that’s why.

To get this code working just grab newest revisions of QueryTemplates and phpQuery.