Creating a Sortable Table Component in Ember.js

Sometimes in our web applications we want to display tabular data. This by itself is easy enough, but we would also want this table to be sortable AND reusable. We could just use a JS plugin we found on the web, but it's possible to do this in just a couple of lines of code in Ember.js.

In this post I will show you how to create a table component that accepts any data and has sorting built-in. We will use Ember CLI version 1.13.8 and Ember version 2.1.0.

If you just want to play around with the end result, the code is available in this github repository.

Setup

I'll assume that you have Ember CLI installed already, so we'll just cut straight to the chase. First, we need to initialize an Ember project with this command:

$ ember new ember-dynamic-table

We will also use ember-cli-mirage in this example. This addon will allow us to easily mock data that will later be displayed in our table. Run this command to install the addon, its dependencies and create a basic configuration:

$ ember install ember-cli-mirage

Next up, we will need to create a model for our data. We will use the resource generator to automatically create the model, route and template files. We will also create a controller:

$ ember g resource users
$ ember g controller users

We create an application adapter:

$ ember g adapter application

And finally, we will add Bootstrap to our project, so that the table looks a bit nicer:

$ bower install --save-dev bootstrap

We need to add bootstrap assets to ember-cli-build.js:

// ember-cli-build.js
var EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function(defaults) {  
  var app = new EmberApp(defaults, {
  });

app.import('bower_components/bootstrap/dist/css/bootstrap.css');  
  app.import('bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.eot', {
    destDir: 'fonts'
  });
  app.import('bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.svg', {
    destDir: 'fonts'
  });
  app.import('bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf', {
    destDir: 'fonts'
  });
  app.import('bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff', {
    destDir: 'fonts'
  });
  app.import('bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2', {
    destDir: 'fonts'
  });

  return app.toTree();
};

Model and mocking

We will create a simple user model with some basic fields:

// app/models/user.js
import DS from 'ember-data';

export default DS.Model.extend({  
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),
  age: DS.attr('number'),
  createDate: DS.attr('date')
});

Furthermore, we want to configure ember-cli-mirage to mock some user data. Create a user.js file under the app/mirage/factories folder:

// app/mirage/factories/user.js
import Mirage, { faker } from 'ember-cli-mirage';

export default Mirage.Factory.extend({  
  firstName() { return faker.name.firstName(); },
  lastName() { return faker.name.lastName(); },
  age: faker.list.random(18, 20, 28, 32, 45, 60),
  createDate() { return faker.date.past(); }
});

The above code defines how to generate sample data using faker.js, so we won't have to worry about populating any models for our development. Now we also need to configure the default mirage scenarios, in order for the addon to actually generate the data:

// app/mirage/scenarios/default.js
export default function( server ) {  
  server.createList('user', 10);
}

Finally, we need to add a users route to the mirage configuration:

// app/mirage/config.js
export default function() {  
  this.namespace = 'api';

  this.get('/users');
}

Here we have set the namespace to api. In order for this setting to work properly, we also need to do the same in our application adapter:

// app/adapters/application.js
import DS from 'ember-data';

export default DS.RESTAdapter.extend({  
    namespace: 'api'
});

Route

Now we need to setup our users route, so that it returns a list of users:

// app/routes/users.js
import Ember from 'ember';

export default Ember.Route.extend({  
  model() {
    return this.store.findAll('user');
  }
});

And here is the route's template:

// app/templates/users.hbs
<div class="container">  
  <div class="row">
    <div class="col-xs-12">
      {{dynamic-table content=model columns=userColumns}}
    </div>
  </div>
</div>  

As you can see above, we will need to create a dynamic-table component. But before we do that - what is the userColumns object?
Well, before we continue, we have to create a simple configuration object, which we will then pass to our component. This is what it looks like:

// app/controllers/users.js
import Ember from 'ember';

