Javascript Handlebars Templates With Custom Helpers

Last time, I talked about Handlebars with Partials and before that it was Basic Handlebars Templates, so now I think it is time to talk about custom helper methods and what they allow us to do. If you have used Handlebars and have used the notation “#each” you have used a helper. Handlebars also lets us create our own Helper Methods, so we can make #DoMyCustomWork. I should also note that any Helper that uses the ‘#’ notation, needs to have the closing ‘/’ notation. For example, the built in “{{#if}}” needs to be closed with a “{{/if}}”.

I am going to build on my project from the last post in which I was using sport team information.

This time, I want to display the NHL teams sorted on the major segmentation, or conference, and the minor segmentation, or division. Basically, NHL teams are in a Division, and each Division belongs to a Conference and I want my list to reflect that.

Since I want to sort the teams based on Conference and Division, I feel like I should look at the most general split, or by conference. To do this I need something that will look at my data and only act on the teams that I want at the moment. It would be logical to think that one option would be to iterate through all the teams and use the built in #if helper. And if you thought that, you are wrong.
The #if only tests for True or “Falsey” values. Yes, “Falsey”. The #if helper checks if the value is “null”, “undefined”, and yes even “false”. A quick example: lets say I have data for a sports league, but the data on individual teams is empty, I can for example “{{#if teams}}” in a template, which will allow me to show the team data if is is there. Next I can put “{{else}}”, and put an error message saying “Team data not provided.”

Enough of that tangent. I want a custom helper that gives me all the teams of a certain NHL Conference. To do this I feel like I should make my helper as generic as possible because this time I want conference, but next time it might be divisions, or team color, or team location. So I want my helper to accept my collection, the property I want to search, and the value that I want to match on. With that criteria, this is what I came up with.

        Handlebars.registerHelper("where", function (collection, property, value, options) {
            var subset = [];

            for (var teamIndex in collection) {
                var team = collection[teamIndex];

                if (team[property] !== undefined && team[property].toLowerCase() === value.toLowerCase()) {
                    subset.push(team);
                }
            }

            return options.fn(subset);
        });

I put this snippet in a “registerHelpers()” function that lives in an object dedicated to Handlebars. But lets dive into the specifics a bit. What I am doing it telling Handlebars that I want to register a helper named “where,” which is a function that takes in 4 parameters: collection, property, value, and options. The first three I explained before. The last one, options, seems like an anomaly. In fact, options is a handlebars construct and that allows me to pass data from the JavaScript back to the template. Once in the function it’s just a basic JS function to iterate through my collection and compare the values, saving off the ones I want. After that has completed I give my subset back to the template by returning the subset through “options.fn().” Again, that is just how handlebars works.

To call this, I simply put :

{{#where <collection> <propertyToSearchOn> <valueToMatch> }}

in my template. The stuff in <> are placeholders for the actual values.
For my structure the actual line to call my helper is:

{{#where teams "conference" "west" }}

I should also point out that using #where, returns an object and shifts my context. That means using "{{#each teams}}" like in my previous post will no longer work. The object I have when I get to that point is not the same object as I had before. Have no fear, because #where returns just the object I am looking for in an array, I can just change the line of code to be "{{#each this}}." What I am saying there is "for each object in the collection" do whatever is in the each block.

So when I modify my template to use the #where helper

<script id="sports-template" type="text/x-handlebars-template">
    <div id="sport-list">
        {{#each leagues}}
        <div id="league-accordion" class="panel-group">
            <div class="league-header">
                <h1>{{{name}}} ({{acronym}})</h1>
            </div>

            <h3>Founded {{{founded}}}</h3>
            <div class="panel panel-default">
                {{#where teams "conference" "west" }}
                {{#each this}}
                <div class="conferenceDiv">
                    {{> teamTemplate}}
                </div>
                {{/each}}
                {{/where}}
            </div>
        </div>
        {{/each}}
    </div>
</script>

and run it, I get a list of the teams that have "West" listed as their conference.

The eagle eyed may have noticed that now I only have half my data. I am only showing teams in the West. How can I show teams for both Conferences? Well, I could simply duplicate my #where block and change the string to "east," but what is the fun in that, and I would be duplicating code. What I could do instead is look at my data then iterate the proper number of times.

I will do my best to make this more brief. I want to look at my data see how many Conferences I have in the NHL. Something like a Distinct call to a database. So I made a helper I call, "Distinct." It looks like this:

        Handlebars.registerHelper("distinct", function (listOfObjects, property, options) {
            var unique = {};
            var distinctList = [];

            for (var objectIndex in listOfObjects) {
                var object = listOfObjects[objectIndex];

                if (typeof (unique[object[property]]) == "undefined") {
                    distinctList.push(object[property]);
                }

                unique[object[property]] = 0;
            }

            return options.fn(distinctList);
        });

I call it like this:

{{#distinct teams "conference"}}

Then my full template turns into this:

<script id="sports-template" type="text/x-handlebars-template">
    <div id="sport-list">
        {{#each leagues}}
        <div id="league-accordion" class="panel-group">
            <div class="league-header">
                <h1>{{{name}}} ({{acronym}})</h1>
            </div>

            <h3>Founded {{{founded}}}</h3>

            {{#distinct teams "conference"}}
            {{#each this}}
            <div class="conferenceDiv">
                <h2>{{this}}</h2>
            </div>
            {{#where ../../teams "conference" this }}
            {{#each this}}
            <div class="conferenceDiv">
                {{> teamTemplate}}
            </div>
            {{/each}}
            {{/where}}

            {{/each}}
            {{/distinct}}
        </div>
        {{/each}}
    </div>
</script>

And now I get both the NHL conferences.

To get to the division level, I do a very similar pattern.
-Get the distinct divisions
-Grab the teams from those division
-Plop them in the DOM

If you want to see this, grab my project.

That explains helpers for Handlebars.

My project (again): Handlebars Sample

Leave a Reply

Your email address will not be published. Required fields are marked *