Post Featured Image

Add and Use Custom Handlebars Helpers

The SCA templating engine is powered by Handlebars. We've talked before about its features: how it is meant to be simple and logicless — to compliment the JavaScript backbone provided by, er, Backbone and the other libraries and frameworks that we include.

Indeed, one of its greatest strengths is the fact that it is so simple. Rather than having to rely on something like Underscore, which can be cumbersome and mixes JavaScript and HTML, our template files are never more than the HTML that we want to include plus the little extra markup provided by Handlebars.

While Handlebars provides very simple things, such as simply populating variables with data, it also provides tools (minimal tools) that we can use to make the files truly into reusable templates. Helpers are used to create support for things like conditional statements and loops.

As Handlebars is powered by JavaScript, it's straightforward on how these helpers work. What's also straightforward is how to create custom helpers, that can do the specific things we want. This came up when we talked about adding custom translation text: the translate helper is one such example of this. However, we didn't go into details about how to add them, or what other ones we could. Let's do that today.

Why Have Custom Helpers at All?

In the case of translate function, we needed something that could run our custom translation script on-the-fly in our templates. While we could translate the values in the view before sending them to the template, this would be overkill and misses out on using a part of Handlebars that makes it so useful.

For example, if we want to generate a link to the login page, we don't need to pass the context any information about this link (other than, say, whether to show it all). The text of that link is simply none of its business. Indeed, making values available in the context of the view is really only required if they have been returned by the system or manipulated in some way before being exposed. Simple text strings can and should live in the template. Thus, we need a helper to provide translation functionality (and it's bad form to include JavaScript in template files).

