Balint Erdi

Observable Path Patterns in Ember

Property paths are the heart and soul of Ember.js apps. You use them in templates and you define dependencies for computed properties and observers through property paths.

In this post, I concentrate on this latter and show you various ways of setting up these dependencies through a practical example. There is a pretty good section in the guides about one path pattern. Here, I intend to cover more (all?) of them.

Badges are back

I am going to build on the badges “micro-app” that I had started to develop in my previous post about getters in setters.

There are two minimal model classes, User and Badge:

1
2
3
4
5
6
7
8
9
App.User  = Ember.Object.extend({
  name: ''
});

App.Badge = Ember.Object.extend({
  name: '',
  score: 0,
  unlocked: false
});

We also create a couple of instances to have some data to show and work with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var cory = App.User.create({ name: "Cory Filibuster" });

var rook = App.Badge.create({
  name: "R00k",
  score: 1,
  unlocked: true
});

var taciturn = App.Badge.create({
  name: "Taciturn",
  score: 10
});

var talkative = App.Badge.create({
  name: "Talkative",
  score: 100
});

var hemingway = App.Badge.create({
  name: "Hemingway",
  score: 1000
});

Our application looks like this initally:

Initital state

Simple property chain

The simplest, non-aggregate property path is just a series of names, connected by dots. This designates a property that you can arrive at by walking the path, segment by segment, where each of them gets you to another object until you finally reach the property.

(If a path is very long, you should probably think about the dependencies between your objects and the structure of your code.)

You see that the profile panel has the user’s first name as its header. The property that gets displayed there can be defined by such a path:

1
2
3
4
5
6
App.IndexController = Ember.ArrayController.extend({
  (...)
  firstName: function() {
    return this.get('user.name').split(/\s+/)[0];
  }.property('user.name'),
});

This sets up a computed property (CP) that will be recomputed whenever user.name changes. The arguments to the property call are called the dependent keys of the CP and you can pass as many as you would like (although, thanks to the various property path patterns, you will rarely need a lot).

Now, whenever the name property of the user property on the controller changes, firstName is recomputed and this change gets propagated to all the instances where firstName is used (e.g in the header of the panel).

Above that, the user.name key also triggers a change if the user object itself changes. To see that, we turn to the thing you should only ever use for demo purposes, the __container__ object:

1
2
var maggie = App.User.create({ name: "Maggie Raindance" });
App.__container__.lookup('controller:index').set('user', maggie);

You can see the name change in the header right away:

User name changes

Aggregate property paths

On other occasions, a CP should depend on an array of items. Whenever something gets added to or removed from the array, the property needs to be updated.

One example of that is the number of badges in the profile panel:

1
2
3
4
5
6
App.IndexController = Ember.ArrayController.extend({
  (...)
  badgeCount: function() {
    return this.get('model').length;
  }.property('model.[]'),
});

The model here is the array of badges so when we add another one through the New badge panel, badgeCount gets its new value:

Badge count gets updated

What I said about the user.name path triggering an update when the user changes also holds true here. If the array of badges was swapped out for another array, it would trigger the recalculation of badgeCount.

Aggregate property path with a specified property

There are cases where the value of the CP becomes stale also when the items in the dependent array stay the same, but a certain property of one of them changes. Ember has a way to express this very succintly.

The example is the “Total score” in the profile panel:

1
2
3
4
5
6
7
App.IndexController = Ember.ArrayController.extend({
  (...)
  totalScore: function() {
    var sum = function(s1, s2) { return s1 + s2; };
    return this.get('model').getEach('score').reduce(sum);
  }.property('model.@each.score'),
});

This is the most inclusive of the patterns we have seen so far. It prompts an update if the model changes, if any item is added or removed and also if the score of any item changes. If we type this at the console:

1
App.__container__.lookup('controller:index').set('model.lastObject.score', 200);

, then the total score changes accordingly, even though no item was inserted or deleted:

Total score

Brace yourself

To present the next pattern, let’s assume that not all badge scores need to be tallied to get the total but only the unlocked ones (which makes total sense). So the dependent keys for totalScore needs to account for that. That’s pretty easy:

1
2
3
4
5
6
7
App.IndexController = Ember.ArrayController.extend({
  (...)
  totalScore: function() {
    var sum = function(s1, s2) { return s1 + s2; };
    return this.get('model').filterBy('unlocked').getEach('score').reduce(sum);
  }.property('model.@each.score', 'model.@each.unlocked'),
});

When the second badge is unlocked, the score jumps from 1 to 11 (and the number of badges from 1 to 2), so the dependent keys work fine:

1
App.__container__.lookup('controller:index').get('model').objectAt(1).set('unlocked', true);

Unlocked property change triggers update

Starting with Ember 1.4.0, though, there is a more concise way to define the same, called “property brace expansion”. It works very similar to argument expansion in the shell:

1
2
3
4
5
6
7
App.IndexController = Ember.ArrayController.extend({
  (...)
  totalScore: function() {
    var sum = function(s1, s2) { return s1 + s2; };
    return this.get('model').filterBy('unlocked').getEach('score').reduce(sum);
  }.property('model.@each.{score,unlocked}'),
});

This establishes that totalScore should be recomputed if either the score or unlocked properties of any item in the model array changes.

An important restriction of property brace expansion is that the expansion part can only be placed at the end of the path, so e.g property('{foo,bar}.baz') will not have the desired effect.

Computed property macros are the bee’s knees

Computed property macros have several advantages. They are very expressive, very performant and perhaps most importantly more robust than typing out the property path patterns by hand where a typo can cause a considerable amount of head-scratching.

They are also a joy to work with and compose. In fact, all the CP definitions above can be re-defined by using only macros:

1
2
3
4
5
6
7
App.IndexController = Ember.ArrayController.extend({
  (...)
  badgeCount: Ember.computed.alias('unlockedBadges.length'),
  unlockedBadges: Ember.computed.filterBy('model', 'unlocked'),
  unlockedScores: Ember.computed.mapBy('unlockedBadges', 'score'),
  totalScore: Ember.computed.sum('unlockedScores'),
});

