Advanced Barhandles

Extracting schemas from your Handlebar templates even if custom helpers are involved

Wilfred Springer
East Pole
Published in
5 min readSep 22, 2017

--

Barhandles is a proof of concept that aims to extract a document schema from your Handlebars documents. Really useful, if you happen to be in the business of creating document templates, and you want to validate if whatever you’re passing it is actually what it needs.

I already wrote about Barhandles before, but just as a reminder, this is how you use it:

… and then this is what you get:

The schema it produces is not really a standard schema language, but with a simple transformation you can turn it into JSON Schema or JOI, or whatever other flavour of schema language you like, which the aforementioned document explains how to do.

All good right?

Well not really. Barhandles works quite well with whatever Handlebars is offering as standard helpers. However, you will hardly ever use Handlebars without registering a bunch of helpers yourself. And unfortunately, each helper might have its own expectations on what to expect from the data getting past in, based on its configuration.

Let’s take {{#with}} as an example. That helper is built into Handlebars by default, and it's very useful if you have a block of content in which you're continuously referring to data nested need down in the data structure you're passing in.

With {{#with}}, a piece of Handlebars content like this:

… can be rewritten as:

… which is very useful.

However, if Barhandles would have treated {{#with}} as any other directive, then the schema it produced would have expected these properties to be present when passing in data:

  • owner.address
  • street
  • city
  • zipcode

… which is clearly not the template is expecting.

For that reason, Barhandles is treating {{#with}} a little different than other helpers. In case of a {{#with}} directive, it actually changes the context of any embedded references to whatever is getting passed as the first argument of {{#with}}. As a result, it correctly finds that what really needs to be passed in is:

  • owner.address
  • owner.address.street
  • owner.address.city
  • owner.address.zipcode

So, the {{#with}} helper is treated differently than most other helpers in Handlebars. However, your own helpers might have similar processing expectations. That's why Barhandles allows you to customize how you want your own helpers to be treated.

But how?

The secret to specifying how you want Barhandles to interpret your own helpers is by passing in some configuration options as a second argument.

If you have a helper for which you want to customize how Barhandles treats it, then you add a new entry to the options object with the name of the helper as a key, and an object with configuration settings as a value.

Now, if you have a {{#with}} alike helper called withAlike, then this is how you make Barhandles aware of its existence, and this is also how you make sure it's getting treated in a similar way.

Helper configuration options

In the previous example, I have shown one configuration option that you can tie to a directive, but there are more. Currently there are three different settings that influence how Barhandles treats a directive it encounters:

Changing the context

If you have a helper that changes the context of all references nested inside, then you use the contextParam to indicate which of the parameters of that helper is actually defining that context. It might be relevant only in {{#with}} alike situations, but if you ever run into that, now at least you know how make Barhandles aware of it.

Suppose you have a {{#propertyList {type} {object}}} helper that is generating some boilerplate HTML for a list of properties and then sets the context of whatever is inside to whatever you have as the second argument. In that case, you will have to configure Barhandles by passing in:

Setting arguments to be optional

By default, Barhandles will assume that if you’re including references to variables in your data structure, those references are required to be present in the data structure you pass in. However, that might not be a sensible default in some circumstances.

Suppose that you have a directive called join that joins together some variables in a human readable way:

If the variables are set to “Bob”, “Alice”, “Joe”, then the helper would render Bob, Alice and Joe. However, if the third name would not be set in the data passed in, then this specific helper would be okay with that and just render Bob and Alice.

If you would not have a way to define how Barhandles treats these situations, then it would assume a data structure that assumes all of these properties to be required. However, we actually expect none of these properties to be required.

In order to make Barhandles aware of that, you would pass in configuration like this:

This tells Barhandles to treat any references inside this directive as optional.

Transmogrify

The name already gives it away: this is the most tricky bit, and you probably will never have a reason to use it. I never had a reason to use it for my custom helpers, but it is used for a helper Handlebars registers by default, so you should be aware of it, and it might be helpful.

The prototypical example is {{#each}}. With that directive, you're looping over the elements of a collection. Without transfogrify option, Barhandles would look at a snippet like this:

… and assume what the template requires is an object like this:

… which is clearly not what the template is expecting in reality.

The transmogrify takes the path that is indicated by the parameters that sets the context (remember, you can tell Barhandles about that with the contextParam option), and rewrites that path into something that makes sense to the output of Barhandles.

In this case, where we are traversing an array, we want Barhandles to see that what is really required here is that children is an Array. We do that by passing in a transmogrifyoption, pointing to a function that rewrites the path to make sense to Barhandles:

Summary

Sometimes Barhandles is unable to understand how it should treat custom helpers you’re registering yourself. There is however an option to pass some configuration options to influence Barhandles’ behavior if it encounters these custom helper references. The configuration options are limited, and it would be really helpful to consider a more robust customization solution in the future, but that is left as an exercise to the reader.

Originally published at gist.github.com.

--

--

Wilfred Springer
East Pole

Double bass playing father of three, hacker and soul searcher