Accessing a Common Template with Handlebars JS and Twig

Handlebars and Twig

For you full-stack developers out there, I’d like to share a nifty coding tip with you. There are certain situations where you may want to create some consolidated template markup that could be shared between the front-end and back-end of your application. Let’s take the scenario of a list of posts of users sharing their services or brief summaries about themselves, which you need to render from the backend on page-load (whether for search engine optimization benefits or other reasons) rather than through an AJAX request. You also have a form that allows a new user to submit his own post, but before submitting, you would like to let the user see a quick preview of their post without having to make a call to a backend script that returns a template.

The less desirable approach to making this work is to form a string of HTML representing the preview post by concatenating the necessary markup with the correct variable names and then to call a function that returns this as a DOM element. The main problem with this is: 1) it is generally messy — especially for long strings of markup — and can be difficult to track down if you are working on a large code-base that has multiple types of these snippets, and 2) if you are already serving templates from a backend script in order to render the existing list on page load, you will most likely end up with two copies of the same markup, which means duplicated code — not very efficient for upkeep.

//the less desirable approach
function renderPreview(user_id, user_name, user_pic, title, description) {

    var preview_post = $('<div class="post">'+
                         '<a href="/users/'+user_id+'">'+
                            '<img src="'+user_pic+'" alt="'+user_name+'" />'+
                            '<span>'+user_name+'</span>'+
                         '</a>'+
                         '<h2>'+title+'</h2>'
                         '<p>'+description+'</p>'+
                        '</div>');
 
    return preview_post; 
} 

I will show you a viable solution which mixes Twig, a popular backend template engine that integrates directly with PHP, and Handlebars, a dynamic template engine, based on Mustache JS, that uses a Javascript compiler. You may very well be able to achieve the solution I’m going to describe using a different pair of template languages, and it is worth exploring further to find what may work best overall for your project, but the key to this trick is that both Twig and Handlebars employ the common curly braces notation for variables.

Create a Shared Template

Let’s start by creating the template for the post that will be shared by Twig and Handlebars. For this example, I decide to call it “user_post.html” and save it in my ‘templates/shared/posts/’ directory. Note the use of the curly braces to designate variables:

<div class="post">
    <a href="/users/{{user_id}}">
        <img src="{{user_pic}}" alt="{{user_name}}" />
        <span>{{user_name}}</span>
    </a>
    <h2>{{title}}</h2>
    <p>{{description}}</p>
</div>

Set Up the Main View with PHP

Next, you’ll want to have a PHP script that serves up a main Twig template for the HTML page where the list of user posts will appear. By referencing the Twig API docs, you can read specifically about how to set up a template manager that loads and renders a template from a common directory by name as I won’t be able to cover that in this post. Here is the gist of what the PHP might look like:

<?php

   require_once $_SERVER['DOCUMENT_ROOT'].'/app/Services/TemplateManager.php';

   //user posts -- example dummy data
   user_posts = array( array('user_id'=>1, 
                            'user_name'=>"Mark Johnson", 
                             'user_pic'=>'/path/to/image1.jpg',
                             'title'=>"iOS Developer",                          
                            'description'=>"Sample description text1"),
                      array('user_id'=>2, 
                             'user_name'=>"John Smith", 
                             'user_pic'=>'/path/to/image2.jpg',
                             'title'=>"Android Developer",
                             'description'=>"Sample description text2"),
                      array('user_id'=>3, 
                               'user_name'=>"Barry Lewis",
                              'user_pic'=>'/path/to/image3.jpg',
                              'title'=>"Full-stack Developer",
                              'description'=>"Sample description text3") );

   $templateMgr->render('/main_page.html', 
       array(
         'user_posts' => $user_posts, //pass an options array into the template
       )
   );

In this example, I have populated some dummy data for the posts into an array that gets passed into the Twig template. In a real application, you will probably want this data coming from an external source such as a database.

Use a Twig Loop To Include the Template Repeatedly

Now in the main Twig HTML, I use a “for each” statement to loop through the array of posts and “include” the “user_post.html” template for each one. The “with” clause allows me to designate an object containing values to be passed to the shared template.