They have one big disadvantage, though. It is very hard to use them in a blog post to explain property path patterns.

(The code presented here can be found as a gist on Github)

ps. Yes, that Maggie Raindance.)

Ember.js Getters and Setters

To make all the magic fairy-dust sprinkling efficient, like auto-updating templates and computed properties, Ember uses getters and setters instead of accessing properties directly the javascript (and dare I say, the Angular) way. At its most simplest form, it is object.get(property) and object.set(property).

Howevers, it would not be Ember if we were not provided methods on top of these primitives to make our hard lives as web developers simpler. In the following post, I am going to show (some of) these methods through an example, so let’s go.

Badges, badges, I want more badges

I am certainly in favor of having badges in every application. Discourse is actively discoursing their badge system so I quickly sketched out something to get ahead of them.

Let me just paste the code here and then use it to explain what the getter and setter methods are good for.

You can quickly scroll through the below templates. They are mostly there so that you get the whole picture. The stuff relevant to this discussion is found in the javascript code.

<script type="text/x-handlebars">
  <div class="container">
    <div class="panel panel-primary">
      <div class="panel-heading">
        <h3 class="panel-title">Your badges, sir/ma'am.</h3>
      </div>
      <div class="panel-body">
        {{outlet}}
      </div>
    </div>
  </div>
 </script>
<script type="text/x-handlebars" data-template-name="index">
  <ul class="list-group">
    {{#each badge in arrangedContent}}
      <li {{bind-attr class=":list-group-item badge.unlocked::locked"}}>
        {{badge.name}}
        <span class="badge">{{badge.score}}</span>
      </li>
    {{/each}}
    <li class="list-group-item">
      <em>Total:</em>
      <span class="pull-right">{{totalScore}}</span>
    </li>
  </ul>
  <div id="new-badge" class="row">
    <span class="col-xs-6">
      {{input class="input" type="text" placeholder="Badge name" value=name}}
    </span>
    <span class="col-xs-4">
      {{input class="small-input" type="text" placeholder="Score" value=score action="addBadge"}}
    </span>
    <span class="col-xs-2">
      <button class="btn btn-primary btn-sm pull-right" type="button"
          {{action "addBadge"}}>
          Add
      </button>
    </span>
  </div>
  <div id="unlock-all" class="row">
    <div class="col-xs-12">
      <button class="btn btn-danger btn-md" type="button"
        {{action "unlockAll"}}>
        Unlock all
      </button>
    </div>
  </div>
</script>

First, the badge class is defined and some badges created so that we have something to work with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
App = Ember.Application.create();

App.Badge = Ember.Object.extend({
  name: '',
  score: 0,
  unlocked: false
});

var rook = App.Badge.create({
  name: "R00k",
  score: 1,
  unlocked: true
});

var talkative = App.Badge.create({
  name: "Talkative",
  score: 10
});

var hemingway = App.Badge.create({
  name: "Hemingway",
  score: 1000
});

App.Router.map(function() {
});

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return [rook, talkative, hemingway];
  }
});

getProperties and setProperties

The first couple of methods come handy when working with a single object but multiple properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
App.IndexController = Ember.ArrayController.extend({
  sortProperties: ['score'],
  sortAscending: true,

  (...)

  actions: {
    addBadge: function() {
      var newBadgeProperties = this.getProperties(['name', 'score']);
      newBadgeProperties.score = parseInt(newBadgeProperties.score, 10);

      var newBadge = App.Badge.create(newBadgeProperties);
      this.get('model').pushObject(newBadge);
      this.setProperties({ name: '', score: '' });
    },

    (...)
  }
});

On line 9, we want to create a new object with the values provided in the input boxes (addBadge is the action that gets triggered when the Add button is clicked, check the template). getProperties will create a javascript object creating key-value pairs for the passed properties. So the above might e.g yield { name: "Silent Bob", score: "2" }. That gets directly passed in to create a new badge object.

On line 14, we use the mutating pair of getProperties to reset the input fields. Pretty straightforward.

getEach and setEach

Ember has us covered when we are working with an array and want to get (or set) the same property of each item.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
App.IndexController = Ember.ArrayController.extend({
  sortProperties: ['score'],
  sortAscending: true,

  totalScore: function() {
    var sum = function(s1, s2) { return s1 + s2; };
    return this.get('model').getEach('score').reduce(sum);
  }.property('model.@each.score'),

  actions: {
    (...)

    unlockAll: function() {
      this.get('model').setEach('unlocked', true);
    }
  }
});

When the “Unlock all” button launches the unlockAll action, it calls setEach on the badges (line 14), making all of them unlocked (you can verify this in the demo by seeing the color of the badge names turn darker - their css class has been updated). Another advange of setEach is that it guards against calling set on null or undefined values.

You might know the reader counterpart, getEach as mapBy. It goes through the array and makes another array by getting the passed property on each item. So in the above example (line 7), we first collect the score for each badge and then sum them up by way of reduction. (A shiny example of a non-distributed map-reduce :) ).

A macro can make nice code nicer

I have used a reduce computed macro before to set up sorting. I have gotten the hang of it and realized I could use another couple of them to make the above code simpler (and more performant):

1
2
3
4
5
6
7
8
9
App.IndexController = Ember.ArrayController.extend({
  sortProperties: ['score'],
  sortAscending: true,

  scores: Ember.computed.mapBy('model', 'score'),
  totalScore: Ember.computed.sum('scores'),

  (...)
});

The problem is that the subject under discussion, getEach is gone now, so pretend you did not see this.

Finally, here is the jsbin, should you decide to play around with it:

Getter and Setters

I hope some of that sticks and you’ll write less “bare” gets and sets.

Sorting Arrays in Ember.js by Various Criteria

I concluded the previous post on route refactoring by saying that the fact that the artists.songs route now has the songs as its model will pay dividends in the future.

I will show one thing that becomes really easy this way and also one less-known Ember feature that builds on top of that.

Showing list items in a certain order

Even with our superb routing, songs for an artist appear in random order, differently at each load of the application. This is confusing and it is now easy to remedy.

