Archive for September, 2009

Taking template inheritance to the next level

Thursday, September 10th, 2009

The design of Tierra Templates was heavily influenced by Django’s template inheritance.  Template inheritance (as defined in Django docs) “allows you to build a base skeleton template that contains all the common elements of your site and defines blocks that child templates can override.”.

Template inheritance can be thought of as the inversion of template or server side includes – instead of working from the base template and including blocks you start with defining the blocks and work your way up the inheritance tree to the base template.

Let’s look at a simple example, starting with a base template called _page.html

<html>
  <head><title>Example.com {@ pageTitle ? `- {$}` @}</title></head>
  <body>
    [@ start content @]
      this is the default content
    [@ end content @]
  </body>
</html>

and then a let’s create our homepage at index.html:

[@ extends "_page.html" @]

<@ pageTitle = "Homepage" @>

[@ start content @]
  Welcome to example.com!
[@ end content @]

When the index.html template is loaded the generated template code sets the pageTitle variable to “Homepage” and sets the contents of the content block.  The index.html template then loads the parent _page.html template which sets the HTML <title> contents using the pageTitle in the conditerator and replaces the default content in the content block with the content from the index.html template.

The first example is pretty much exactly what you would do in Django or other templating systems like Dwoo that support inheritance.  But like the late night infomercials say, that’s not all you can do with Tierra Templates.  We’ve extended our inheritance system to support:

  • prepending and appended content to blocks in parent templates
  • echoing block content created by the template calling code or by the template’s children
  • conditionally performing block operations with support for cascading else blocks
  • block decorators which are functions called at code generation time allowing you to wrap blocks in code
  • a page level psuedo block that lets you apply decorators to the content of entire page

Let’s see how each one of these extensions work:

Prepending and Appending Blocks

You can prepend and append content by using the “prepend” or “append” block keyword instead of “start”.  Here is a simple example where the child template adds html inside the parent template’s head tag.  First the parent template using _page.html again.

<html>
  <head>
    [@ prepend pageHead @]
      <title>Test</title>
    [@ end pageHead @]
  </head>
</html>

and then the child template:

[@ extends "_page.html" @]

[@ prepend pageHead @]
  <style>
    a {text-decoration: none; }
  </style>
[@ end pageHead @]

when the child template is output the <head> element will look like:

<head>
  <title>Test</title>
  <style>
    a {text-decoration: none; }
  </style>
</head>

Note that the parent block also used “prepend”.  If it used “start” then the block contents set in parent would be ignored.  Using “start” is equivalent to saying “if this block hasn’t been defined then set its contents to this…”.   The parent could also use append but then the order of the output of the head would be switched and the <title> would come after the style.

Echoing Blocks

Echoing existing block content is a nice shorthand way to output block content created by the template’s calling code or its children.  Using echo is functionally equivalent to starting a block and then immediately closing it.   In code this looks like:

[@ echo content @]

instead of

[@ start content @][@ end content @]

Conditional Blocks

Conditional blocks use the same if syntax that conditerators use and support cascading else blocks.  Let’s look at a simple example where a different message is shown depending on some application defined function that returns the user access level:

<@ userLevel = getUserLevel() @>
[@ start if userLevel == "admin" @]
  you are an admin
[@ else if (userLevel == "editor") || (userLevel == "copyeditor") @]
  you are an editor or copy editor
[@ else @]
  you are a normal user
[@ end @]

Note that in conditional blocks you aren’t required to use a block name and the conditionals define the code path so if you have a function call in an if or else clause that is false then that function is not called.

Block Decorators

Block decorators are functions called by the code generator while the template is being compiled and give you a powerful way to plugin to the Tierra Template code generation process.  They are not called when the cached template code is executed.  Block decorators are called twice, once at the start of the block and once at the end.  You can name multiple block decorators for a single block and you can remove a block decorator from a parent block.  Let’s see how block decorators work with the built in memcache block decorator:

[@ start sidebar do memcache({for: "30 minutes", vary: "session['user_id']"}) @]
  {@ runHugeQueryToGetSidebarLinks() ? "<ul>" `<li><a href="{link}">{text}</a></li>` "</ul>" @}
[@ end sidebar @]

When this template’s code is generated the memcache function is called with two arguements, the first is an automatically generated array with the calling context and the second is the array (json objects are deserialized to arrays) containing the template parameters.  The built in memcache code uses the context and template parameters to wrap code around the block to lookup the block content in the memcached server using a block hash provided in the generated context array.

You can write your own block decorator functions very easily.  As an example let’s write a decorator that wraps a block in start and end comments that you can use when you are viewing source in the browser.  Let’s call it addHtmlComment.  Here is what the function would look like:

function addHtmlCommentDecorator($context, $comment) {
  if (!$context["guard"])
    return "";
  return "<!-- " . ($context["isStart"] ? "start" : "end") . " {$comment} -->";
}

you could then setup the search path to find the function (covered in a future blog post) and then use the decorator in your templates like this:

[@ start copyright do addHtmlComment("copyright") @] &copy; {@ date("Y") @} [@ end copyright @]

this would output (in 2009 as I write this):

<!-- start copyright --> &copy; 2009 <!-- end copyright -->

The check for the context guard is made because you can remove decorators from parent blocks in child templates.  Let’s say for a page you don’t want to add the copyright html comment  In the child template you would do this:

[@ start copyright do remove addHtmlComment @]
  &copy; {@ date("Y") @}
[@ end sidebar @]

this would cause the addHtmlComment decorator to not be called in the parent.  Note that block decorators are called in parent templates even for blocks already defined in the children.  This makes sense if you think about wanting to cache the main content block in the parent – you wouldn’t want to have to add the decorators to each child template that overrode the main content block.  Also note that parameters to block decorators need to be literal values since the decorator is called at compile time.  It can’t use template variables or request variables BUT the code that the decorator outputs can use them as that is run each time a template is loaded.

