Sortable/filterable jsonapi compliant tables for ember-cli.
- Sort on a column by column basis.
- Filter on a column by column basis.
- Make physical requests to the API when filtering/sorting/paginating
$ ember install ember-tabular
Setup the ember-tabular template.
columns
- array (detailed below).modelName
- for the component to make the proper request when filtering/sorting, you must pass the model type matching your Ember model structure. e.g.brand/diagram
,product
.record
- this is bound to the controller and is used to iterate over the table's model data.
You have full control over your table's tbody
content. We are setting this to render the content into the {{yield body}}
of the table component.
Setup the columns array, which is how the table headers are constructed, label
is required in all cases.
// app/controllers/my-route.js
export default Ember.Controller.extend({
users: null,
columns: [
{
property: 'username',
label: 'Username',
defaultSort: 'username',
},
{
property: 'emailAddress',
label: 'Email',
},
{
property: 'firstName',
label: 'First Name',
},
{
property: 'lastName',
label: 'Last Name',
},
{
property: 'updatedAt',
label: 'Last Updated',
type: 'date',
},
],
});
Ember Tabular sticks very closely to jsonapi spec, a few examples of requests:
/users?filter[last-name]=McClane&page[limit]=10&page[offset]=0
filter[last-name]
- Filter based on jsonapi's recommended filtering: http://jsonapi.org/recommendations/#filteringpage[limit]
- Using a "offset-based" pagination strategy, send # of items per page.page[offset]
- Using a "offset-based" pagination strategy, starting item number.
/orders?filter[date-ordered-min]=2016-12-10&filter[dateordered-max]=2016-12-12&filter[order-number]=1029LG31&filter[order-profile]=1&page[limit]=&page[offset]=&sort=-date-ordered
sort
- Sort based on jsonapi's recommended sorting: http://jsonapi.org/format/#fetching-sorting- Ascending unless prefixed with
-
for descending.
- Ascending unless prefixed with
makeRequest
- boolean/string - Default: true- If
true
: Ember Tabular will make request based onmodelName
. - If
false
: Typically you'd bind the route's model torecord
.
- If
class
- string- Wraps the entire component.
tableClass
- string - Default: "table-bordered table-hover"- Wraps only the
<table>
and replaces defaults if provided.
- Wraps only the
staticParams
- object - Default: null-
Object to pass in static query-params that will not change based on any filter/sort criteria, ex. additional table-wide filters that need to be applied in all requests
?filter[is-open]=1
.// app/controllers/location.js export default Ember.Controller.extend({ staticParams: Ember.computed('model', function() { return { 'filter[is-open]': '1', 'include': 'hours', }; }), ... });
-
tableLoadedMessage
- string - Default: "No Data."- In some cases when the API response is loaded but does not contain any data "No Data." will not apply, on a case by case basis you can override this. For example, if you'd like to prompt the user to do some kind of action. "No data, select a different product".
export default Ember.Controller.extend({
users: null,
columns: [
{
property: 'username',
label: 'Username',
defaultSort: 'username',
type: 'text',
},
{
property: 'emailAddress',
label: 'Email',
type: 'text',
},
{
property: 'firstName',
label: 'First Name',
type: 'text',
},
{
property: 'lastName',
label: 'Last Name',
type: 'text',
},
{
property: 'updatedAt',
label: 'Last Updated',
type: 'date',
},
],
});
columns.property
- string- Required for column filtering/sorting
- Properties should be in camelCase format
columns.label
- string- Required in all use-cases
columns.type
- string - Default: text- Sets the filter
<input type="">
- Sets the filter
columns.sort
- boolean - Default: true- Required for column sorting
columns.defaultSort
- string- Initial sort value for API request
- Will be overridden with any sorting changes
Component has 3 yields setup by default, header
, body
, and footer
:
{{yield header}}
is rendered outside (above) the<div class="table-responsive">
on the root of the template.{{yield body}}
is rendered within the<tbody></tbody>
. Conditional based onrecord
.{{yield footer}}
is rendered outside (below) the<div class="table-responsive">
on the root of the template above the pagination.
Typically the global filter component would be rendered into the {{yield header}}
of the main table component using the yield conditional {{#if section.isHeader}} ...
. However, it can be used outside of the context of the main component if the proper properties are shared between the main component and sub-component.
- Sent in request as:
?filter[filterProperty]=searchFilter
, e.g.?filter[username]=John.Doe2
filter
- object - Default: null- Required
- Must also expose the
filter
property on the parentember-tabular
component to be able to pass thefilter
object back and forth between parent and child components.
query
- object - Default:this.get('query') || this.get('parentView.query')
- Pass the query object from the parent component if it is different or if used outside of the context of the component, otherwise query is optional and it component will attempt to grab within the context of the parent component.
filterProperty
- string - Default: null- Required
- Used with the "Global Filter Sub-Component".
- Pass the property name in camelCase format.
filterPlaceholder
- string - Default: null- Optional
- Placeholder to be used for the global-filter.
label
- string - Default: null- Optional
- Set a label on the global-filter.
inputClass
- string - Default: null- Optional
- Wraps the input field in a div.
labelClass
- string - Default: null- Optional
Date filter changes input type="date"
to take advantage of a browser's HTML5 date widget. Typically the date filter component would be rendered into the {{yield header}}
of the main table component using the yield conditional {{#if section.isHeader}} ...
. However, it can be used outside of the context of the main component if the proper properties are shared between the main component and sub-component.
- Sent in request as:
?filter[filterProperty]=dateFilter
, e.g.?filter[updated-at]=2015-06-29
filter
- object - Default: null- Required
- Must also expose the
filter
property on the parentember-tabular
component to be able to pass thefilter
object back and forth between parent and child components.
query
- object - Default:this.get('query') || this.get('parentView.query')
- Pass the query object from the parent component if it is different or if used outside of the context of the component, otherwise query is optional and it component will attempt to grab within the context of the parent component.
filterProperty
- string - Default: null- Required
- Used with the "Global Filter Sub-Component".
- Pass the property name in camelCase format.
dateFilter
- string - Default: null- Optional
- Sets the input value.
label
- string - Default: null- Optional
- Set a label on the global-filter.
inputClass
- string - Default: null- Optional
- Wraps the input field in a div.
labelClass
- string - Default: null- Optional
- This component adheres to jsonapi spec: http://jsonapi.org/
- This component expects jsonapi error format: http://jsonapi.org/format/#error-objects
- Specifically
error.detail
to display in the alert/error box
- Specifically
- Pagination is constructed using,
?page[offset]=A&page[limit]=B&sort=
- All other filters are sent through the jsonapi format spec: http://jsonapi.org/recommendations/#filtering
- This add-on imports font-awesome via bower, if your project is already including this dependency you may run into build issues, typically you can add
{overwrite: true}
withinmergeTree
in your ember-cli-build.js will resolve this conflict.
If you are using Ember Data, then you can lean on your application's custom adapter.
- Pagination
- Responses, depending upon API pagination strategy will need to be converted in the adapter/serializer to pass ember-tabular
offset
/limit
/page
properties to generate pagination internally.
- Responses, depending upon API pagination strategy will need to be converted in the adapter/serializer to pass ember-tabular
- Filtering
- This addon expects a
filter
object with nested property/value pairs.
- This addon expects a
If you are not using Ember Data then you can extend this addon's component and override a set of serialize and normalized methods:
import EmberTabular from 'ember-tabular/components/ember-tabular';
export default EmberTabular.extend({
serializePagination(params) {
// override default pagination ?page[offset]= and ?[page]limit=
// offset and limit will be sent as ?offset= and ?limit=
params.offset = (params.page * params.limit) - params.limit;
if (isNaN(params.offset)) {
params.offset = null;
}
return params;
},
});
import EmberTabular from 'ember-tabular/components/ember-tabular';
export default EmberTabular.extend({
serializeProperty(property) {
// Override to convert all properties sent in requests to camelize instead of the default dasherized
// ?filter[lastName]&sort=isAdmin
// (pseudo code)
if (property) {
return Ember.String.camelize(property);
}
return null;
},
});
Check add-on source for full list of serialized/normalized methods available for extension. Note:
- On success you must set the
record
with the array of table data
git clone
this repositorynpm install
bower install
ember server
- Visit your app at http://localhost:4200.
npm test
(Runsember try:testall
to test your addon against multiple Ember versions)ember test
ember test --server
ember build
For more information on using ember-cli, visit http://www.ember-cli.com/.