The controller backing the artist.songs template is now an ArrayController so establishing a sort order is a piece of cake:

1
2
3
4
5
6
App.ArtistSongsController = Ember.ArrayController.extend({
  (...)
  sortAscending: false,
  sortProperties: ['rating', 'title'],
  (...)
});

What I say with these sorting-related definitions is that I want songs to be shown in descending order of first their ratings and then -should the rating be the same- by title. That’s all there is to it, those tunes are now aptly sorted:

Ascending ordering

Sorting by multiple properties, in different directions

There is a slight deficiency in the above sorting API. It cannot sort multiple properties if the sorting is to be done ascending for some properties and descending for others. The sortAscending flag is “global” for the whole sortProperties array.

Taking the above example, it is a perfectly valid request to have songs first ordered in descending order of their rating and then in ascending order of their title. But how can we do that?

Reduce computed macros

The Ember.computed.sort macro in the reduce_computed_macros package provides a clean way to do exactly that:

1
2
3
4
5
6
App.ArtistSongsController = Ember.ArrayController.extend({
  (...)
  sortProperties: ['rating:desc', 'title:asc'],
  sortedSongs: Ember.computed.sort('model', 'sortProperties'),
  (...)
});

The API is so explicit it does not need any explanation. In order for the songs to appear in the specified order, we now need to iterate through sortedSongs so the #each in the template becomes #each sortedSongs.

Let’s take a look at how it has changed the order of the songs:

Perfect ordering

Perfect, I would say that is the ordering that makes most sense.

Wait, it gets better still

When first playing with Ember.computed.sort, my initial try was to set up sortedSongs like that:

1
  sortedSongs: Ember.computed.sort('model', ['rating:desc', 'title:asc']),

That, however, throws an error. Taking a look at the code and the tests, I realized that the second argument needs to be a property on the same object the sorting is defined on. This seems “over-engineered” at first but in fact opens up further possibilities.

Under the hood, Ember.computed.sort sets up an observer not only to the array being sorted but also to the sort properties. That way, when the sorting criteria changes, the list is reordered and rerendered automatically. This makes it possible for the criteria to be easily modified via user action.

To give you an example, I’ll make a button set the rating to happen only by title, in ascending alphabetical order:

