NetSuite Commerce’s templating engine is powered by Handlebars. One of the flexibilities it provides developers is helper functions, which can do value evaluation, and block and conditional statements. This article describes how you can write custom ones, to go beyond the default ones in Handlebars and the proprietary ones in commerce bundles.

There are many templating engines available, and some of our legacy features use Underscore for them, but the SuiteCommerce products use Handlebars for the frontend. One of the aims is that templating is ‘logicless’, in the sense that you don’t put conditionals and calculations in the templates, that isn’t always feasible. As such, it allows us to use a number of statements to make sure the values and HTML we want is published in the right places.

In order to do this, it has ‘helpers’ that are, essentially, JavaScript functions: you give it a name and a callback, and it processes your data as you want. There are a number of these that are built into Handlebars, which you can and should read in their documentation, and we, NetSuite, have included a number of proprietary ones that are required for many features to function. You can see them in the HandlebarsExtras module.

Why Use Custom Handlebars?

To give you an example, let’s think about the proprietary translate() helper. We wrote this to provide an easy way for multi-language sites to operate. We need to be able to provide translated text for multi-language sites, and it’s best if we can do that in the context where the text appears; that means the template.

As we maintain a dictionary of translations, we have written a function that takes the base translation (which is American English), which it uses like a key to look up the translation based on the user’s language. If no translation is found, the original string is returned.

The translation is handled by a utility function that we have written (so we can use it anywhere within our JavaScript files) but the helper hooks into it and provides the functionality directly in the template. The prime benefit of this is that when designers are building their themes, they don’t need to edit the JavaScript files to put in new translations.

Considerations

So, before adding your own helpers, consider whether helpers are the best way to accomplish your goal. The translate function is a good example, because we made it available in the JavaScript and templates, but if you’re writing a pure Handlebars helper, ask yourself whether it should be a helper. One of the primary objectives is keeping the templates as free of logic as possible.

Another thing to keep in mind is whether you need the transformation to happen in the template: this can give benefits to theme designers, who might operate separately to the core code developers. By creating a helper, you’re making the function you’ll write available globally on a templates level. If it’s only going to be used in one place, you might think about, for example, simply modifying that view’s context object so that the transformation occurs there instead.

Creating a Simple Custom Helper

There are a number of complexities to be aware of when using your own helpers, but we will start with the basics.

The specific mechanism is the registerHelper() method on the Handlebars module. It operates like this:

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

Let’s go through this step-by-step

  1. In your extension or SCA source, create a new module folder and file, called something like CustomHandlebarsHelpers
  2. Add Handlebars as a dependency
  3. Use Handlebars.registerHelper() to define your new helper
    1. Give it a unique name
    2. Define the parameters (ie what values you expect to pass to it)
    3. In the callback, perform the transformation / write your code
    4. Return the value
  4. Add an ns.package.json file for it in your module’s directory, pointing to the JavaScript
  5. Update your distro.json file to register the module and its JavaScript

So, for example, your code might look like this:

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

    Handlebars.registerHelper('isTheBest', function (name) {
        return name + ' is the best!'
    });
});

If you’re using TypeScript, it’ll look like this:

/// <amd-module name="CustomHandlebarsHelpers"/>

import Handlebars = require('../../../Commons/Utilities/JavaScript/Handlebars');

Handlebars.registerHelper('isTheBest', function (name) {
    return name + ' is the best!'
});

Note that the path to Handlebars depends on where you’ve put your customization.

An important thing to keep in mind here is that your custom module does not necessarily need to return an object that has a mountToApp property for this. We are calling the Handlebars the module directly, and using a built-in method to modify the Handlebars object.

Testing Your Helper

The best way to test your helper is in a template, but that process can be cumbersome. For simple helpers, you can test them in your browser’s developer console.

Once your local site is up and running, visit it and open the developer console. You can access it by doing something like this:

var Handlebars = require('Handlebars');
Handlebars.helpers.isTheBest('Steve');
> "Steve is the best!"

If your helper is more complicated as is used to iterate over objects, collections, models, etc, then you probably won’t be able to test it this way.

Creating a Block Helper

Custom block helpers let you define your own iterators and conditionals. For this example, we’re going to add a custom helper to see if a passed string starts with a particular set of character.

ES6 has a native startsWith() function for strings, but we use ES5 so we will use a proprietary implementation of it.

In CustomHandlebarsHelpers, put:

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

    return options.inverse(this);
});

This is a custom if/else block. Here’s what’s going on:

  1. We registered the startsWith helper
  2. It’s passed three parameters:
    1. The prefix characters we want to check the string for
    2. The text string we want to check
    3. A hash of options, which Handlebars will handle for us, which determines which output to show in the template
  3. When invoked, we use indexOf to see if the starting point of the prefix is at the start of the main string
    • If it’s there, we return the template code for the truthy part of the condition
    • If it’s not, we return the template code for the falsy part of the condition

Given the complexity of this helper, it’s not possible to test it in your browser console, so you’ll need to update a template. You will need to deploy the code, and, if it’s an extension, you’ll need to activate it. If it’s an extension, you’ll also need to fetch the activated extension to your local machine too.

Once you’ve done that, you can test it out like this:

{{#startsWith 'Ste' 'Steve'}}
    <p>Steve? That's a great name!</p>
{{else}}
    <p>Well, that name is pretty good, but it's not Steve</p>
{{/startsWith}}

(If you edit a template that has access to the current user’s profile data, then you could replace the 'Steve' parameter with something like model.fullname.)

As mentioned previously, they have a lot of documentation on this area, so I would recommend reading more about block helpers.

Additional Use: Debugging

You can use helpers to aid you in debugging by printing the contents of objects to the developer console or the page.

Newer versions of SCA and SuiteCommerce already have a helper built in, which is simply called log. To use it, you can use it like this:

<!-- Log the view's context object
Note that `this` is contextual, so it will change value if, for example, you use it in an iterator or block -->
{{log this}}

<!-- Log the model -->
{{log model}}

Also, modern versions of the templates list the properties the developers have sent to the templates, so you can look for their names at the bottom of the template.