Rows grouping in the table

This tutorial fits ember-models-table version 2.2.3 or higher. Rows grouping is supported only for models-table and it wasn’t tested with a models-table-server-paginated!

First of all to start use rows grouping you must set component’s parameter useDataGrouping to true. Next you have to set property name used to group initially (currentGroupingPropertyName). Next you’ll see that rows grouping is working and looks like this:

Rows grouping example

Table rows are grouped by property country ()table doesn’t have a column with such property

Clicking on country name will collapse or expand rows group.

Briefly, the list will describe everything that the Component is able to do in scope of rows grouping, and then on a real example we will analyze in more detail:

  • Rows grouping by some property values. Property many be nested.
  • Sorting by groping property values.
  • Ability to set a list of properties available for grouping and switch between them. However, grouping may be done only by single property.
  • Property used for grouping may shown in the separated row on the top of the group or in the first left column (it’ll contain a single cell with rowspan equal to the rows number in the group).
  • Instead of the value of the group property, you can render a custom Component. It has next properties and handlers:
    • Name and value of the current group property
    • closure action to toggle group
    • closure action to expand/collapse all rows in the group
    • closure action to select/deselect all rows in the group
    • List will all selected rows in the group
    • List will all expanded rows in the group
    • List with all rows in the group
    • List will all visible rows in the group. The previous one differs only if the group on the page is the last one and some of its rows are displayed on the next page

Let’s use all of this in the real example. As usual GitHub API will be used. We’ll show all comments for issue ember.js/13071 in the table.

Two Models are needed – comment and user. Firs one is a comment itself and second one is a data about comment’s author.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// app/models/comment.js
import Model from "ember-data/model";
import attr from "ember-data/attr";
import { belongsTo, hasMany } from "ember-data/relationships";
import Ember from 'ember';
 
export default Model.extend({
  url: attr('string'),
  html_url: attr('string'),
  issue_url: attr('string'),
  user: belongsTo('user'),
  created_at: attr('string'),
  updated_at: attr('string'),
  author_association: attr('string'),
  body: attr('string'),
  createdDay: Ember.computed('created_at', function () {
    const d = new Date(this.get('created_at'));
    let month = d.getMonth();
    month = (month < 10 ? '0': '') + month;
    let day = d.getDate();
    day = (day < 10 ? '0' : '') + day;
    return `${d.getFullYear()}-${month}-${day}`;
  })
});
1
2
3
4
5
6
7
8
9
10
11
12
13
// app/models/user.js
import Model from "ember-data/model";
import attr from "ember-data/attr";
import { belongsTo, hasMany } from "ember-data/relationships";
 
export default Model.extend({
  login: attr('string'),
  avatar_url: attr('string'),
  url: attr('string'),
  html_url: attr('string'),
  type: attr('string'),
  site_admin: attr('boolean')
});

Response for request to the api/comments will give us whole needed data. So, we may use only Adapter and Serializer for Model comment:

1
2
3
4
5
6
7
8
9
10
11
// app/adapters/comment.js
import DS from 'ember-data';
 
export default DS.RESTAdapter.extend({
  host: 'https://api.github.com',
  namespace: 'repos/emberjs/ember.js/issues/13071/comments',
  query(store, b, query) {
    const url = `${this.get('host')}/${this.get('namespace')}`;
    return this.ajax(url, 'GET', {data: query});
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// app/serializers/comment.js
import Ember from 'ember';
import DS from 'ember-data';
 
export default DS.JSONAPISerializer.extend({
  _mapIncluded(data, type) {
    const clb = item => ({id: item.id, type, attributes: item});
    return Ember.isArray(data) ? data.map(clb) : [clb(data)];
  },
  normalizeQueryResponse(a, b, payload) {
    const included = [];
    const newPayload = {
    	data: payload.map(item => {
        const itemData = {
          id: item.id,
          type: 'comment',
          attributes: item,
          relationships: {
            user: {
              data: { id: item.user.id, type: 'user' }
            }
          }
        };
        included.pushObjects(this._mapIncluded(item.user, 'user'));
        return itemData;
      })
    };
    newPayload.included = included;
    return newPayload;
  }
});

Data for table may be get in the Route for page where table is:

1
2
3
4
5
6
7
8
// app/routes/index.js
import Ember from 'ember';
 
export default Ember.Route.extend({
  model() {
    return this.get('store').query('comment', {per_page: 100});
  }
});

Our table will have four columns:

1
2
3
4
5
6
[
  {propertyName: 'id'},
  {propertyName: 'user.login', title: 'User'},
  {propertyName: 'created_at'},
  {propertyName: 'updated_at'}
]

dataGroupProperties allows to set a list of properties used to group rows. It can be a list of string or objects with two fields label and value.

1
[{value: 'user.login', label: 'User'}, {value: 'createdDay', label: 'Created Day'}]

We use two properties createdDay and user.login. As you can see, grouping can be done even for property from the another Model.

dataGroupProperties must have a value from currentGroupingPropertyName set on Component init!

groupingRowComponent contains a Component name used in place of the default one. Let’s consider this Component in detail. We can create it completely from scratch or extend a default models-table/row-group-toggle.js. Let’s use a second option:

1
2
3
4
5
// app/components/grouping-row.js
import DefaultComponent from './models-table/row-group-toggle';
 
export default DefaultComponent.extend({
});

The advantage of this method is that we do not have to duplicate the declaration of the three event handlers. However, we have to create our custom template. In the demo purposes it will have all available functional:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{{! app/templates/components/grouping-row.hbs}}
<p>{{currentGroupingPropertyName}}: {{groupedValue}} ({{visibleGroupedItems.length}} / {{groupedItems.length}})</p>
<div class="btn-group">
  <button class="btn" {{action "toggleGroupedRows" bubbles=false}}>
    <i class="glyphicon glyphicon-resize-full"></i>/<i class="glyphicon glyphicon-resize-small"></i>
  </button>
  <button class="btn" {{action "toggleGroupedRowsExpands" bubbles=false}}>
    <i class={{themeInstance.expand-row}}></i>/<i class={{themeInstance.collapse-row}}></i>
    ({{expandedGroupedItems.length}})
  </button>
  <button class="btn" {{action "toggleGroupedRowsSelection" bubbles=false}}>
    <i class={{themeInstance.select-row}}></i>/<i class={{themeInstance.deselect-row}}></i>
    ({{selectedGroupedItems.length}})
  </button>
</div>

currentGroupingPropertyName – is a property name used now for rows grouping. groupedValue – is its value for current rows group. visibleGroupedItems – is a list with visible rows in the group. groupedItems – is a list with rows in the group. The difference between them is only if the group is displayed last on the current page of the table and does not get there completely.

Next we have three buttons. First one toggle rows group. Second one is used to expand/collapse all rows in the group. Third one os used to select/deselect all rows in the group. Keep in mind that all handler are declared with bubbles=false. As the text of the last two buttons (besides the icons), the number of rows of the group in the corresponding state (expanded and selected) is displayed.

Demo as usual availalbe on the ember-twiddle – Rows Grouping. Code examples are taken from there, so the are no 2.16 features (for some reasons only 2.12 can be used in the twiddle).

, , ,

Add comment

Top ↑ | Main page | Back