Migrating to Ember Tables on Open Event Frontend – Part 1: The Set-Up

This blog article will illustrate how ember tables were set up, reopened as a component, for customization and how the pagination module was implemented.

Ember source 3.11 which Open event frontend uses is not compatible with the last release of ember tables, hence the master branch of ember tables which does support the latest ember source was chosen.  To install a dependency from a Github repository link instead of a yarn package, we can use

yarn add addepar/ember-table#0aa5637

Ember tables offer no inbuilt theme, and use the default HTML styles. They do have support for styling, but only via CSS selectors in CSS files. In our use case, we needed the styling to be those of semantic UI tables. However, for that the classes had to be added inside the table element, and ember tables by default, don’t allow addition of classes, as the table was under other layers. Even if the classNames property of component had  been specified, it would just append the specified class names to the wrapper of the table element, not the actual table itself. Ember’s reopen feature was used to solve this problem. The reopen method allows ‘reopening’ of the component in the sense that it’s existing properties can be overwritten, or new properties can be added.

It is a convention to store the reopened component files in a folder separate from external folder.

The definition of the table was inside a component.js file inside the source of ember tables. Hence a new file located at app/extensions/ember-table/component.js was created. 

Then the component.js was reopened to change the source of the template file which is used for ember tables.

import component from 'ember-table/components/ember-table/component';
import layout from './template';

component.reopen({
  layout
});

This reopening modifies the ember table component, such that it now searches for the layout file in the new directory at ember-table/components/ember-table/layout.hbs

The file layout.hbs now contains 

<div class="resize-container">
  <table class="ui unstackable table">
    {{yield (hash
              api=api
              head=(component "ember-thead" api=api)
              body=(component "ember-tbody" api=api)
              foot=(component "ember-tfoot" api=api)
            )}}
  </table>
</div> 

It is the exact same file which was present in the source of ember tables, with the only modification being the addition of class ui unstackable table to the table element. This class makes the table support semantic UI styling.

Ember tables follow a structure similar to ember model tables, wherein the columns are defined inside the controller of the route which will render the table. However, it is not possible to pass the actions defined in the controller to the final cell components for columns without passing them throughout until the very last layer. I.e. actions of controller are not passed to the custom cell components, automatically. Specifying them explicitly results in loss of generalisation, and a significant portion of code will be repeated.  Also, sometimes, we might need to pass more than one valuePaths if we don’t need to pass the entire entity, if a column cell needs only some of the properties. Custom options were a requirement as well. Hence, to generalise these properties, the expanded form of  ember table from ember table docs was slightly modified to :

{{#ember-table as |t|}}
      {{#t.head sortFunction=null columns=columns enableReorder=true as |h|}}
        {{#h.row as |r|}}
          {{#r.cell as |column|}}
            {{#if column.headerComponent}}
              {{#component
                column.headerComponent
              }}
                {{column.name}}
              {{/component}}
            {{else}}
              {{column.name}}
            {{/if}}
             {{/r.cell}}
            {{/h.row}}
         {{/t.head}}
      {{#t.body rows=rows as |b|}}
        {{#b.row as |r|}}
          {{#r.cell as |cell column row|}}
            {{#if column.cellComponent}}
              {{#component column.cellComponent
                           record=(get row column.valuePath)
                           extraRecords=(get-properties row column.extraValuePaths)
                           props=(hash options=column.options actions=column.actions)
              }}
                {{cell}}
              {{/component}}
            {{else}}
              {{cell}}
            {{/if}}
             {{/r.cell}}
            {{/b.row}}
         {{/t.body}}
    {{/ember-table}}

Two additions were made in terms of properties passed to a cell in the table.

A props object, which is a hash of the options and actions properties which will be defined inside the columns definition as column properties.

Also the extraValuePaths are passed as extraRecords using getProperties  helper.

The getProperties helper takes in an object and a list of properties, it then returns those properties of an object, as  hash.

export function getProperties(params = []) {
  if (params.length < 2 || !params[1]) {
    return {};
  }
  let inputParams = params.slice();
  const row = inputParams.shift();
  return emberGetProperties(row, flatten(inputParams));
}

An important thing to note is that even though the actions defined in the controller can be passed in the actions property, they still require a correct context of this. That is achieved using binding the current context of this to them before passing.

actions: {
          moveToPublic  : this.moveToPublic.bind(this),
          moveToDetails : this.moveToDetails.bind(this),
          editEvent     : this.editEvent.bind(this)
        }

The example above shows how the correct context of this is binded with actions.

The next blog will cover more details about the implementation ember tables, and focus on various utilities present inside tables like pagination, searching and sorting.

Resources 

CosmicCoder96

GSOC 17 @ FOSSASIA | Full Stack Developer | Swimmer

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.