Grouping Items for Display in Ember

ember

I’m working on an Ember.js project that requires grouping a list of objects by one of their properties for display. I’ve got one model - call it a plan - which is joined to a bunch of actions by a join model. Each action has a year attribute defined on it.

When a user views the plan, I’d like to make a view that shows all of the actions, grouped by year. This would be trivial if the actions belongedTo some sort of year model, which would belongTo the plan - in that case, something along the following lines in the Handlebars template would work great:

template
1
2
3
4
5
6

  <h1>  </h1>
  
    <!-- some display logic for the action -->
  

However, we can’t do that, for a variety of back end / performance reasons - the two-tiered fetch from the REST API becomes a problem.

Here’s what I originally wanted to do. In the controller:

controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
groupedActions: function() {
  //First we need to get an array of the years
  var years = this.get('actions').map(function(act) {
    return act.get('year');
  }).uniq();

  var grouped = years.forEach(function(year) {
    var filteredActions = this.get('actions').filter(function(action) {

    });

    return [year, filteredActions];
  });
}.property('actions.@each')

Then, in the view, we could display the grouped records as so:

template
1
2
3
4
5
6

  <h1>  </h1>
  
    <!-- some display logic for the action -->
  

Ember.js won’t let you do this, which might be frustrating for those of us who are used to less pervasive frameworks. There is a simple solution, though: just replace the array with a non-persisted Ember.Object!

The following solution works. In the controller:

controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
groupedActions: function() {
  //First we need to get an array of the years
  var years = this.get('actions').map(function(act) {
    return act.get('year');
  }).uniq();

  var grouped = years.forEach(function(year) {
    var filteredActions = this.get('actions').filter(function(action) {
      return action.year === year;
    });

    //Todo
    return Ember.Object.create({
      year: year,
      actions: filteredActions
    });
  });
}.property('actions.@each')

This, when used with the template code shown below, works splendidly.

template
1
2
3
4
5
6

  <h1>  </h1>
  
    <!-- some display logic for the action -->
  

The moral of the story is thus: remember that when workin with Ember.js objects that are to be displayed in Handlebars templates, your life will be made easier by defaulting to returning Ember objects, even if those objects have no associated model.

One word of caution: I don’t actulaly recommend making the groupedActions function observe actions.@each via the .property() call. In my experience, it was somewhat difficult to get reliable performance in this way. I created another property that determined if all the actions were loaded, and then made the wrapper function observer that, so that the function would only be called after a completed reload from the server.