<script type="text/x-handlebars" data-template-name="artist/songs">
  {{#if canCreateSong}}
    <div class="list-group-item">
      (...)
      <button class="btn btn-link pull-right" {{action "sortBy" "title:asc"}}>Sort by title</button>
    </div>
  {{/if}}
  (...)
</script>

The sortBy action handler sets the passed string as the value for the sortProperties which will rearrange the list:

1
2
3
4
5
6
7
8
9
10
11
12
App.ArtistSongsController = Ember.ArrayController.extend({
  (...)
  sortProperties: ['rating:desc', 'title:asc'],
  sortedSongs: Ember.computed.sort('model', 'sortProperties'),

  actions: {
    sortBy: function(sortProperties) {
      this.set('sortProperties', [sortProperties]);
    },
    (...)
  }
});

Indeed when the link is clicked, the songs become ordered by title only:

Ordering by only title

Declarative sorting

Let me finish by pointing out that this arrangment makes it possible to have several actions that set (or modify) the ordering criteria, without having to write a single line of code to implement the sorting itself.

You gotta love this.

A Common Resource Route Pattern in Ember.js

Intro

A very common pattern in web applications, be them server- or client side, is resource URLs. We might have a list of users that we want to show at /users and then different pages related to the user which is encoded in the URL. These might be e.g /users/dave-hopkins/activity and /users/dave-hopkins/followers.

The pattern is a top-level URL to list all the resource instances, and then separate pages to display pieces of information regarding specific resource instances.

Artists and songs

That’s exactly what I did for the the Rock & Roll application, where the routes were defined as such:

1
2
3
4
5
App.Router.map(function() {
  this.resource('artists', function() {
    this.route('songs', { path: ':slug' });
  });
});

The simplest thing that works. However, the above is not ideal especially when more pages (or views, if you will) are added below the artists resource route. That’s because the singular artist instance is encoded in the songs route, by having its identifier (in this case, slug) in the path of that route.

Imagine we need to add additional info about each band. Just blindly extending the above URL scheme, this would become:

1
2
3
4
5
6
App.Router.map(function() {
  this.resource('artists', function() {
    this.route('songs', { path: ':slug/songs' });
    this.route('info',  { path: ':slug/info' });
  });
});

The cracks start to show. The artist for both the artists.songs and the artists.info routes would have to be fetched in both routes, with identical code. Nested routes -and how it lends itself to a nested UI- is truly a masterpiece, a shining emerarld on Ember’s crown. It would be a pity not to take advantage of it.

DRY up those routes

So we established that the problem is having the artist “encoded” in all routes below the top-level artists resource. The solution is consequently pretty straightforward -this always seems to be the case in retrospective-, let’s extract the path segment that represents the artist:

1
2
3
4
5
6
7
App.Router.map(function() {
  this.resource('artists', function() {
    this.resource('artist', { path: ':slug' }, function() {
      this.route('songs');
    });
  });
});

With the introduction of the artist resource, the duplication is gone, but we are not done yet. First, we have to define the route and set up its model hook. Second, since the “routing table” has changed, we’ll have to adjust route names and code that uses them. Since the naming conventions in Ember have the route names as their basis, we’ll probably have to change code in several places.

Route changes

Resource routes reset the routing namespace, so the route that corresponds to the artist route name in the table is App.ArtistRoute:

1
2
3
4
5
6
7
8
9
10
11
App.ArtistRoute = Ember.Route.extend({
  model: function(params) {
    return Ember.RSVP.Promise(function(resolve, reject) {
      App.Adapter.ajax('/artists/' + params.slug).then(function(data) {
        resolve(App.Artist.createRecord(data));
      }, function(error) {
        reject(error);
      });
    });
  }
});

That is exactly what we had for App.ArtistsSongsRoute in the previous version, which makes sense. The artist is now fetched one route level higher.

For simple, non-resource routes, the name of the route is the name of the resource route above (if it exists) plus the name of the route itself. In this case, the route name is artist.songs which gets resolved as App.ArtistSongsRoute):

1
2
3
4
5
6
7
8
9
10
11
App.ArtistSongsRoute = Ember.Route.extend({
  model: function(params) {
    return this.modelFor('artist').get('songs');
  },

  setupController: function(controller, model) {
    this._super(controller, model);
    controller.set('artist', this.modelFor('artist'));
  },
  (...)
});

The first interesting thing is modelFor. It gets the model for another, already resolved route. In Ember route models are resolved stepping down from the top-level application route. That means that at this point we can be certain that the artist route already has its model, the artist instance resolved.

The model of this route is simply the songs belonging to that artist.

The other interesting bit is setupController. We have already come across this hook before; it is the place to do additional setup -above fetching the model and deciding which template to render- for the controller. Since we’ll want to display artist-related data in the template, we store it in an artist property and we make sure to call _super, the implementation of this hook in Ember.Route, that sets the controller’s model property to the model argument in this method.

Templates & controllers

The mechanical part of the routing update is to replace all occurrences of the artists.songs route name to artist.songs.

What deserves more attention is that the controller for artist.songs now has the songs of the artist as its model, not the artist itself. That means that we should adjust the controller type it extends:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
App.ArtistSongsController = Ember.ArrayController.extend({
  artist: null,

  newSongPlaceholder: function() {
    return 'New ' + this.get('artist.name') + ' song';
  }.property('artist.name'),

  songCreationStarted: false,
  canCreateSong: function() {
    return this.get('songCreationStarted') || this.get('length');
  }.property('songCreationStarted', 'length'),

  (...)
});

All changes are made necessary by the model change. Properties of the artist now need to be prefixed by artist (e.g name => artist.name) while properties of the songs no longer need to have the songs prefix since it is the model (e.g songs.length => length).

This also holds true of the template. To give an example, rendering the stars for each song can becomes more concise:

<script type="text/x-handlebars" data-template-name="artist/songs">
  (...)
  {{#each}}
    <div class="list-group-item">
      {{title}}
      {{star-rating item=this rating=rating maxRating=5 setAction="setRating"}}
    </div>
  {{else}}
  (...)
</script>

The #each helper, without parameters, loops through the items in the model of the template, in our case, the songs, which is exactly what we want.

That wraps up our route sanitizaion. In the next post, we will take advantage of the benefit that the songs route now has the artist’s songs as its model.

Readers’ Letters: Making an Ember.js Component Even Better

Last time I showed a way to make the star-rating component more reusable. The solution employed a useful, low-level method, Ember.addObserver and its destructive sibling, Ember.removeObserver.

A couple of my readers offered alternative solutions that, I think, make the code simpler without harming reusability of the component.

This post is going to be sort of a “Readers’ Letters”, showing these solutions and explaining how they are better than my original take.

Ember.defineProperty

David Chen chimed in in the comments suggesting using Ember.defineProperty instead of setting up and tearing down an observer:

1
2
3
4
5
6
7
8
9
10
11
App.StarRatingComponent = Ember.Component.extend({
  classNames: ['rating-panel'],

  numStars:  Ember.computed.alias('maxRating'),

  defineFullStars: function() {
    Ember.defineProperty(this, 'fullStars', Ember.computed.alias('item.' + this.get('ratingProperty')));
  }.on('init'),
  (...)

});

Ember.defineProperty makes a fullStars property on the component which is an alias of item.rating (or item.score). We can concatanate ‘item.’ with that property name in the body of defineFullStars, something I could not get around earlier.

Finally, the on function, an extension to the Function prototype sets up a listener and gets called when the component’s init method is executed.

It is better, because there is a lot less code, it is more comprehensible and there is no need for a second step, tearing down the observer.

Passing in the value rating directly

Ricardo Mendes takes my approach one step further and shows that it is unnecessary to pass in the name of the ratingProperty.

Passing in the value of the property directly takes separation of concerns to the next level. The component does not need to know about the name of the rating property, all it needs to know is its value:

<script type="text/x-handlebars" data-template-name="artists/songs">
  (...)
  {{#each songs}}
    <div class="list-group-item">
      {{title}}
      {{star-rating item=this rating=rating maxRating=5 setAction="setRating"}}
    </div>
  (...)
  {{#each}}
</script>

What changed is that instead of ratingProperty=”rating” (which could be ratingProperty=”score”), the value of the rating itself is passed in. Note that there are no quotes around rating which establishes a binding.

The definition of the fullStars property now could not be simpler and more expressive:

1
2
3
4
5
6
7
App.StarRatingComponent = Ember.Component.extend({
  classNames: ['rating-panel'],

  numStars:  Ember.computed.alias('maxRating'),
  fullStars: Ember.computed.alias('rating'),
  (...)
});

Since the component does not know about the rating property, it can’t set the item’s rating which is a good thing since it’s not its responsiblity. It just sends an action to its context with the appropriate parameters:

1
2
3
4
5
6
7
8
9
10
11
12
App.StarRatingComponent = Ember.Component.extend({
  (...)
  actions: {
    setRating: function() {
      var newRating = parseInt($(event.target).attr('data-rating'), 10);
      this.sendAction('setAction', {
        item: this.get('item'),
        rating: newRating
      });
    }
  }
});

This action is then handled by the controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
App.ArtistsSongsRoute = Ember.Route.extend({
  (...)
  actions: {
    setRating: function(params) {
      var song = params.item,
          rating = params.rating;

      song.set('rating', rating);
      App.Adapter.ajax('/songs/' + song.get('id'), {
        type: 'PUT',
        context: this,
        data: { rating: rating }
      })
      (...)
    }
  }
});

Clear separation of concerns, less and more expressive code.

Replacing data-rating

A further simplification comes from Tom de Smet.

He rightfully pointed out that there is no need to get the rating that was clicked on via a data attribute. It is already known at template rendering time and can thus be passed to the action helper.

So this:

<script type="text/x-handlebars" data-template-name="components/star-rating">
  {{#each stars}}
    <span {{bind-attr data-rating=rating}}
      {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}}
      {{action "setRating"}}>
    </span>
  {{/each}}
</script>

becomes this:

<script type="text/x-handlebars" data-template-name="components/star-rating">
  {{#each stars}}
    <span
      {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}}
      {{action "setRating" rating}}>
    </span>
  {{/each}}
</script>

And then setRating simply receives the new rating as an argument:

1
2
3
4
5
6
7
8
9
10
11
App.StarRatingComponent = Ember.Component.extend({
  (...)
  actions: {
    setRating: function(newRating) {
      this.sendAction('setAction', {
        item: this.get('item'),
        rating: newRating
      });
    }
  }
});

Instead of adding an extra data-binding property, we rely on the action helper and we do not need the additional fetching (and parsing) of the property.

Give credit where credit is due

This week’s post was made possible by David, Ricardo and Tom. Their insights made the star-rating component impeccable for which they deserve a huge “thank you!” from me.

Making an Ember.js Component More Reusable

Intro

We saw how to turn the star-rating view into a component to make it more reusable, and less reliant on its context. Everything that the component needs to do its job had to be passed in, and that is enough for it to be reusable not just across screens in your application but also across different applications. Or is it? Let’s take a look at the component code again:

1
2
3
4
5
6
7
8
9
10
11
12
13
App.StarRatingComponent = Ember.Component.extend({
  classNames: ['rating-panel'],

  fullStars: Ember.computed.alias('item.rating'),
  numStars:  Ember.computed.alias('maxRating'),
  (...)
  actions: {
    setRating: function() {
      var newRating = parseInt($(event.target).attr('data-rating'), 10);
      this.get('item').set('rating', newRating);
      this.sendAction('setAction', this.get('item'));
    }
  }

Is something assumed about the object whose rating our component will display and set? I’ll give you some time to think about it.

A glove that fits all hands

What we assume is that the item that gets passed in has a rating property. If we really want our component to be used in all Ember applications (why not reach for the stars?), then this should not be an assumption that we make. After all, a player in a hockey team might have a score property and not rating. We could get around that by aliasing score to rating in our controller:

1
2
3
App.PlayerController = Ember.ObjectController.extend({
  rating: Ember.computed.alias('score');
});

However, this is inconvenient for the app developer and is only necessary because the star-rating component is not flexible enough. It’s as if I had to reshape my hand to fit the glove.

So let’s make it take the property name as a parameter, too:

<script type="text/x-handlebars" data-template-name="artists/songs">
  {{#each songs}}
    <div class="list-group-item">
      {{title}}
      {{star-rating item=this ratingProperty="rating" maxRating=5 setAction="setRating"}}
    </div>
  (...)
  {{/each}}
</script>

That was easy, now comes the harder part, the component code. Previously, the fullStars property of the component was just an alias for item.rating. We can’t do that anymore, since the name of the rating property is only known when the component is used in a template, and can thus differ in each case.

Did Ember let us down this time? Before, it had kept the fullStars property of our component in sync with the item’s rating. We just sat back and took sips of our mojito. Now, when the going gets tough, we are on our own.

Well, not really. We are doing some advanced stuff so it’s no surprise that we have to use advanced tools that are not needed in the majority of cases. Ember has nice lower-level functions to support us.

We have to set up the property synchronization ourselves but it sounds scarier than it is. We just have to watch when the item’s rating (score, points, etc.) property changes and set the fullStars property to that value:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
App.StarRatingComponent = Ember.Component.extend({
  classNames: ['rating-panel'],

  numStars:  Ember.computed.alias('maxRating'),
  fullStars: null,

  didInsertElement: function() {
    var property = this.get('ratingProperty');
    this.set('fullStars', this.get('item').get(property));
    Ember.addObserver(this.get('item'), property, this, this.ratingPropertyDidChange);
  },

  willDestroyElement: function() {
    var property = this.get('ratingProperty');
    Ember.removeObserver(this.get('item'), property, this.ratingPropertyDidChange);
  },

  ratingPropertyDidChange: function(item, ratingProperty) {
    this.set('fullStars', item.get(ratingProperty));
  },
  (...)
}

There are several things that might be new to you, dear reader, so let me go through each of them.

The most important thing is the call to ‘Ember.addObserver(object, property, context, function)’. Whenever property of object changes, it calls function with context as its this. (Providing a context is optional).

The observer function (ratingPropertyDidChange) gets the object that was changed as its first parameter and the property name that was changed. In this case, it does not have to do anything else but set the fullStars property of the component to the new value of the item’s rating property.

The observer is set up in the didInsertElement function. It is a handy lifecycle-event for Ember views (and thus components) which gets called after the view has been inserted into the DOM. This time, we don’t need it to be in the DOM already but it serves as a convenient way to add the observer.

Lastly, since the observer was added manually, it has to be torn down manually, too, when it is no longer needed. We do this in willDestroyElement, another view lifecycle event which gets called before the element gets removed from the DOM. Also, the code comments mention the following about willDestroyElement:

If you write a `willDestroyElement()` handler, you can assume that your
`didInsertElement()` handler was called earlier for the same element.

This makes didInsertElement - willDestroyElement a perfect pair for manually setting up and tearing down event handlers (or observers) even if no DOM manipulation has to be carried out.

I’ve made a jsbin to show how the star-rating component can now be used with a score property while the component code stays identical:

Reusable Star Rating component

Conclusion

We now have a star-rating component that is general enough to be used in all contexts. Go ahead and use it in your Ember app and let me know if I missed something.

Actually, there are a couple of featurettes -unrelated to its flexibility, as far as I see- we can add which I might come back to.

Convert a View Into a Component

Intro to components in Ember

Components landed in Ember in 1.0.rc6, in June 2013. They are reusable widgets that are built on top of HTML and provide a richer functionality. For the sake of reusability, they are isolated from their surroundings, and -as opposed to views- do not have access to their context. Everything a component has to know from the outside world has to be passed in at creation. Anything it wants to communicate to the outside world needs to be sent via events (or actions, in Ember parlance).

Usability is meant not just between different parts of the same application but across Ember applications, too. Once achieved, it would imply that a component for a specific task needs to be written once and could be used anywhere, just like jQuery plugins.

That is an ambitious goal which is to expected from an ambitious framework. We are not quite there, yet, and the specifics are still under discussion. Nevertheless, components are a great thing and you should start using them today, if you have not already.

“View”.replace(“Component”)

Wherever you would use a component today, you would have used a view before components were possible. Views still have their role in an Ember app but when existing html functionality is enhanced to give a richer, or more complex user interaction and reusability is important, you should reach for components.

In this post, I’m going to show how to swap out an existing view with a component. The example I’m going to use is the star rating view from the Rock & Roll application.

Star rating component

Here is what the star rating view looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
App.StarRating = Ember.View.extend({
  classNames: ['rating-panel'],
  templateName: 'star-rating',
  rating: Ember.computed.alias('context.rating'),

  fullStars: Ember.computed.alias('rating'),
  numStars:  Ember.computed.alias('maxRating'),

  stars: function() {
    var ratings = [];
    var fullStars = this.starRange(1, this.get('fullStars'), 'full');
    var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty');
    Array.prototype.push.apply(ratings, fullStars);
    Array.prototype.push.apply(ratings, emptyStars);
    return ratings;
  }.property('fullStars', 'numStars'),

  starRange: function(start, end, type) {
    var starsData = [];
    for (var i = start; i <= end; i++) {
      starsData.push({ rating: i, full: type === 'full' });
    };
    return starsData;
  },
  actions: {
    setRating: function() {
      var newRating = $(event.target).data('rating');
      this.set('rating', newRating);
      App.Adapter.ajax('/songs/' + this.get('context.id'), {
        type: 'PUT',
        context: this,
        data: { rating: newRating }
      }).then(function() {
        console.log("Rating updated");
      }, function() {
        alert('Failed to set new rating');
      });
    }
  }
});

The most important thing about components is that they do not have access to their context so any code that does use it needs to be changed.

Back when I wrote the code, I was, somewhat surprisingly, wise enough to use properties whose semantics reflect the inner operation of the widget, namely fullStars and numStars. Now we can reap the benefits of this foresight, because the entire stars and starRange method can remain untouched. It is only the definition of the fullStars property and the setRating action that need to change.

Let’s quickly sketch up the interface of the component. It will need the item whose rating it sets/displays, the name of the action it sends to the outer world when a new rating is set (setAction) and the maximum number of stars.

Having established that, the code transforms to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
App.StarRatingComponent = Ember.Component.extend({
  classNames: ['rating-panel'],

  fullStars: Ember.computed.alias('item.rating'),

  (...)

  actions: {
    setRating: function() {
      var newRating = parseInt($(event.target).attr('data-rating'), 10);
      this.get('item').set('rating', newRating);
      this.sendAction('setAction', this.get('item'));
    }
  }
});

fullStars is now the rating property of the item (in our case, a song) that was passed in.

When a star is clicked, the setRating action is triggered. Here, again, the rating is updated on the item that was passed in. After that, it sends the action that was passed in as setAction to the controller it was used from, passing along the item it received. That is the aforementioned way of sending messages outside.

(You might wonder what sendAction does. It is a shorthand form of sendAction(this.get('foo'), ...).)

Rendering the component

The template will only have minor modifications made to it. Here is what it looked like in its infancy, back when it was a view:

<script type="text/x-handlebars" data-template-name="star-rating">
  {{#each view.stars}}
    <span {{bind-attr data-rating=rating}}
      {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}}
      {{action "setRating" target=view}}>
    </span>
  {{/each}}
</script>

And here is the shiny, new component form:

<script type="text/x-handlebars" data-template-name="components/star-rating">
  {{#each stars}}
    <span {{bind-attr data-rating=rating}}
      {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}}
      {{action "setRating"}}>
    </span>
  {{/each}}
</script>

The data-template-name of a component needs to start with components and the name of the component needs to have a dash in its name to prevent name collisions with html tags.

The other changes relate to the essence of components, namely that they are not embedded in their context but work in isolation. That is why we both property lookups (in #each stars) and action handlers (action "setRating") both target the component and thus the target does not need to be defined explicitly.

Even more importantly, an action fired from a component’s template will look for that action in the component but will not bubble to the controller (or route). That again enhances the component’s isolation and thus its reusability and shows the care that was made when desinging it.

(Unfortunately, if an action by that name is not found on the component, it will die a silent death which makes debugging more difficult).

Using the component

Now comes that part I love most. Using our polished component is just like calling a function in a language where state is not shared. You pass in everything the component needs to do its bidding and be done with it:

<script type="text/x-handlebars" data-template-name="artists/songs">
  (...)
  {{#each songs}}
    <div class="list-group-item">
      {{title}}
      {{star-rating item=this maxRating=5 setAction="setRating"}}
    </div>
  (...)
  {{/each}}
</script>

Handling the action sent from the component

We saw how the component will send the action passed in as setAction and pass along the item (now: song) with it. We just need to handle it the classic Ember way, either on the controller or the route:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
App.ArtistsSongsRoute = Ember.Route.extend({
  (...)
  actions: {
    setRating: function(song) {
      App.Adapter.ajax('/songs/' + song.get('id'), {
        type: 'PUT',
        data: { rating: song.get('rating') }
      }).then(function() {
        console.log("Rating updated");
      }, function() {
        alert('Failed to set new rating');
      });
    }
  }
});

Observe how the action to update a song’s rating to the backend had to be moved to the route, instead of the view/component where it does not belong. Another win for components.

Don’t get confused by the two different setRating actions. The first is the one defined on the component that gets triggered via the action helper from the component’s template, the second one is the action name that needs to be passed in and has to match the name of the event handler on the route.

Towards better reusability

I hope you got a taste of why components rock and what steps are taken in their design towards their reusability. However, it’s up to writers of components to go all the way and make components general enough to fulfill this promise.

That’s what I’m going to strive for in a later post.

Promises Instead of Callbacks

A few weeks ago I built up a very simple identity map to get rid of a bug in highlighting active links. I introduced promises in order to leverage the fact that Ember.js blocks in model hooks until the model is resolved (or rejected).

In this post, I am taking a step back, and converting all the ajax calls that fetch data from and store data to the backend to use promises. I am also going to extract the most basic adapter that exists, just to do away with the repetition in the XHR calls. Once I have these in place, I will able able to build nice features on top of that.

App.TheMostBasicAdapterThereIs

All calls go to the same backend and talk in json so these can be trivially extracted:

1
2
3
4
5
6
7
App.Adapter = {
  ajax: function(path, options) {
    var options = options || {};
    options.dataType = 'json';
    return Ember.$.ajax('http://rock-and-roll-api.herokuapp.com' + path, options)
  }
}

With that out of the way, we can see where these were used, and replace Ember.$.ajax with App.Adapter.ajax. In the process we are also going to convert the callback-style code to use promises, a worthwhile transformation.

Don’t call me back, promise me you’ll be there

Here is what the code for fetching all the artists from the API looks like after applying the adapter change:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
App.ArtistsRoute = Ember.Route.extend({
  model: function() {
    var artistObjects = [];
    App.Adapter.ajax('/artists', {
      success: function(artists) {
        artists.forEach(function(data) {
          artistObjects.pushObject(App.Artist.createRecord(data));
        });
      }
    });
    return artistObjects;
  },
  (...)
});

Notice that the model initially is an empty array and only when the ajax call returns successfully does that array get filled up and the template rerendered. Not a big deal in itself, but if in a child route we rely on the array containing all the artists (e.g when looking up the identity map or using modelFor), we can be bitten by the async bug. Promises to the rescue.

As I mentioned in the identity map post, if a promise is returned from a model hook, Ember.js will block until the promise is resolved (or rejected). Let’s follow in Ember.js footsteps and convert the above code to return a promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
App.ArtistsRoute = Ember.Route.extend({
  model: function() {
    return Ember.RSVP.Promise(function(resolve, reject) {
      App.Adapter.ajax('/artists').then(function(artists) {
        var artistObjects = [];
        artists.forEach(function(data) {
          artistObjects.pushObject(App.Artist.createRecord(data));
        });
        resolve(artistObjects);
      }, function(error) {
        reject(error);
      });
    });
  },
  (...)
});

We wrap the promise returned from the App.Adaptar.ajax call in another promise, which resolves with artist objects instead of the raw data that is returned by the API. In the rejection handler, we pass along any potential error responses by rejecting with the same error that we got.

Next, we do the same thing in the child route. We go from here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
App.ArtistsSongsRoute = Ember.Route.extend({
  model: function(params) {
    var artist = App.Artist.create();
    App.Adapter.ajax('/artists/' + params.slug, {
      success: function(data) {
        artist.setProperties({
          id: data.id,
          name: data.name,
          songs: App.Artist.extractSongs(data.songs, artist)
        });
      }
    });
    return artist;
  },
  (...)
});

To here:

1
2
3
4
5
6
7
8
9
10
11
12
App.ArtistsSongsRoute = Ember.Route.extend({
  model: function(params) {
    return Ember.RSVP.Promise(function(resolve, reject) {
      App.Adapter.ajax('/artists/' + params.slug).then(function(data) {
        resolve(App.Artist.createRecord(data));
      }, function(error) {
        reject(error);
      });
    });
  },
  (...)
});

To get the “100% promisified” seal, we’ll transform the create calls, too. I’ll only show the one to create an artist since creating a song is the same.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
createArtist: function() {
  var name = this.get('controller').get('newName');

  App.Adapter.ajax('/artists', {
    type: 'POST',
    data: { name: name },
    context: this
  }).then(function(data) {
      var artist = App.Artist.createRecord(data);
      this.modelFor('artists').pushObject(artist);
      this.get('controller').set('newName', '');
      this.transitionTo('artist.songs', artist);
  }, function(reason) {
    alert('Failed to save artist');
  });
}

Here, there is not that much of a difference, the success and error callbacks are replaced by fulfillment and rejection handlers.

The source code with these changes can be got here.

Further studies & posts

You can acquire a deeper knowledge about promises by reading Domenic Denicola’s “You’re missing the point of promises” post and using that as a base for further exploration. Steven Kane’s excellent promise-it-wont-hurt package makes you solve increasingly difficult challenges with promises, which is the best way to learn.

Promisifying all backend calls sets the stage for other routing-related improvements. Stay tuned for more.

How Real-time Updates Work in Discourse

Given that I started engaging with web sites in the early 2000s there are still some things today that I constantly marvel at. One of these things is real-live update, the absolutely wonderful experience that I’m looking at a page and it displays a change due to an action of another user right in front of my eyes, without me hitting refresh.

Discourse, being a state-of-the-art forum software does this, too, and, provided my enthusiasm with all things that bring the web alive, I wanted to understand how that works. More specifically I wanted to understand how displaying new posts for the topic I am looking at can work its magic.

In the following post, I want to lead you through the whole process so that you see exactly how the pieces fit together. In fact, that may be the thing I enjoy most as a developer. Being able to take apart a complex application and gain the comprehension of how the individual pieces function and how they are orchestrated to make a complex system work.

Tools

Discourse is built on Ruby on Rails and Ember.js, two fantasic frameworks. Given my recent fascination with front-end development, and Ember.js in particular, I’ll focus on the front-end part here and only talk about the back-end mechanism as much as it is needed to see the whole picture.

Consequently, some knowledge about Ember.js is assumed. You can go through the Getting Started guide on the official Ember.js site or -if you prefer showing to telling- sign up to my mailing list to watch a series of screencasts to get a basic grip on Ember.js architecture as we go through the building of an application.

Message bus

Discourse uses a ruby gem (library) called message_bus that enables listeners to subscribe to any channel of their liking and get notified about events happening on that channel.

It also includes a javascript lib to allow connecting to the message bus from the client-side application. That’s what Discourse uses from the Ember.js app. Let’s see how.

Subscribing to new posts on a topic

When the user navigates to a topic page, the topic route gets activated and its hooks run. After resolving the model, the setupController which, as its name indicates, sets up the controller belonging to the model. It, among other things, calls the subscribe method on the controller, see below:

1
2
3
4
5
6
Discourse.TopicRoute = Discourse.Route.extend({
  setupController: function(controller, model) {
    (...)
    controller.subscribe();
    (...)
  }

The controller for the model is Discourse.TopicController, so next we will look into that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Discourse.TopicController = Discourse.ObjectController.extend(..., {
  subscribe: function() {

    // Unsubscribe before subscribing again
    this.unsubscribe();

    var bus = Discourse.MessageBus;

    var topicController = this;
    bus.subscribe("/topic/" + (this.get('id')), function(data) {
      (...)

      // Add the new post into the stream
      topicController.get('postStream').triggerNewPostInStream(data.id);
    });
  },
  (...)
}

The controller subscribes to the channel /topic/<topic_id>. The client polls the message bus for potential new messages every 15 seconds. You can see the XHR calls in the console of your browser:

Polling the message bus

When something is published to that channel, the callback function gets called back with the data related to that event. The data, in that case, is going to be the new post record. When the callback is fired, we call the triggerNewPostInStream method on the postStream with the id of the post. What does triggerNewPostInStream do, then? We can check that in the PostStream model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
  We use this class to keep on top of streaming and filtering posts within a topic.
**/
Discourse.PostStream = Em.Object.extend({
  (...)
  /**
    Finds and adds a post to the stream by id. Typically this would happen if we receive a message
    from the message bus indicating there's a new post. We'll only insert it if we currently
    have no filters.
  **/
  triggerNewPostInStream: function(postId) {
    (...)

    var loadedAllPosts = this.get('loadedAllPosts');

    if (this.get('stream').indexOf(postId) === -1) {
      this.get('stream').addObject(postId);
      if (loadedAllPosts) { this.appendMore(); }
    }
  },
}

The docstring is quite revealing. If the post id is already in the stream, we don’t do anything. If it is not, we add it to the stream (an Ember array). If the loading of posts has finished, we are ready to append the new posts to the stream.

Notice we are adding post ids, not actual post records so the next investigation step is to explore how ids get turned into records.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
  Appends the next window of posts to the stream. Call it when scrolling downwards.

  @method appendMore
  @returns {Ember.Deferred} a promise that's resolved when the posts have been added.
**/
appendMore: function() {
  var self = this;

  // Make sure we can append more posts
  if (!self.get('canAppendMore')) { return Ember.RSVP.resolve(); }

  var postIds = self.get('nextWindow');
  if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve(); }

  self.set('loadingBelow', true);

  var stopLoading = function() {
    self.set('loadingBelow', false);
  };

  return self.findPostsByIds(postIds).then(function(posts) {
    posts.forEach(function(p) {
      self.appendPost(p);
    });
    stopLoading();
  }, stopLoading);
},

The above appendMore method is responsible for retrieving the post ids that have to be added below the currently visible posts and turning these ids into actual post records.

We are getting close now, but let me speed things up a bit by only explaining the process but not showing all the code which makes it so that the new post objects are finally pushed to the posts array from where they will be finally displayed. (If you are such a code untangler as I am, and would like to see the code, it is right here)

What happens is that the new posts get displayed in windows, not one by one. This window is kept updated in the nextWindow property, from the stream we pushed post ids into. It is the slice in this stream that starts at the last loaded post id and has a maximum length of posts_per_page, a configuration setting. This construct also makes it possible, quite ingeniously, for this same code to load the next batch of posts to the page as the user scrolls down.

The window still contains ids and to fetch the related post records an identity map (yes, Discourse has its identity map implementation, too!) is used via the findPostsByIds method. Once the records are retrieved , they are each passed to the appendPost method that just pushes them to the posts array.

Displaying the new post in the topic stream

The only thing remains to be seen for the whole picture to be clear is how the stream of posts is displayed in the browser. The template that renders the topic, along with its posts, is the topic template.

The relevant part of the template is below:

{{#unless postStream.loadingFilter}}
  {{cloaked-collection cloakView="post" idProperty="post_number" defaultHeight="200" content=postStream.posts slackRatio=slackRatio}}
{{/unless}}

If the post stream is not loading, we render the posts through the cloaked collection. I will not go into details about what cloaked-collection does, (but I highly recommend a blog post on it by its author, @eviltrout), the important thing in the current discussion is that it renders the post template (cloakView=”post”) for each post from postStream.posts (content=postStream.posts).

That is where the two parts come together. Since a binding is established with the above handlebars line to the posts property of the postStream, every time new posts are added (see how in the first part of the walkthrough), the collection is going to be rerendered and consequently the posts appear in “real-time”. The magic of Ember.js bindings.

In parting

I skipped over a couple of things so that this post does not turn into a chapter of a novel, but I hope that my walkthrough could let you peek behind the curtains and see how such a miraculous feature is made possible.

The key takeaway is that building with the right tools (namely the message bus and the solid foundations of Ember.js), which a lot of people have put an enormous amount of time into, makes such a killer feature within your reach. Not easy, but definitely doable.

At Your Service: Publicly Available API for the Rock and Roll Ember.js App

So far, if you wanted to code along my developing an example Ember.js application, Rock & Roll, you had to run the server side component. That required ruby to be installed on your machine, and you had to clone the repository, and start the server each time you wanted to make some progress with the application.

I realized that might be cumbersome and thus I made the server publicly available at http://rock-and-roll-api.herokuapp.com.

I have also updated the client-side component to connect to that remote api.. All backend requests now go through App.Adapter.ajax which basically just delegates to Ember.$.ajax prefixing urls with the backend host:

1
2
3
4
5
App.Adapter = {
  ajax: function(path, options) {
    return Ember.$.ajax('http://rock-and-roll-api.herokuapp.com' + path, options)
  }
}

If you work with an earlier version of the client app, you might have to rewrite urls in multiple places but I figured it is still less work than running the server yourself.

I hope that facilitates your working along with the Rock & Roll app and makes your journey to Ember.js proficiency smoother. If you want to see the screencast series in which I develop said application, you can sign up to my mailing list and have each episode auto-delivered to your inbox.