Sharing Templates Between PHP and JavaScript

Tweet

Templating in web applications is crucial for separating your business logic from your presentation. There are two types of templates: server-side templates, which are used through a server-side templating engine during the initial page load, and client-side templates, which are used for working with JavaScript-based applications or Ajax requests.

In scenarios where your web application depends heavily on Ajax, it can be difficult to maintain both server-side and client-side templates without duplication. Hence it can be useful to choose a templating engine which provides both client- and server-side support which allows maximum reusability.

Mustache is quickly becoming one of the more popular templating engines among those available, and provides implementations for different programming languages and platforms to cater to our needs.

In my article Creating HTML Templates with Mustache.js on JSPro, I showed you how to work with Mustache templates in client-side code. If you don’t have any previous experience working with Mustache, I recommend reading it. In this article I’ll focus on creating server-side Mustache templates and sharing the same set of templates with client-side JavaScript.

Introduction to Mustache.php

The Mustache implementation in PHP is named Mustache.php, and you can grab a copy of the library using from the official GitHub project page. However, if you are familiar with using Composer, I recommend you to install Mustache using that for better dependency management (and if you’re not familiar with Composer, I recommend you read PHP Dependency Management with Composer).

Add the following to your composer.json file and run either composer install or composer update (whichever is appropriate):

{
    "require": {
        "mustache/mustache": "2.0.*"
    }
}

Let’s start by looking at a simple example for doing templating with Mustache.php.

<?php
require 'vendor/autoload.php';

$tpl = new Mustache_Engine();
echo $tpl->render('Hello, {{planet}}!', array('planet' => 'World'));

First the Composer autoloader is included (alternatively you can use Mustache’s autoloader if you are working with a cloned or downloaded copy of the library). Then the render() method of the Mustache_Engine class is called to generate the view by passing it the template data and some replacement values.

Ideally you wouldn’t provide the template contents inline like the example does. Templates should be kept in their own files in a dedicated directory. You can configure the path to the template files when creating an instance of the Mustache_Engine class.

<?php
$mustache = new Mustache_Engine(array(
   'loader' => new Mustache_Loader_FilesystemLoader('../templates')
));

$tpl = $mustache->loadTemplate('greeting');

echo $tpl->render(array('planet' => 'World'));

loader in the configuration array defines the path to the template files (more details on the configuration parameters can be found in the Mustache.php Wiki). In this scenario, template files are located inside the templates directory. The loadTemplate() method loads the file templates/greeting.mustache.

Having briefly covered the necessary theory for working with Mustache, we can now look at how to share the templates.

Sharing Templates between PHP and JavaScript

Our main goal is to share the templates between PHP (server-side) and JavaScript (client-side). For the basis of our example, let’s assume we have an e-commerce site with two product categories: Books and Movies. When the user first visits, all of the products will be displayed in a single list as shown below.

You can see that the details of each product type are different; hence we need separate templates for the two categories.

Create two templates, one called books.mustache and another movies.mustache. This is how the books.mustache file will look like:

{{#products}}
{{#book}}
<div>
<p>Book Title: {{title}}</p>
<p>Book Author: {{author}}</p>
<p>Book Price: {{price}}</p>
</div>
{{/book}}
{{/products}}

The above is a simple template with a section for looping purposes and some template-specific variables. {{#products}} is used to loop through all of the elements inside the products section of the array we will provide. {{#book}} is used to check if the item is actually a book. If the book key doesn’t exist, then nothing will be displayed to the user.

On the initial page load, all of the products need to be retrieved and displayed to the user, so we use PHP to generate the initial display using both the template and the data set.

<?php
$books = array();
$result = $db->query('SELECT title, author, price FROM books');
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
    $row['book'] = true;
    $books[] = $row;    
}
$result->closeCursor();

$movies = array();
$result = $db->query('SELECT name, price, cast FROM movies');
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
   $row['movie'] = true;
   $movies[] = $row;    
}

$booksTmpl = $this->mustache->loadTemplate('books');
$moviesTmpl = $this->mustache->loadTemplate('movies');

$data = array(
    'products' => array_merge($books, $movies)
);
$html = $booksTmpl->render($data);
$html .= $moviesTmpl->render($data);

echo $html;

We use two database queries to pull the book and movie data from the database and save it into an associative array for passing into the templates . I’ve used basic PDO methods for pulling the data; you can use your own database abstraction layer or ORM if you like.

The templates for books and movies are loaded separately into two variables using the loadTemplate() method, and I pass the products array to each template’s render() method. It applies the template to the product data and returns the HTML output.

To filter the results based on product type, we could get all the filtered books (or movies) as rendered HTML from the server, but all of the markup would incur additional overhead for each request. We really don’t want to have to send the same markup over and over again. This is where the client-side templates come into play – we can keep the necessary templates loaded into memory on the client and only load the data from the server using Ajax.

To make the templates available to the client, they can be injected in the initial page request in <script> tags for future use by JavaScript. The code inside these tags will not be displayed, nor will they be executed by JavaScript if we set the type to text/mustache.

<script id="booksTmpl" type="text/mustache">
<?php
echo file_get_contents(dirname(__FILE__) . '/templates/books.mustache');
?>
</script>
<script id="moviesTmpl" type="text/mustache">
<?php
echo file_get_contents(dirname(__FILE__) . '/templates/movies.mustache');?>
</script>

In order to use these templates in JavaScript, we just need to include the Mustache.js library in the document. You can grab a copy of mustache.js file from its GitHub page and include it in your HTML as following:

<script type="text/javascript" src="mustache.js"></script>

Once the user selects the filter option, we can get the data using an Ajax request. But unlike the initial page load, we won’t apply the templates to the data before sending them to the client since we have the templates available client-side already.

<script>
$("#booksFilter").click(function () {
   $.ajax({
       type: "GET",
       url: "ajax.php?type=books"
   }).done(function (msg) {
       var template = $("#booksTmpl").html();
       var output = Mustache.render(template, msg);
       $("#products").html(output);
   });
});
</script>

The book list is retrieved from the database and returned in JSON format to the client, and then we fetch the book template from within its script tag using $("#booksTmpl").html() and pass both the template and data to the client-side Mustache rendering function. The generated output is then applied back to a products section on the page.

Summary

Working with separate template engines both client-side and server-side can be a difficult task and can lead to template duplication. Luckily, various implementations of Mustache allow us to share the same templates between both sides. This increases the maintainability of your code. If you’d like to play around with some example code for this article, you can find it on the PHPMaster GitHub account.

Feel free to share your suggestions and previous experiences with Mustache templates in the comments below.

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Jools

    Thanks for this intro to Mustache.
    Instead of merging $books and $movies and then passing it all to each template you could do the following in the initial display:

    $html = $booksTmpl->render(array(‘products’ => $books));
    $html .= $moviesTmpl->render(array(‘products’ => $movies));

    • http://www.innovativephp.com Rakhitha Nimesh

      Hello Jools

      sorry for being late to reply. Thanks for pointing that out.It looks like I could have gone with the array merge.

      Thanks

  • http://mikemclin.net Mike McLin

    Thanks, I really enjoyed this post. It convinced me to get off of Handlebar.js and start using Mustache because of all of the languages that have libraries for it. You actually inspired me to create a video on the subject. You can find it here: http://mikemclin.net/mustache-templates-for-php-and-javascript/

    • http://www.innovativephp.com Rakhitha Nimesh

      Hello Mike
      I am glad that you liked my tutorial.
      Your video tutorial on mustache is awesome. I am planning to start on video tutorials as well. I hope you can share some tips on creating video tutorials with me :)

      • http://mikemclin.net Mike McLin

        Glad you enjoyed the video. I use Camtasia (by Techsmith) for my screencasts. It is $99 for Mac, and they have a Studio version for PC that is $299. I actually own both, and prefer the Windows version. These programs are great, and they’ve got a bunch of tutorial screencasts on their site to help you get started. Keep up the good work :)

  • ano

    pity that old smarty engines are not updated to the ajax versions …