Page Blocks

Finally page pseudo blocks let you run block decorators with the content of the entire page.  You can use the built in memcache decorator to cache the entire page varying by the page’s query string and logged in user and wrap it in an html comment by doing the following:

[@ page do memcache({for: "until midnight", vary: "get, session['username']"}), addHtmlComment("page") @]

note that the order is important – in this order the html comment is cached, if you reverse the order the html comment is added to the cached page and then output.

Conclusion

This introduction to the Tierra Template inheritence system was rather long but I hope it gives you an idea of the cool things it will enable you to do with your applications.  Once you start using template inheritance you’ll never want to go back to using includes.

What’s a “conditerator”?

Tuesday, September 8th, 2009

One of the core features of Tierra Templates is a new code construction called a “conditerator.”    Conditerators combine an if condition with a loop iterator and greatly reduce the size and complexity of your templates.

One of the most common code patterns you see in pure PHP code or other templating engines is this:

if (isset($users) && (count($users) > 0) {
  echo "<div id='users'>Users:<ol>";
  foreach ($users as $user)
    echo "<li>{$user->fullname}</li>";
  echo "</ol></div>";
}
else
  echo "No users found";

Conditerators reduce that code to this:

{@ users ? "<div id='users'>Users<ol>" `<li>{fullname}</li>` "</ol></div>"
else "No users found" @}

Conditerators can be read like PHP’s ternary if with the colon (:) replaced with the keyword “else” (the colon character is used in the templating syntax to link up filter functions so it can’t be used here without introducing ambiguity to the parser).  In the template conditerators  are delimited with {@ and @} and have the following syntax:

{@ (<statement>;)* [expression] ([if <expression] ? [<before loop output>]
 [<loop output>] [<after loop output>])+  [else <else output>] @}

The optional leading list of statements allow you do to assignments and related function calls before the main expression (if present) is evaluated using the following code:

if (is_array($expression) || is_object($expression))
  $result = count($expression) > 0;
else if (is_string($expression))
  $result = strlen($expression) > 0;
else if (is_int($expression))
  $result = $expression != 0;
else
  $result = $expression;

this differs slightly from PHP’s empty() function in that strings with “0″ are true.

If the expression is true then right side of the ? is evaluated with up to 3 output expressions being accepted.  If the expression is an array then the loop output is evaluated with each item in the array, otherwise  it is only output once.  If the expression is false, then the single else output expression is used.

Here are some more examples of simple conditerators and their output after the =>.

{@ “foo” @} => “foo”

{@ bar = false; “foo” if bar else “bam” @} => “bam”

{@ bar = 1; “foo” if ((bar * 3) < 2) ? bar else if ((bar * 3 < 10) “bam” else “boom” @} => “bam”

{@ bar = 1; if bar < 2 ? “foo” else “bam” @} => “foo”

Here are some conditerators showing Tierra Template’s built in support for JSON.  The ‘$’ character is a special variable meaning the current value of the conditerator loop.  There are other special values like $previous, $next, $first, $next, $count, $even, $odd, etc.

{@ [1,2,3,4] ? $ @} => “1234″

{@ [1,2,3,4] ? “Numbers: ” $ @} => “Numbers: 1234″

{@ [1,2,3,4] ? “” $ ” – after” @} => “1234 – after”

{@ [1,2,3,4] ? “foo” @} => “foofoofoofoo” @}

{@ [1,2,3,4] ? `<p>{$}</p>` @} => “<p>1</p><p>2</p><p>3</p><p>4</p>”

{@ [[1,2,3],4,[5,6]] ? (count($) > 1 ? “foo” else $) @} => “foo4foo” @}

Finally here is a conditerator containing another conditerator.  The $$ special variable is the value of the parent loop, $$$ is the grandparent, etc.  The template syntax uses dot notation to access members of an object so $$.name in the conditerator below means the name attribute of the current loop value of the parent loop.

{@ [{id: 1, name: "Fred", posts: [{id:1000, title: "Test"}]}, {id: 2, name: “Barney”}] ? (posts ? `<p>{$$.name}<ol>` `<li><a href=”/post/{id}”>{title}</a></li>` “</ol></p>”) @} => “<p>Fred<ol><li><a href=”/post/1000″>Test</a></li></ol></p>”

I hope this has been a clear introduction to the power of conditerators.  As we flesh out the documentation in preparation of the initial beta release we’ll include a cookbook of common conditerators.  Please let us know what you think and if there are any other examples you would like to see.

Announcing Tierra Templates

Thursday, September 3rd, 2009

We are pleased to announce the availability of an alpha version of our PHP templating framework on GitHub.  Tierra Templates is a ground up rewrite of Tierra’s internal templating framework incorporating many new internal feature requests from using it on multiple client projects.  We are announcing the alpha version to get feedback from the community with the plan to release the initial beta version at 0.9 sometime in mid to late September and the stable 1.0 version in December.

Tierra Templates was inspired by the template inheritance structure of Django templates and extends it to allow blocks to be both conditionally included and easily appended and prepended.  Its most unique feature however is a new code construction called a “conditerator” that combines conditional logic and an iterator allowing you to dramatically reduce your template code.  Other unique features include block decorators which are functions called at compile time allowing you to add your own code during code generation and a function call syntax enabling you to call functions in external files within their own namespace.  Built-in decorators allow you to do things like easily cache fragments of your templates using memcached and set expire times for pages.

We will be blogging about each of the major features of the framework and how we built the framework in the days to come.  Please subscribe to the RSS feed and stay tuned!