Paul Cowan

Nomadic cattle rustler and inventor of the electric lasso

Ember.js - Creating a Custom Handlebars if Helper

This is post is really a continuation of a post that I wrote some time ago about how to call a handlebars helper from another handlebars helper.

I came across a situation today that cried out for me to create my own version of the handlebars if helper. The if helper is one of the first handlebars helpers that you will experience and one you will use time and time again along with its inverse, the unless helper.

The if helper can be used in a hanldebars template like so:

1
2
3
4
5
{{#if isTrue}}
  {{!do stuff}}
{{else}}
  {{! don't do stuff}}
{{/if}}

The above code will test the property expression of the current context for a truthy value and if one is found it will execute the code between the if and the else or the closing if, if no alternative else was provided. If the current context in the above code was a controller called IndexController then it would check the isTrue value of that controller:

ind.js
1
2
3
4
5
App.IndexController = Ember.ObjectController.extend({
  isTrue: (function(){
      return true;
  }).property()
})

The if helper is also a live helper and handlebars creates an observer for whatever property expression you pass to it which means that any changes to the property will cause that part of the template to re-render. This is one of the things that first drew me to ember.

One of Handlebars strengths is that it is logicless, which means you cannot use things like the logical && operator to combine property expressions like this:

1
2
3
{{#if isTrue && alsoTrue}}
  {{!do stuff}}
{{/if}}

You could put together an ugly hack that creates multiple if calls like below but I really did not want to go down that path.

1
2
3
4
5
{{#if isTrue}}
  {{#if alsoTrue}}
    {{!do stuff}}
  {{/if}}
{{/if}}

My scenario concerned only displaying ember-data models that did not have their isNew, isDeleted or isLoading flags set.

The naieve and crude approach would have been to do this:

1
2
3
4
5
6
7
{{#unless isNew}}
  {{#unless isDeleted}}
    {{#unless isLoading}}
      {{!do stuff}}
    {{/unless}}
  {{/unless}}
{{/unless}}

Instead I created my own custom if helper that is really just a wrapper around the handlebars boundIf helper that adds a property to the context and then calls the real boundIf helper.

Below is how the newly christened ifIsLive helper that is called from the handlebars template:

1
2
3
{{#ifIsLive model}}
  {{!do stuff}}
{{/ifIsLive}}

All I am doing is passing in a property path to the model object from the context.

And here is the implementation:

customif.js
1
2
3
4
5
6
7
8
9
10
11
Ember.Handlebars.registerHelper('ifIsLive', function(property, options) {
  var model = this.get(property),
    _this = this;

  if (!this.hasOwnProperty('isLive')) {
    Ember.defineProperty(this, 'isLive', Ember.computed('isNew', 'isDeleted', 'isLoading', function() {
      return !model.get('isNew') && !model.get('isDeleted') && !model.get('isLoading');
    }));
  }
  return Ember.Handlebars.helpers.boundIf.call(this, "isLive", options);
});
  • On line 2, I retrieve the model from the context.
  • On line 5, I check to see if the custom property exists on the context.
  • If the property does not exist then I create the computed property using ember’s slant on the EMAScript 5 defineProperty which takes an array of dependant keys to create a computed property from.
  • Lastly, I call Ember.Handlebars.helpers.boundIf, using the JavaScript call method to maintain the context and I also pass in the newly created isLive property from which the framework will create an observer to watch for changes.

The boundIf does all the clever stuff and is really the hero of the piece but I think this is a useful technique to DRY up your code.

Comments