export default Ember.Controller.extend({  
  userColumns: Ember.A([
    {
      'key': 'firstName',
      'displayName': 'First Name'
    },
    {
      'key': 'lastName',
      'displayName': 'Last Name'
    },
    {
      'key': 'age',
      'displayName': 'Age'
    },
    {
      'key': 'createDate',
      'displayName': 'Created at'
    }
  ])
});

They key property will be responsible for getting relevant data out of our model. The displayName will control what's displayed in the table's header.

Component

Finally, we can create the actual component:

$ ember g component dynamic-table

The above command will create the component file along with its template.

Here's the logic:

// app/components/dynamic-table.js
import Ember from 'ember';

export default Ember.Component.extend({  
  sortProps: [],
  sortedContent: Ember.computed.sort('content', 'sortProps'),

  actions: {
    sort(direction, key) {
      this.set('sortProps', [key + ':' + direction]);
    }
  }
});

Let's stop here for a minute. We want the content of our table to be sorted and in order to do this, we will use the magnificent Ember.computed namespace. It contains a ton of useful methods, one of which is sort, used in our example. Thanks to some Ember magic, our sortedContent will automatically recompute when the model or the sort properties are changed - this will also trigger an update in the template.

We also define a sort action, which will modify the sortProps property depending on the arguments received (they will be sent from the component's template).

Now here's the template:

// app/templates/components/dynamic-table.hbs
<table class='table'>  
  <thead>
    <tr>
      {{#each columns as |column|}}
        <th>
          {{column.displayName}}
          <span class='glyphicon glyphicon-triangle-top' aria-hidden='true' {{action 'sort' 'asc' column.key}}></span>
          <span class='glyphicon glyphicon-triangle-bottom' aria-hidden='true' {{action 'sort' 'desc' column.key}}></span>
        </th>
      {{/each}}
    </tr>
  </thead>
  <tbody>
    {{#each sortedContent as |item|}}
      <tr>
        {{#each columns as |column|}}
          <td>
            {{get item column.key}}
          </td>
        {{/each}}
      </tr>
    {{/each}}
  </tbody>
</table>  

Here we have defined a simple table. In it's thead we create a row for the columns we've previously defined in the controller. This will render the displayName properties. Next to these names we define clickable arrows (thanks to the Bootstrap Glyphicons), which will trigger the sort action. The actions will pass 2 parameters: the column's key and the direction of the sort, either asc or desc.

Next, we iterate through the model we've passed into our component. For each model we create a new row, where a cell is created for each property we have defined earlier in the controller.

Here we're using a brand new get helper, introduced in Ember version 2.1.0. We pass in the current user we're iterating through and the key we want to display. This will get the value of the property we're interested in.

For example, {{get user 'firstName'}} would display the user's firstName property in our template.

The component in action

Finally, the component should be ready for use. Let's fire up the ember development server:

$ ember serve

Now let's open up our browser and go to the following address: http://localhost:4200/users.

We will see the table component with 10 rows of sample users (generated by ember-cli-mirage).

Click the arrows in the header and - guess what? It sorts! Here's what it looks like:
Dynamic Table GIF

Neat, huh?

Summary

We have successfully created a dynamic table component in Ember.js. It is easily configurable, accepts any data and has built-in sorting.
All this in only a few lines of code (the component logic itself was 12 lines!). And we didn't have to use any external plugins.

I hope you can see now how easy it is to build semi-advanced functionality in Ember. Much more is possible and quite often we don't need to download any addons.

The code for this post is available at this repository. If you want to see it in action, visit http://kevinkucharczyk.github.io/ember-dynamic-table.

What's next?

The component we've created is pretty basic. If you want to practice some more Ember, here's a couple of things you could do to improve it:

  • Add pagination
  • Add a customizable column width
  • Add lazy loading for further rows of data

I hope you liked what you saw. If you have any questions or suggestions, feel free to comment below!

Kevin P. Kucharczyk

Read more posts by this author.

Kraków, Poland