<div class="list_container">
 
    {% for item in user_posts %}
        {% include '/shared/posts/user_post.html' with 
             {'user_id': item.user_id,
              'user_name': item.user_name, 
              'user_pic': item.user_pic,
              'title': item.title,
              'description': item.description} %}
    {% endfor %}
 
 </div>

Once you see that your list is rendering successfully on page-load, we can set up the form that allows a user to submit a post. To simplify things, we will assume that the user is already registered and has a name, ID, and picture stored in a session.

<form name="submitpost_form" id="submitpost_form" method="post">

   <label>Title</label><br/>
   <input name="title_text" id="title_text" type="text" value="" />

   <label>Summary</label><br/>
   <textarea name="description_text" id="description_text"></textarea>

   <input type="submit" name="submit" id="submit_btn" value="Submit" />
   <input type="button" name="preview" id="preview_btn" value="Preview" />
</form>

Set up the Handlebars Block

Now it’s time to implement Handlebars so that we can generate a preview post at runtime when the “preview” button is clicked. Handlebars allows real-time rendering of HTML markup by designating a special block of Javascript with a proprietary “type” (“text/x-handlebars-template”) and placing the relevant markup within it. By virtue of this designation, the markup will not be rendered to the DOM when the page loads. It will simply wait to be invoked by calling a “compile” function built into the Handlebars API.

So, after we’ve included the Handlebars plug-in in our main HTML, we can actually use Twig notation to include our shared post template right inside a script block! This is because our main HTML template is interpreted by Twig.

<script type="text/javascript" src="/js/plugins/handlebars-v4.0.2.js"></script>

<!-- handlebars template for inserting a preview post -->
<script type="text/x-handlebars-template" id="preview-post-template">​
     {% include '/shared/posts/user_post.html' with 
                  {'user_id': '{{user_id}}', 
                    'user_name': '{{user_name}}',
                    'user_pic': '{{user_pic}}',
                    'title': '{{title}}',
                    'description': '{{description}}'} %} 
</script>

One non-obvious thing to note here is that, because we are going be populating the preview post data into the template dynamically at runtime, the values in the braces need to be rendered out “raw” or “verbatim” so that Twig will ignore them and they will only be picked up by Handlebars. Otherwise, Twig will see the values as undefined and blank them out immediately before JavaScript has loaded. Aside from that, as long as you give your template a distinct ID — in this case I am calling it “preview-post-template” — everything that follows will work.

Compile the Template at Runtime with Handlebars

The next step is to write a simple function — which you may want to place in an external JS file or global object so it can be accessed from anywhere — that takes the ID of the template as a parameter and returns a compiled template. I am using jQuery for this, but you could just as easily use an equivalent Javascript method if you prefer.

// for Handlebars JS template compilation
function getTemplate(template_id) {
   //Get the HTML from the template in the script tag​
    var templateScript = $('#'+template_id).html().replace(/[\u200B]/g, '');
    return Handlebars.compile(templateScript); 
}

Now, on click of the preview button, I call this function and populate it with a “context” object containing the logged-in user’s data and the values in the form fields. And, finally, I append the rendered template to a preview container that can be placed anywhere on our main page.

$('.preview_btn').click(function(e) {
    e.preventDefault();
    var template = getTemplate('preview-post-template');

    var context = {'user_id': session.user_id, 
                   'user_name': session.user_name, 
                   'user_pic': session.user_pic,
                   'title': $('#title_text').val(),
                   'description': $('#description_text').val() };
 
    $('.preview_container').append(template(context));
});

Wrapping Up

That should do it! One thing to keep in mind when using this approach is that your dynamic template needs to be generalized enough to be shared by two different template languages, which means that you will not be able to add notations that are only recognized by Handlebars or only work for Twig. For instance, each language uses a different syntax for loops and conditional statements, so it is best to keep that kind of logic outside of the shared templates.

Going forward, you have a few options for how to handle submitting the actual form. If you are looking to submit it at runtime (without a page refresh) and would like to update the page with the new post afterwards, you can accomplish this by making an AJAX “POST” call that inserts the new post into your database and then either:

A) returns the fully-populated post template rendered by Twig, OR
B) returns JSON data that you can use to fill the context and invoke the Handlebars template again.

Feel free to experiment with this, and let me know if you come up with any insights or findings of your own.