Four Kitchens
Insights

Theme it once

3 Min. ReadDesign and UX

The popularity of front-end JavaScript frameworks, driven largely by improvements in JavaScript performance, is skyrocketing. One of their strongest features is allowing developers to build extremely rich applications that will work the same way on most modern platforms. So it comes as no surprise that backbone.js found its way into Drupal 8 as a core JavaScript library. If you haven’t worked with a rich JavaScript application yet, you can be certain you will soon.

A key component of any rich JavaScript application, be it built on a framework or not, is templating. Each templating engine behaves slightly differntly but there’s one thing that they, and in fact all templating engines, have in common: scaffolding markup has placeholders which are later replaced with variables, like this:

That should look relatively familiar, in Drupal we’re used to seeing things like this:

Front end templating is great – it’s flexible and allows you to offload some of the processing power required to render a page to the client. There are some gotchas though, users without JavaScript support (or minimal JavaScript support) is an obvious one. A less obvious case that front end templating isn’t ideal is when you need to be able to render a page both on the client and server side. Dan Webb (@danwrong) from Twitter wrote extensively about Twitter’s move from an exclusively front-end rendered site to a mix of front-end and back-end rendered pages. In their case, and in many cases, a webpage can be delivered and ready for user interaction much faster when it’s been rendered on the server.

So, what am I getting at, and how does it relate to Drupal? Using either of the front-end templating gotchas from above, you can see that we’d need to have a template for both the frontend and backend source. That obviously sucks because now we have twice the markup that we need to deal with.

It doesn’t have to be that way though. By relying on Drupal’s rendering pipeline we can use one set of markup for both our front-end and back-end templates. Consider this simple module. In the theme_it_once() function we’re constructing an array of items to theme. Simple enough, but there’s something different about this page callback:

return array(
   'items' => array(
     '#theme' => 'item_list',
     '#items' => $items
   ),
   'template' => array(
     '#theme' => 'example_item',
     '#name' => '{name}',
     '#description' => '{description}',
     '#prefix' => '<li>',
     '#suffix' => '</li>',
   ),
 );

This render array has an additional element – template – which adds the markup from our PHP template to the page, wrapped in a script tag so it won’t be displayed. The resulting markup, which should look very familiar, will look like this (with some extra whitespace added for readability):

Now let’s move to the front end and see how we can take advantage of this. In our example we’re going to use dust.js for the front-end templating engine, but the concept will be the same regardless of what you choose. Consider this script:

Notice that we’re grabbing the HTML from our template and compiling it into dust. We’re then rendering the template with a name and description variable and appending the result to the list generated by Drupal. Yes, it really is that simple to write your markup once and use it everywhere!

Beyond Examples

One of the biggest problems with this example for a rich application is the overhead associated with compiling the templates and the access to the DOM to fetch the template in the first place. In our example we only have one template, so the hit we take isn’t that great, but if we had many templates we’d want to precompile them. This obviously means adding a build task to create the generated templates file as needed. A drush command would be a great way to still keep everything in PHP templates. Your command would pass the front-end placeholders into your theme function in exactly the same way as I’ve shown here, but would then pass the resulting markup off to a child process – possibly grunt.js – to compile that markup.

A functional example of this code can be downloaded from the gist.