We actually have a whole host of custom helpers included in SCA, along with the translate function in Modules > HandlebarsExtras > HandlebarsExtras.js. Some do 'little fixes' but another popular one is each — an example of us using this helper is the carousel on the homepage — we have, in Home > home.tpl, the {{#each carouselImages}} block helper that iterates all of the slides for the carousel. While Handlebars includes its own #each, we needed one that could iterate over Backbone collections. So we wrote one, and you can too.

Creating Your Own

If you look in HandlebarsExtras.js you'll see the structure of how to create a helper:

Handlebars.registerHelper('helperName', function(param1, param2, etc)
{
    // Your code
});

This is the basic structure, and from here you can write two kinds of custom helper: function and block.

Function helpers are the simplest and most closely resemble JavaScript functions. You write them to accept parameters (if you want), perform some sort of function or transformation, and then return the result. A good example of this is the translate helper, which takes a string and then runs the translate function on it.

Block helpers are the ones that start with a # in templates. They have opening and closing tags, which means that some function is performed on the contents of these tags. The most obvious one is the previously mentioned each, which iterates through an array and often produces HTML substituting in values for the variables.

Custom Function Helper

To begin, create the directory and file structure for a new module in your customizations folder, for example:

  • CustomHelpers
    • JavaScript
      • CustomHelpers.js
    • ns.package.json

Update the distro.json file to register the module and include it in the JavaScript for each of the applications.

The ns.package.json should be the standard one for including only JavaScript.

Run gulp deploy to get this code up to NetSuite.

Then, in CustomHelpers.js, put the following:

define(
  'CustomHelpers'
, [
    'Handlebars'
  ]
, function (
    Handlebars
  )
{
  'use strict'

  Handlebars.registerHelper('boogie', function(text)
  {
    return new Handlebars.SafeString(text);;
  });
});

All this is doing is creating a helper called boogie that takes a parameter and then returns it. In a template (for example: Home > home.tpl) add in the following code:

<p>{{boogie 'woogie'}}</p>

Now run gulp local to start your local server. Visit the page which runs the template you edited and you should see some boogie woogie!

Custom Block Helper Example

For our next example, let's make it a bit more interesting. What I want to do is create a block that shows something special to people called Stu, because I only want to boogie with Stu. What we'll need is something that checks the user's addresses and see if any of them start with "Stu" (an exact match isn't quite right as they could prefer "Stuart").

The first part of this is understanding the data we're going to be dealing with. The Address module deals with the address book, and Address.Details.View.js is what is used to create the cards that appear on the address book page. It's here where we'll get the user's name. You'll see in the getContext function that returns the addressee's name:

fullname = this.model.get('fullname')

Here, it's returning the full name of the user (which has been concatenated to include their last name). We can't do a simple equality check, we need something that'll check the start of the string. Luckily for us, ECMAScript 6 contains a method that checks whether a string starts with some text, so we don't have to write our own.

In CustomHelpers.js, put the following:

Handlebars.registerHelper('startsWith', function(prefix, text, options)
{
  if (text.startsWith(prefix))
  {
    return options.fn(this)
  }

  return options.inverse(this);
});

So here are some facts about this:

  • We've registered a helper called startsWith
  • It's passed three parameters: the prefix we're checking for, the text we want to check, and the options hash, which is an object containing the template's context
  • We use the startsWith() method to check the text we just passed in
  • If it's there, we run the fn() method, which returns the 'truthy' part of the block
  • If it's not, we run the inverse() method, which returns the 'falsy' part of the block

What we need to do now is edit a template and test this out. As we're only testing this change, and not putting it in permanently, you can edit the template directly to get a sense of how it works. (If you wish, you can set up a custom module with a template override and do it by the book.)

In address_details.tpl, below the address block, add the following:

{{#startsWith 'Stu' fullname}}
    <p>Hey Stu, we want to <a href="#">boogie with you</a>!</p>
{{else}}
    <p>You cannot boogie right now :(</p>
{{/startsWith}}

Next, if you don't have an address where the recipient's name starts with "Stu" then you need to add one. Now, when you go to your address book, you should see something like this:

The block takes the full name, runs some JavaScript on it, returns a true or false, and then processes which block to show in that situation. What I could do is then link this off to a page with a special category of products just for Stu, or maybe something that shows them a discount code. Super handy!

Additional Use: Debugging

There are other things you can do with this too. I found one example on Treehouse, which is to create a debug helper. In the previous example, we used this in our code and, whenever you use this, it can be confusing if you get an unexpected result. You may also want to reference a particular part of the context, for example some of the data, and not know how.

In your CustomHelpers.js file, add in:

Handlebars.registerHelper("debug", function(optionalValue) {
  console.log("Current Context");
  console.log("====================");
  console.log(this);

  if (optionalValue) {
    console.log("Value");
    console.log("====================");
    console.log(optionalValue);
  }
});

If I were to dump this into the same template, then it would print out a bunch of potentially useful stuff into the developer console:

In essence, this is very similar to putting a console.log() in the view that's rendering the template but with the added benefit of knowing exactly that the context being returned is the one the template sees (that is, rather than the one that you think is being sent to it).

Final Thoughts

Handlebars helpers are there to provide the utility needed to make the templating system work. Without the ability to run basic functions, such as logic checks or iterations, it just wouldn't be a worthwhile solution.

One of the great things about Handlebars is that it has tried to be 'barebones' as much as possible. While this may mean that it doesn't have all the features you may want or need, it is also flexible enough to add in those if you wish.

When you add in a helper be aware that you're effectively adding logic to a logicless templating system. You should evaluate whether it makes more sense to add the logic directly into your view instead, as a number of things can be performed there first.

In our example, one could quite simply translate the Handlebars helper into a function for the context, which returns true or false. Then, in the template, create a simple conditional statement that evaluates it. For example, in the getContext function you could put the following:

, startsWithStu: fullname.startsWith('Stu')

And then something like this in the template:

{{#if startsWithStu}}

Simple, right? Well, there's some important points here worth considering:

  • The custom helper is more generic and therefore has more utility. The value we added to the context is specific to this template, and only evaluates one pre-specified string. The helper we wrote performs a similar task but accepts a string as a parameter and can be reused throughout the site with no additional effort. This is more flexible.
  • Creating a custom helper does not require you to extend the view. While creating a helper requires you to have a custom helpers module, the view itself does not need to be modified.

You need to decide how to best fit in the script you want to add, whether it's in a JavaScript file or as a helper in the template. Blindly doing one or the other could be detrimental to your code quality, as well as cause confusion among your team.

In short, helpers are a good way to add in tools that help you handle data from the view better. They shouldn't be used to transform or manipulate that data, rather to read it and format or render HTML. For things that can be evaluated to produce a simple true or false, it may be better to do that in the view — but consider the impact of this, and whether that action can be generalized using a helper.

More Information