Sorting table rows in Ember


#1

I have some experience with Angular and I’d like to learn Ember to make sure I’m using the right tool for the job. I’ve looked at Tom’s demo “blog” video and browsed the docs, but honestly I’m finding it a little tough to do simple things.

For instance, assume we have a table that writes out dynamic rows. Looking around SO, this is the best way I’ve found to sort on row properties:

Template

<table>
  <thead>
    <th {{action "sort" "id"}}>ID</th>
    <th {{action "sort" "name"}}>Name</th>
  </thead>
  <tbody>
    {{#each item in sortedContent}}
      <tr>
        <td>{{item.id}}</td>
        <td>{{item.name}}</td>
      </tr>
    {{/each}}
  </tbody>
</table>

App

App = Ember.Application.create();

App.IndexRoute = Ember.Route.extend({
  actions: {
    sort: function(sortBy) {
      var previousSortBy = this.controller.get('sortProperties.0');

      if (sortBy === previousSortBy) {
        return this.controller.set('sortAscending', !this.controller.get('sortAscending'));
      }
      else {
        this.controller.set('sortAscending', true);
        return this.controller.set('sortProperties', [sortBy]);
      }
    }
  }
});

App.IndexController = Ember.ArrayController.extend({
  sortProperties: ['id'],
  sortAscending: true,
  content: [{ id: 1, name: "a" }, {id: 2, name: "b"};],
  sortedContent: (function() {
    var content = this.get("content") || [];

    return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
      content: content.toArray(),
      sortProperties: this.get('sortProperties'),
      sortAscending: this.get('sortAscending')
    });
  }).property("content.@each", 'sortProperties', 'sortAscending')
});

First off, please correct me if there is a simpler way to do this. I’m just going off what I’ve seen used elsewhere.

A few things strike me about this:

  1. Does the entire controller need to extend ArrayController? What if I had lots of other data and this array was just a portion of it?
  2. It seems strange to have to deal with ArrayProxy.createWithMixins since I’ve already defined content as an array.
  3. It’s not clear to me what property("content.@each") is for or why it’s necessary.

In Angular, I’d solve the same problem with something like this:

<tr ng-repeat="item in items | orderBy:sortField:sortAscending">...</tr>

I know it’s not Apples-to-Apples. Still, I can’t help but feel there’s a little too much pomp and circumstance with the Ember implementation. My sense is that Angular is easier, but that Ember might be better for larger pages since it avoids things like dirty checking. That being said, my first medium sized Angular app was a breeze. So far, I haven’t had the same success with Ember.

Congrats on 1.0. I look forward to learning more about the framework.


#2

You shouldn’t need to define sortedContent. Use {{#each controller}} instead of {{#each sortedContent}}.

This should be enough:

App.IndexController = Ember.ArrayController.extend({
  sortProperties: ['id'],
  sortAscending: true
});

It’s not clear to me what property(“content.@each”) is for or why it’s necessary.

content.@each is a way to observe on each item in the array, no just the array’s properties on the top level (like length). Basically if you didn’t have that, if a property changed on an item in your array, the observer wouldn’t fire. But you shouldn’t need this since you don’t need to define sortedContent.


#3

This is great stuff. I’m so glad to see an elegant solution to a simple problem like this.

Thanks for highlighting some framework details as well. Your description of .property("content.@each") describes the feature nicely. After building complicated apps it’s nice to know there’s some flexibility in how dynamic properties are observed.


#4

Take a look at sorting table header.

<table>
  <thead>
    {{view Bootstrap.SortingTableHeader property="id" text="ID"}}
    {{view Bootstrap.SortingTableHeader property="name" text="Name"}}
  </thead>
  <tbody>
    {{#each item}}
      <tr>
        <td>{{id}}</td>
        <td>{{name}}</td>
      </tr>
    {{/each}}
  </tbody>