Hacking with Handlebars in Java and Clojure: Part I

Pranav Gajjewar
Jun 12 · 7 min read

What is Handlebars?

Handlebars is a templating engine. It means that given some template text and corresponding data, Handlebars generates a document with the data inserted at the positions in the template.

E.g.

Template:

<h1>Hello {{name}}!</h1>

Data:

{
"name": "John"
}

Result:

<h1>Hello John!</h1>

Handlebars originated as an extension of Mustache. It is developed in Javascript and most commonly used in Javascript runtime environments (Node.js or in browsers).

We are going to look at Handlebars.java which is an implementation of Handlebars in Java. After looking at how this library works, we will take a look at how we can extend this to support a more complex use case.

In the end, we will see how to use and extend this Java library in Clojure.

Handlebars.java

Handlebars Java follows the same syntax as the Javascript version but it is implemented in Java.

You can check the README for more details but in a nutshell, you can render a template file with code like this:

  1. Create a Handlebars object.

But how does the library load the contents of mytemplate.hbs file using just a name? We have not specified the full path of this file. So where should the library search for this file?

For this, the library has a concept of TemplateSource and TemplateLoader. We will look at it shortly in more detail. But in brief:

  • TemplateLoader contains logic on how to construct the full path using just filename.

By default, handlebars uses ClasspathTemplateLoader. This loader looks for the filename on the java classpath. And if the file exists, then it loads the contents of the file.

If you want to load files from a different path, you can use FileTemplateLoaderas shown below:

E.g. if the file is located at /home/templates/mytemplate.hbs.

In the apply function, we give the data that we want to pass to the template and the variables in the templates will be replaced by this data. This data is referred to as the context.

This is the basic way of using Handlebars java for rendering handlebars template files.

But let’s back up a bit, why does the library even need such concepts as TemplateLoader and TemplateSource? Why not keep it simple — take this template string, take this data and return the rendered result?

Partials

Partials are a way to reuse the template files and avoid repetition. They are normal Handlebars templates that may be called directly by other templates. The syntax for calling partials is as follows:

Data:

{
"username": "John"
"content": "Glad to see you here!"
}

Final output:

<body>
<h1>
Welcome John!
<h1>
Glad to see you here!
</body>

When we render theindex.hbs file. The contents inside the header.hbs are substituted in place of {{> header.hbs}}.

When handlebars starts parsing a template (index.hbs), it has no knowledge of any other files. But once it encounters the partial call {{> header.hbs}}, it needs the contents of header.hbs before proceeding.

How does the parser know where to get the contents of header.hbs?

In order that handlebars is able to resolve any filename that it encounters while parsing, it needs to know the full file path and how to load that file’s contents. And that’s exactly what TemplateLoaders and TemplateSources do!

TemplateLoader & TemplateSource

Let’s take a deeper look intoTemplateLoader and TemplateSource. These are two interfaces defined in the IO part of the library.

TemplateLoader:

This is an interface that allows us to add our own logic on how filenames are utilized by handlebars.

  1. resolve method: Takes filename as input and returns the full path. E.g Input: index.hbs and Output: /var/www/templates/index.hbs

TemplateSource:

  1. content method: Takes the full path of the file and loads the contents of the file.
    For example, if the file is located on disk, then using absolute path, the contents are loaded with File IO.

Having these two interfaces in the library provides flexibility in how you define the source of your template files. The library comes equipped with a wide variety of TemplateLoaders for a plethora of use-cases. You can find them here.

But what if you want to go beyond this? Well, you can simply, write your own TemplateLoaderand TemplateSource!

Template Storage Options

As discussed, TemplateSource and TemplateLoader provide us with a lot of flexibility in terms of how we are loading the template content. And the library has some very useful TemplateLoader implementations included in it.

But all the TemplateLoaderin the library are created with the assumption that the template files are located on the disk or available as a resource on the local machine.

But what if you want a more flexible storage option? Suppose you are building a service and you do not want the template files to be bundled with the service. This is a valid use case as this allows benefits like:

  • The templates can be accessed or generated dynamically.

Let’s take an example, and see exactly how to do this.

Template Files on Cloud Storage

To be able to store/update the template files independently of the service, we decide to upload these files to Amazon S3 storage (or any similar cloud storage).

So when we present Handlebars with a filename, it should get resolved to the file’s location on S3 and the contents should be accessed to render the resultant document.

The problem at hand is letting handlebars know how to resolve any requested filename to its URL and how to fetch the content.

So let us define a TemplateSource and TemplateLoader for template files stored in S3.

Usually, the files uploaded to a public S3 bucket are accessed using a URL:

https://bucket-name.s3.Region.amazonaws.com/key-name

Suppose for some template files, you will have a list of URLs:

https://bucket-name.s3.Region.amazonaws.com/index.hbs
https://bucket-name.s3.Region.amazonaws.com/header.hbs
https://bucket-name.s3.Region.amazonaws.com/footer.hbs

Now we need a TemplateLoader that would define how we can access these template files using a URL.

If we look at the above URLs, we can see that the files index.hbs, header.hbsand footer.hbs all have the same prefix i.e. https://bucket-name.s3.Region.amazonaws.com/.

So to resolve the full path of the file using a filename, we just need to prepend the base URL to the filename. With this logic, we can write the resolve method for our TemplateLoader.

Custom TemplateLoader

Let us create a new class called HTTPTemplateLoader as follows:

Note: We are extending the URLTemplateLoader instead of writing it from scratch. It is an abstract class already present in the handlebars IO that we can use to work with URL sources. So we will be extending it.

The advantage of extending this abstract class is that it already knows how to load the contents of a file from a resolved url path using URLTemplateSource.

When the parser comes across a partial call e.g. {{> header.hbs}} , it knows how to resolve this filename to its URL and how to download the file contents using that URL.

Handlebars in Clojure

Clojure has the magical ability to directly interact with any Java Code using interop. We will use that and implement the same examples discussed in Java using Clojure.

For using handlebars in Clojure, you can use sunng87/hbs library.

Using sunng87/hbs

Result:

Hello John!

We can extend Handlebars.java in Clojure by using proxy to create a class implementation like HTTPTemplateLoader:

  • We implement the resolve method which returns the absolute path using prefix + filename + suffix

The above function http-loader returns a proxy object of URLTemplateLoader with overridden implementations of resolve and getResource object. Functionally, it is equivalent to the HTTPTemplateLoader class we wrote in Java.

Now, we can initialize the Handlebars object with the proxy loader object as follows:

In this way, we can extend and use the Handlebars Java in Clojure to do server-side template rendering.

If you want to look at complete code for working with Handlebars in Clojure, you can refer to:

  • clj-handlebars: My GitHub repository including extended Clojure code.

In this post, we took a look at how to use Handlebars.java and extend it in Clojure using proxy. It gets the job done.

But there are other ways in which we can write custom TemplateLoader and TemplateSource:

  • Using reify to implement the required interfaces.

There are all kinds of hacking you can do with Java and Clojure. And it is also worth taking a look at the performance of all these different approaches.

Stay tuned for Part II. Thank you!

helpshift-engineering

Engineering blog for Helpshift