CodeBrief

Eight Ember.js Gotchas With Workarounds

Having used Ember.js for a few months now, I have compiled a list of gotchas that I have encountered. Many of these will change/be fixed as Ember matures, but until then I hope these will serve as pre-emptive time savers for other developers.

1. Computed Properties and .cacheable()

This one has actually been fixed in the lastest build of Ember, but I'm putting it here since it is still present in the latest 0.9.5 release. Consider the following object:

var object = Ember.Object.create({
  tags: function() {
    return [];
  }.property()
});

It turns out that if you try and bind to the tags property of the above object, you will encounter an infinite run loop. Any computed property which does not return a primitive value will suffer from this, the underlying cause being due to failing equivalence tests. This fix is to add a call to cacheable() to the end of the computed property:

var object = Ember.Object.create({
  tags: function() {
    return [];
  }.property().cacheable()
});

Even though the infinite loop issue has been fixed, making computed properties cacheable is generally a good idea and will be more the rule than the exception.

2. firstObject and lastObject

These properties of enumerables are not bindable. Although this was intentional for performance reasons, this is quite unexpected and I hope this to be fixed in the future. For now, you can create a custom computed property as a substitute:

var myController = Ember.ArrayController.create({
  ...
  firstItem: function() {
    return this.getPath('content.firstObject');
  }.property('content.@each').cacheable()
});

3. Combining Static and Dynamic CSS Class Names

It will often be the case that you want an element inside a handlebars template to have both a static css class name as well as class name that is the result of binding to a property. In order to do this in version 0.9.5, use the code below:

<div {{bindAttr class="myProperty:dynamicClass alwaysTrue:staticClass"}}></div>

This code will add the dynamicClass class to the div when myProperty is true. The alwaysTrue property is a property you define that always evaluates to true; this will ensure that the div always has the staticClass class.

In the latest build of ember, a shorthand has been created for this:

<div {{bindAttr class="myProperty:dynamicClass :staticClass"}}></div>

Specifying just :staticClass (without the alwaysTrue property) will have this same effect.

4. Bound Handlebars Helpers

As it currently stands, handlebars helpers inside of Ember are just normal helpers. They do not have any of the secret sauce which makes them automatically update when the properties they depend on change. In order to make helpers bound to properties, you must add the magic in yourself.

Fortunately, I have created a gist which packages this functionality up into a convenient syntax that is similar to computed properties:

To define a bound helper this way, do something like the following:

Ember.registerBoundHelper('currency', function(value, options) {
  if(!value) return ""
  return "$" + value.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
});

Now you can use this inside of any of your handlebars templates:

<span>{{currency myProperty}}</span>

The helper output will auto-magically update when the value of myProperty changes.

5. Each Blocks, Indices, and Metamorph

This one can be very inconvenient at times. Ember's {{#each}} helper does not provide access to an index property. This can make things such as styling cumbersome. The situation is also compounded by the fact that the DOM-binding library that Ember uses, Metamorph, also adds html metadata (in the form of script tags) to the dom that can break certain CSS selectors.

Consider the following handlebars template:

<ul>
{{#each myArray}}
  <li>{{this}}</li>
{{/#each}}
</ul>

This template loops over the enumerable myArray and outputs its contents into list item elements. One would intuitively expect to be able to use the :first-child CSS pseudo-selector to give the first element in the list a particular style, but this is not the case. Upon inspecting the DOM, it will be noticed that there is a <script> tag before the first element. In order to work around this, use the nth-of-type pseudo selector.

In some cases, however, using a CSS workaround will not be sufficient and having access to the item array indices will be required. Ultimately, I hope an eachWithIndex helper makes it into Ember's core, but for now the underlying data must be altered to contain the indices. One easy way of doing this is to create a computed property to this end:

  myArrayWithIndices: function() {
    return myArray.map(function(i, idx) {
      return {item: i, index: idx};
    });
  }.property('myArray.@each')

The above template could now be rewritten to include indices:

<ul>
{{#each myArrayWithIndices}}
  <li>{{this.index}}. {{this.item}}</li>
{{/#each}}
</ul>

6. Object Default Value Initialization

The way default values for properties work in Ember is somewhat different from many other languages/frameworks. This will be especially unintuitive for people coming from more traditional object oriented languages such as Java.

Consider the following snippet:

var Category = Ember.Object.extend({
  tags: []
});

var animals = Category.create();
var reptiles = Category.create();

animals.get('tags').pushObject('warm-blooded');

console.log(reptiles.get('tags')); // unexpectedly outputs ['warm-blooded']

In the above example both animals and reptiles share the same tags array. In Ember, object property initialization only runs once per class definition. To get around this, you must either move default value initialization into the init method or just re-define the entire array for each instance:

Fix #1:

var Category = Ember.Object.extend({
  init: function() {
    this._super();
    this.set('tags', []);
  }
});

var animals = Category.create();
var reptiles = Category.create();

animals.get('tags').pushObject('warm-blooded');

console.log(reptiles.get('tags')); // outputs []

Fix #2:

var Category = Ember.Object.extend({
});

var animals = Category.create({
  tags: ['warm-blooded']
});
var reptiles = Category.create({
  tags: []
});

console.log(reptiles.get('tags')); // outputs []

Everything I just said also applies to any non-primitive property, such as objects.

7. Metamorph Metadata

Ember plays nicely with other frameworks. For the most part. Sometimes Metamorph gets in the way. For instance, any framework which reliess on cloning DOM elements will probably have some issues (such as jQuery UI's draggable).

A workaround for this is to clone the element with all of the Metamorph metadata removed. Fortunately, once again I have a gist for this:

{% gist 2018290 %}

8. Run Loop Debugging With Chrome

This one is somewhat more obscure and particular only to Chrome. Ember has the concept of a run loop which batches bindings and does some other neat stuff. Generally speaking, you won't have to know much about it, except when you do.

When inside the run loop, Ember wraps everything in a try/finally statement (with no catch). In theory this should not affect anything, but Chrome has a particularly nasty bug where the console will swallow uncaught exceptions if they go through a finally (and will also not call window.onerror).

The easy workaround for this is to set Chrome to automatically break on uncaught exceptions, but worth mentioning since there is nothing worse than having to discover exceptions through side effects.

comments powered by Disqus