Track table selections in Ember

A common user interface pattern in web applications is to allow users to select rows in a table on which common functions can be performed, such as removing the entry or assigning a category.

In this post I'll be continuing the bookworm application from model-based sidebars with Ember by adding support for tracking row selections.

table with selectable rows

Component

At the heart of the solution are two components: book-table, which renders the outer table, and book-table-row, which renders a row in the table body.

import Ember from 'ember';

var BookTable = Ember.Component.extend({  
    actions: {
        change(bookId) {
            var selectedBooks = this.get('selectedBooks');
            var index = selectedBooks.indexOf(bookId);
            if (index === -1) {
                selectedBooks.pushObject(bookId);
            } else {
                selectedBooks.removeObject(bookId);
            }
        }
    },
    init() {
        this.set(`selectedBooks`, []);
        this._super();
    },
    didUpdateAttrs() {
        this.set(`selectedBooks`, []);
        this._super();
    },
});

BookTable.reopenClass({  
    positionalParams: ['books'],
});

export default BookTable;  

The book-table component keeps track of the selections in an array. The array is reset in the didUpdateAttrs component life-cycle hook:

didUpdateAttrs runs when the attributes of a component have changed (but not when the component is re-rendered, via component.rerender, component.set, or changes in models or services used by the template).

This hook is used to reset the selections when the route changes.

When the row selection changes, the bookId for that row is either added to or removed from the selections array. This must be done with pushObject and removeObject to ensure observers are notified of the changes so that the template is updated.

Closure actions

Closure actions have replaced action bubbling as the method for triggering changes within components. Ember Screencasts has an excellent introduction to closure actions which I recommend you check out. In short, closure actions allow you to pass the function that will handle an event into the component, which will call it directly.

In the book collection manager project, the book-table component passes the change function to the book-table-row component as the handler for its select event:

<nav class="navbar navbar-light bg-faded" style="margin-top: 10px;">  
    <p class="pull-left" style="margin-bottom: 0; padding: 0.425rem 0 0 0.425rem;">
        {{selectedBooks.length}} books selected
    </p>
</nav>

<table class="table table-striped table-bordered">  
    <thead class="thead-inverse">
        <tr>
            <th style="width: 1px;"></th>
            <th>Title</th>
        </tr>
    </thead>
    <tbody>
        {{#each books as |book|}}
            {{book-table-row book select=(action 'change')}}
        {{/each}}
    </tbody>
</table>  

The change function is passed using a handlebars subexpression which calls the action helper with the name of the action handler we've defined in the book-table component. The result of the subexpression, the change function, is assigned to the select parameter of the book-table-row component.

In the book-table-row component the passed in function is available in this.attrs, which we can call in our checkbox handler.

import Ember from 'ember';

var BookTableRow = Ember.Component.extend({  
    tagName: 'tr',
    actions: {
        select(book) {
            this.attrs.select(book.id);
        }
    }
});

BookTableRow.reopenClass({  
    positionalParams: ['book'],
});

export default BookTableRow;  

The checkbox handler itself is also setup in the same way. The template for the book-table-row assigns the select action handler to the change event of the checkbox input component.

<td>  
    {{input type="checkbox" change=(action 'select' book) }}
</td>  
<td>{{book.title}}</td>  

Looking at it in reverse: the checkbox change event calls the select handler in book-table-row, which calls the select handler in book-table, which updates the selection array.


All the code for this post can be found on GitHub in the repository jonblack/bookworm under the tag track-table-selections.

Author image
Creator of Humble Coder and serial hobbyist