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