From f08991844b68642ef3be958be6df81337da858c3 Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Thu, 25 May 2017 15:57:02 -0400 Subject: [PATCH 1/8] Setting up changelog for v0.2.0 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704b832..ea690a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.2.0] - 2017-05-17 - Ember v2.X.X +### Added + +### Changed + +### Fixed + ## [0.1.0] - 2016-08-31 - Ember pre v1.13.10 ### Added - Initial base version built to support JSON API v1.0. From 6e46f3d3b72bbcd4eb4423c79f1abf90c8adccd3 Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Thu, 25 May 2017 15:58:25 -0400 Subject: [PATCH 2/8] Correct changelog entries under new tag --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea690a1..5e06f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [0.2.0] - 2017-05-17 - Ember v2.X.X ### Added +- Adds ability to set column filter/sort independently. ### Changed +- Upgrades ember-power-select to 1.4.3, may require refactoring of `class` to `triggerClass` if applicable. +- Upgrade ember and ember-cli to 2.10.0 ### Fixed @@ -21,14 +24,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Add subcomponent to change the table limit/count. - Setup `autoHide` computed property to conditionally hide the limit dropdown if the results are smaller than the smallest pagination limit. - Adds ability to persist data across transitions, setting `persistFiltering: true` and sharing the `filter`/`sort` properties with a controller/service. -- Adds ability to set column filter/sort independently. ### Changed - Update legacy name references in README.md. - Removed ember-canary from ember-try testing scenarios. - Removed ember-beta from ember-try testing scenarios. -- Upgrades ember-power-select to 1.4.3, may require refactoring of `class` to `triggerClass` if applicable. -- Upgrade ember and ember-cli to 2.10.0 ### Fixed - Fix erring ember try in ember-canary, properly remove component element wrapper. From 386457d9420f03ea50ac8bbd5c47c9aaeaf375bb Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Fri, 26 May 2017 00:26:37 -0400 Subject: [PATCH 3/8] Setup yuidocs and slim down README in favor of API documentation --- .gitignore | 1 + CHANGELOG.md | 1 + README.md | 217 ++++++++++++--------------------------------------- package.json | 1 + yuidoc.json | 19 +++++ 5 files changed, 74 insertions(+), 165 deletions(-) create mode 100644 yuidoc.json diff --git a/.gitignore b/.gitignore index 5ad14dd..576aad4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ /libpeerconnection.log npm-debug.log* testem.log +/docs/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e06f64..668cc30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [0.2.0] - 2017-05-17 - Ember v2.X.X ### Added - Adds ability to set column filter/sort independently. +- Add support for YUIDocs for better API documentation. ### Changed - Upgrades ember-power-select to 1.4.3, may require refactoring of `class` to `triggerClass` if applicable. diff --git a/README.md b/README.md index e79089f..76d6873 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ You have full control over your table's `tbody` content. We are setting this to {{row.emailAddress}} {{row.firstName}} {{row.lastName}} + {{row.updatedAt}} {{#link-to "index" class="btn btn-xs" role="button"}} Edit @@ -75,10 +76,15 @@ export default Ember.Controller.extend({ label: 'Last Updated', type: 'date', }, + { + label: 'Actions', + }, ], }); ``` +* Reference the [API specs/documentation](/docs/index.html) for more information and for advanced usage. + ### Request Format Ember Tabular sticks very closely to jsonapi spec, a few examples of requests: @@ -90,93 +96,6 @@ Ember Tabular sticks very closely to jsonapi spec, a few examples of requests: * `sort` - Sort based on jsonapi's recommended sorting: http://jsonapi.org/format/#fetching-sorting * Ascending unless prefixed with `-` for descending. -## Advanced Usage -### Template -```hbs -{{#ember-tabular - columns=columns - modelName="user" - record=users - class="table-default" - tableClass="table-bordered table-hover table-striped" - staticParams=staticParams - as |section|}} - ... -{{/ember-tabular}} -``` -* `makeRequest` - boolean/string - Default: true - * If `true`: Ember Tabular will make request based on `modelName`. - * If `false`: Typically you'd bind the route's model to `record`. -* `class` - string - * Wraps the entire component. -* `tableClass` - string - Default: "table-bordered table-hover" - * Wraps only the `` and replaces defaults if provided. -* `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`. - - ```js - // 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". - -### Controller -```js -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 `` -* `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 - ### Template - Yields ```hbs {{#ember-tabular columns=columns record=users as |section|}} @@ -206,26 +125,6 @@ Typically the global filter component would be rendered into the `{{yield header filterProperty="username" filterPlaceholder="Search by Username"}} ``` -* `filter` - object - Default: null - * Required - * Must also expose the `filter` property on the parent `ember-tabular` component to be able to pass the `filter` 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 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. @@ -237,26 +136,51 @@ Date filter changes `input type="date"` to take advantage of a browser's HTML5 d filterProperty="updatedAt" label="Last Updated"}} ``` -* `filter` - object - Default: null - * Required - * Must also expose the `filter` property on the parent `ember-tabular` component to be able to pass the `filter` 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 + +#### Dropdown Filter +Use the dropdown filter globally. One way to do this is by setting up a computed property that returns an array of label/value objects. + +```js + +export default Ember.Controller.extend({ + users: null, + actions: { + setIsAdminFilter(object) { + if (object) { + this.set('isAdminFilter', object.value); + } else { + this.set('isAdminFilter', null); + } + }, + }, + adminContent: Ember.computed(function() { + return [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + }, + ]; + }), +}); +``` +```hbs +{{#ember-tabular-dropdown-filter filter=filter filterProperty="isAdmin" label="Is Admin" searchFilter=isAdminFilter}} + {{#power-select + options=adminContent + selected=(find-by adminContent 'value' isAdminFilter) + searchField="label" + searchEnabled=false + placeholder="Select to filter" + onchange=(action "setIsAdminFilter") + as |option|}} + {{option.label}} + {{/power-select}} +{{/ember-tabular-dropdown-filter}} +``` ## Note * This component adheres to jsonapi spec: http://jsonapi.org/ @@ -274,44 +198,7 @@ If you are using Ember Data, then you can lean on your application's custom adap * Filtering * This addon expects a `filter` object with nested property/value pairs. -If you are not using Ember Data then you can extend this addon's component and override a set of serialize and normalized methods: -```js -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; - }, -}); -``` -```js -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 - +If you are not using Ember Data or following json:api then you can extend this component and override a set of serialize and normalized methods, checkout the [API specs/documentation](/docs/index.html) for more details/examples. # Contributing to this addon ## Installation diff --git a/package.json b/package.json index e1214f2..ef57f85 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "ember-cli-sri": "^2.1.0", "ember-cli-test-loader": "^1.1.0", "ember-cli-uglify": "^1.2.0", + "ember-cli-yuidoc": "0.8.8", "ember-data": "^2.10.0", "ember-disable-prototype-extensions": "^1.1.0", "ember-disable-proxy-controllers": "1.0.1", diff --git a/yuidoc.json b/yuidoc.json new file mode 100644 index 0000000..11dd498 --- /dev/null +++ b/yuidoc.json @@ -0,0 +1,19 @@ +{ + "name": "ember-tabular", + "description": "The default blueprint for ember-cli addons.", + "version": "0.2.0", + "options": { + "paths": [ + "addon" + ], + "enabledEnvironments": ["development", "production"], + "exclude": "vendor", + "outdir": "docs", + "linkNatives": true, + "quiet": true, + "parseOnly": false, + "lint": false, + "themedir" : "node_modules/yuidoc-bootstrap-theme", + "helpers" : [ "node_modules/yuidoc-bootstrap-theme/helpers/helpers.js" ] + } +} \ No newline at end of file From e23084aa692a45ae4eb35a91033642e186c00282 Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Fri, 26 May 2017 00:27:41 -0400 Subject: [PATCH 4/8] Create yuidocs for subcomponents --- addon/components/ember-tabular-alert.js | 20 +++++++ addon/components/ember-tabular-date-filter.js | 15 +++++ .../ember-tabular-dropdown-filter.js | 47 ++++++++++++++++ addon/components/ember-tabular-filter.js | 46 ++++++++++++++- .../components/ember-tabular-global-filter.js | 56 +++++++++++++++++++ 5 files changed, 183 insertions(+), 1 deletion(-) diff --git a/addon/components/ember-tabular-alert.js b/addon/components/ember-tabular-alert.js index 25b3be3..9f70d56 100644 --- a/addon/components/ember-tabular-alert.js +++ b/addon/components/ember-tabular-alert.js @@ -1,8 +1,28 @@ import Ember from 'ember'; +/** +* Any errors returned from the request(s) are displayed in an alert box. +* +* @class EmberTabularAlert +*/ export default Ember.Component.extend({ + /** + * @property tagName + * @type String + * @default 'div' + */ tagName: 'div', + /** + * @property type + * @type String + * @default 'info' + */ type: 'info', + /** + * @property typeClass + * @type String + * @default 'alert-[type]' + */ typeClass: Ember.computed('type', function () { return `alert-${this.get('type')}`; }), diff --git a/addon/components/ember-tabular-date-filter.js b/addon/components/ember-tabular-date-filter.js index 82aded6..49a57eb 100644 --- a/addon/components/ember-tabular-date-filter.js +++ b/addon/components/ember-tabular-date-filter.js @@ -1,3 +1,18 @@ import EmberTabularGlobalFilter from './ember-tabular-global-filter'; +/** +* ## Date Filter +* 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` +```hbs +{{ember-tabular-date-filter + filter=filter + filterProperty="updatedAt" + label="Last Updated"}} +``` +* @class EmberTabularDateFilter +*/ export default EmberTabularGlobalFilter.extend(); diff --git a/addon/components/ember-tabular-dropdown-filter.js b/addon/components/ember-tabular-dropdown-filter.js index 82aded6..de7bf7a 100644 --- a/addon/components/ember-tabular-dropdown-filter.js +++ b/addon/components/ember-tabular-dropdown-filter.js @@ -1,3 +1,50 @@ import EmberTabularGlobalFilter from './ember-tabular-global-filter'; +/** +* ## Dropdown Filter +* Use the dropdown filter globally. One way to do this is by setting up a computed property that returns an array of label/value objects. +```js +export default Ember.Controller.extend({ + users: null, + actions: { + setIsAdminFilter(object) { + if (object) { + this.set('isAdminFilter', object.value); + } else { + this.set('isAdminFilter', null); + } + }, + }, + adminContent: Ember.computed(function() { + return [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + } + ]; + }), +}); +``` +```hbs +{{#ember-tabular-dropdown-filter filter=filter filterProperty="isAdmin" label="Is Admin" searchFilter=isAdminFilter}} + {{#power-select + options=adminContent + selected=(find-by adminContent 'value' isAdminFilter) + searchField="label" + searchEnabled=false + placeholder="Select to filter" + onchange=(action "setIsAdminFilter") + as |option|}} + {{option.label}} + {{/power-select}} +{{/ember-tabular-dropdown-filter}} +``` +* +* @class EmberTabularDropdownFilter +* @extends EmberTabularGlobalFilter +*/ export default EmberTabularGlobalFilter.extend(); diff --git a/addon/components/ember-tabular-filter.js b/addon/components/ember-tabular-filter.js index 718273a..f34dcbb 100644 --- a/addon/components/ember-tabular-filter.js +++ b/addon/components/ember-tabular-filter.js @@ -1,11 +1,42 @@ import Ember from 'ember'; +/** +* Filtering on a column by column basis within the component's `
`. +* +* @class EmberTabularFilter +*/ export default Ember.Component.extend({ + /** + * @property tagName + * @type String + * @default 'th' + */ tagName: 'th', action: null, + /** + * Value of filter. + * + * @property headerFilter + * @type String + * @default '' + */ headerFilter: '', + /** + * 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 the component will attempt to grab within the context of the parent component. + * + * @property query + * @type Object + * @default null + */ query: null, + /** + * Must expose the `filter` property on the parent ember-tabular component to be able to pass the filter object back and forth between parent and child components. + * + * @property filter + * @type Object + * @default null + */ filter: null, actions: { @@ -35,10 +66,18 @@ export default Ember.Component.extend({ // to avoid multiple requests for properties that are set on init this.addObserver('headerFilter', this.filterBy); }), - // observable property is set during init + /** + * Debounce the `filterName` method. + * observable property is set during init + * + * @method filterBy + */ filterBy: Ember.observer(function () { Ember.run.debounce(this, 'filterName', 750); }), + /** + * @property isClearable + */ isClearable: Ember.computed('headerFilter', function () { if (this.get('headerFilter')) { return true; @@ -52,6 +91,11 @@ export default Ember.Component.extend({ return 'YYYY-MM-DD'; } }), + /** + * Constructs and sets the `filter` Object. + * + * @method filterName + */ filterName() { const query = this.get('query'); const property = this.get('property'); diff --git a/addon/components/ember-tabular-global-filter.js b/addon/components/ember-tabular-global-filter.js index bcc5371..975805e 100644 --- a/addon/components/ember-tabular-global-filter.js +++ b/addon/components/ember-tabular-global-filter.js @@ -1,12 +1,54 @@ import Ember from 'ember'; +/** +* ## Global Filter +* 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` +```hbs +{{ember-tabular-global-filter + filter=filter + filterProperty="username" + filterPlaceholder="Search by Username"}} +``` +* +* @class EmberTabularGlobalFilter +*/ export default Ember.Component.extend({ + /** + * @property tagName + * @type String + * @default 'div' + */ tagName: 'div', classNames: ['table-filter'], action: null, + /** + * Property to be filtered upon. + * + * @property filterProperty + * @type String + * @default null + */ filterProperty: null, + /** + * Value of filter. + * + * @property searchFilter + * @type String + * @default '' + */ searchFilter: '', + /** + * 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 the component will attempt to grab within the context of the parent component. + * + * @property query + * @type Object + * @default null + */ query: null, filter: null, @@ -15,15 +57,29 @@ export default Ember.Component.extend({ this.set('searchFilter', ''); }, }, + /** + * Debounce the `filterName` method. + * + * @method filterTable + */ filterTable: Ember.observer('searchFilter', function () { Ember.run.debounce(this, 'filterName', 750); }), + /** + * @property isClearable + * @default false + */ isClearable: Ember.computed('searchFilter', function () { if (this.get('searchFilter')) { return true; } return false; }), + /** + * Constructs and sets the `filter` Object. + * + * @method filterName + */ filterName() { // Reference parent component query obj const query = this.get('query') || this.get('parentView.query'); From facd9bf0e77ebaf7456423b0562f896b27f55a34 Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Fri, 26 May 2017 00:40:48 -0400 Subject: [PATCH 5/8] Add yuidocs for main component --- addon/components/ember-tabular.js | 489 +++++++++++++++++++++++++++++- 1 file changed, 484 insertions(+), 5 deletions(-) diff --git a/addon/components/ember-tabular.js b/addon/components/ember-tabular.js index 0e46c05..aca6671 100644 --- a/addon/components/ember-tabular.js +++ b/addon/components/ember-tabular.js @@ -1,15 +1,129 @@ import Ember from 'ember'; +/** +* ## Basic Usage +* - `columns` - Controller array to setup the table headers/columns (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. +* ### Template + ```hbs + {{! app/templates/my-route.hbs }} + + {{#ember-tabular columns=columns modelName="user" record=users as |section|}} + {{#if section.isBody}} + {{#each users as |row|}} + + + + + + + + {{/each}} + {{/if}} + {{/ember-tabular}} + ``` +* ### Controller +* Setup the columns array, which is how the table headers are constructed, `label` is required in all cases. + ```js + // 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', + }, + ], + }); + ``` +* +* @class EmberTabular +*/ export default Ember.Component.extend({ store: Ember.inject.service('store'), action: null, classNames: ['ember-tabular'], + /** + * Component will attempt to make a request to fetch the data. + * + * @property makeRequest + * @type Boolean + * @default true + */ makeRequest: true, + /** + * Used to toggle the filter row bar. + * + * @property showFilterRow + * @type Boolean + * @default false + */ showFilterRow: false, // requires sharing the `filter`/`sort` property with the controller/service/etc to persist persistFiltering: false, + /** + * @property sortableClass + * @type String + * @default 'sortable' + */ sortableClass: 'sortable', + /** + * @property tableWrapperClass + * @type String + * @default '' + */ + tableWrapperClass: '', + /** + * @property tableClass + * @type String + * @default 'table-bordered table-hover' + */ + tableClass: 'table-bordered table-hover', + /** + * @property paginationWrapperClass + * @type String + * @default '' + */ + paginationWrapperClass: '', + /** + * Once the `isRecordLoaded` is determined if true and no data exists then this is displayed. + * + * @property tableLoadedMessage + * @type String + * @default 'No Data.' + */ tableLoadedMessage: 'No Data.', + /** + * Computed Property to determine the column length dependent upon `columns`. + * + * @property columnLength + * @param columns {Array} + * @return {Number} + */ columnLength: Ember.computed('columns', function () { return this.get('columns').length; }), @@ -25,40 +139,220 @@ export default Ember.Component.extend({ isFooter: true, }, - // Model to be requested + /** + * Model to be requested using `makeRequest: true`. + * + * @property modelName + * @type String + * @default null + */ modelName: null, - // Bind variable for table data + /** + * This is typically bound to the controller and is used to iterate over the table's model data. + * + * @property record + * @type Object + * @default null + */ record: null, + /** + * This is typically setup on the controller and passed into the component, and is used to construct the table headers/filtering. + * + ```js + export default Ember.Controller.extend({ + users: null, + columns: [ + { + property: 'username', + label: 'Username', + defaultSort: 'username', + }, + { + property: 'emailAddress', + label: 'Email', + }, + { + property: 'firstName', + label: 'First Name', + sort: false, + }, + { + property: 'isAdmin', + label: 'Is Admin', + list: [ + { + label: 'Yes', + value: true, + }, + { + label: 'No', + value: false, + } + ], + }, + { + property: 'updatedAt', + label: 'Last Updated', + type: 'date', + }, + { + label: 'Actions', + }, + ], + }); + ``` + * + - `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 `` + - `columns.sort` - {Boolean} - Default: `true` + - Required for column sorting + - `columns.list` - {Array} - Default: `null` - Filtering the column based on a dropdown list. + - `list.label` - Displayed to the user for selection. + - `list.value` - Value that is sent in the request. + - `columns.defaultSort` - {String} + - Initial sort value for API request + - Will be overridden with any sorting changes + * + * @property columns + * @type Array + * @default null + */ columns: null, isDropdownLimit: true, // pagination defaults + /** + * @property page + * @type Number + * @default 1 + */ page: 1, + /** + * Used in request to construct pagination. + * + * @property limit + * @type Number + * @default 10 + */ limit: 10, + /** + * Number passed to the pagination add-on. + * + * @property pageLimit + * @type Number + * @default 0 + */ pageLimit: 0, + /** + * Used in request to construct pagination. + * + * @property offset + * @type Number + * @default 0 + */ offset: 0, + /** + * @property sort + * @type String + * @default null + */ sort: null, + /** + * @property filter + * @type Object + * @default null + */ filter: null, - // If additional static params are required in requests - // expects Object {} + /** + * Object to pass in static query-params that will not change based on any filter/sort criteria. + * Additional table-wide filters that need to be applied in all requests. Typically bound to the controller. + ```js + // app/controllers/location.js + + export default Ember.Controller.extend({ + staticParams: Ember.computed('model', function() { + return { + 'filter[is-open]': '1', + include: 'hours', + }; + }), + ... + }); + ``` + + ```hbs + {{! app/templates/my-route.hbs }} + + {{#ember-tabular columns=columns modelName="user" record=users staticParams=staticParams as |section|}} + ... + {{/ember-tabular}} + ``` + * + * @property staticParams + * @type Object + * @default null + */ staticParams: null, // State flags + /** + * @property isSuccess + * @type Boolean + * @default false + */ isSuccess: false, + /** + * @property isFailure + * @type Boolean + * @default false + */ isFailure: false, + /** + * @property isLoading + * @type Boolean + * @default false + */ isLoading: false, + /** + * @property defaultSuccessMessage + * @type String + * @default 'Success!' + */ defaultSuccessMessage: 'Success!', + /** + * @property defaultFailureMessage + * @type String + * @default 'There was an issue. Please check below for errors.' + */ defaultFailureMessage: 'There was an issue. Please check below for errors.', // Messages successMessage: Ember.get(Ember.Component, 'defaultSuccessMessage'), failureMessage: Ember.get(Ember.Component, 'defaultFailureMessage'), - // For pushing any per field errors + /** + * Conforms to json:api spec: http://jsonapi.org/format/#errors + * + * @property errors + * @type Array + * @default null + */ errors: null, + /** + * Used to serialize the parameters within `request`. + * + * @method serialize + * @param params {Object} An object of query parameters. + * @return params {Object} The serialized query parameters. + */ serialize(params) { // Serialize Pagination params = this.serializePagination(params); @@ -70,6 +364,36 @@ export default Ember.Component.extend({ return params; }, + /** + * Transform params related to pagination into API expected format. + * Follows json:api spec by default: http://jsonapi.org/format/#fetching-pagination. + * + * `offset` => `?page[offset]`. + * + * `limit` => `?page[limit]`. + * + * If you are not using Ember Data then you can extend this addon's component and override a set of serialize and normalized methods: + ```js + 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; + }, + }); + ``` + * + * @method serializePagination + * @param params {Object} An object of query parameters. + * @return params {Object} The serialized pagination query parameters. + */ serializePagination(params) { // Override to set dynamic offset based on page and limit params.offset = (params.page * params.limit) - params.limit; @@ -87,6 +411,15 @@ export default Ember.Component.extend({ return params; }, + /** + * Transform params related to filtering into API expected format. + * Follows json:api spec by default: http://jsonapi.org/recommendations/#filtering. + * `?filter[lastName]` => `?filter[last-name]`. + * + * @method serializeFilter + * @param params {Object} An object of query parameters. + * @return params {Object} The serialized filter query parameters. + */ serializeFilter(params) { // serialize filter query params const filter = params.filter; @@ -107,13 +440,47 @@ export default Ember.Component.extend({ return params; }, + /** + * Transform params related to sorting into API expected format. + * Follows json:api spec by default: http://jsonapi.org/format/#fetching-sorting. + * `?sort=lastName` => `?sort=last-name`. + * + * @method serializeSort + * @param params {Object} An object of query parameters. + * @return params {Object} The serialized sort query parameters. + */ serializeSort(params) { params.sort = this.serializeProperty(params.sort); return params; }, + /** + * Follows json:api dasherized naming. + * `lastName` => `last-name`. + * + * If you are not supporting json:api's dasherized properties this can be extended to support other conventions: + ```js + 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; + }, + }); + ``` + * + * @method serializeProperty + * @param property {String} + * @return property {String} + */ serializeProperty(property) { if (property) { return Ember.String.dasherize(property); @@ -122,6 +489,14 @@ export default Ember.Component.extend({ return null; }, + /** + * Used to normalize query parameters returned from `request` to components expected format. + * + * @method normalize + * @param data {Object} Data object returned from request. + * @param params {Object} The returned object of query parameters. + * @return data {Object} + */ normalize(data, params) { // Normalize Pagination data = this.normalizePagination(data, params); @@ -133,6 +508,16 @@ export default Ember.Component.extend({ return data; }, + /** + * Used to normalize pagination related query parameters returned from `request` to components expected format. + * `?page[offset]` => `offset`. + * `?page[limit]` => `limit`. + * + * @method normalizePagination + * @param data {Object} Data object returned from request. + * @param params {Object} The returned object of query parameters. + * @return data {Object} + */ normalizePagination(data, params) { // pagination - return number of pages const pageLimit = Math.ceil(data.meta.total / params.page.limit); @@ -146,6 +531,15 @@ export default Ember.Component.extend({ return data; }, + /** + * Used to normalize filter related query parameters returned from `request` to components expected format. + * `?filter[last-name]` => `filter[lastName]`. + * `?filter[user.first-name]` => `filter[user.firstName]`. + * + * @method normalizeFilter + * @param query {Object} The returned object of query parameters. + * @return query {Object} + */ normalizeFilter(query) { // normalize filter[property-key] // into filter[propertyKey] @@ -176,10 +570,26 @@ export default Ember.Component.extend({ return query; }, + /** + * Used to normalize sort related query parameters returned from `request` to components expected format. + * Expects json:api by default. + * + * @method normalizeSort + * @param query {Object} The returned object of query parameters. + * @return query {Object} + */ normalizeSort(query) { return query; }, + /** + * Used to normalize properties to components expected format. + * By default this will camelize the property. + * + * @method normalizeProperty + * @param property {String} + * @return property {String} + */ normalizeProperty(property) { if (property) { return Ember.String.camelize(property); @@ -188,12 +598,23 @@ export default Ember.Component.extend({ return null; }, + /** + * @method segmentProperty + * @param property {String} + * @return segments {Array} + */ segmentProperty(property) { let segments = property.split('.'); return segments; }, + /** + * Determine if `record` is loaded using a number of different property checks. + * + * @property isRecordLoaded + * @type Function + */ isRecordLoaded: Ember.computed('errors', 'record', 'record.isFulfilled', 'record.isLoaded', 'modelName', function () { // If record array isLoaded but empty @@ -220,6 +641,14 @@ export default Ember.Component.extend({ return false; }), + /** + * Used in templates to determine if table header will allow filtering. + * + * @property isColumnFilters + * @type Boolean + * @return {Boolean} + * @default false + */ isColumnFilters: Ember.computed('columns', function () { const columns = this.get('columns'); @@ -232,6 +661,11 @@ export default Ember.Component.extend({ return false; }), + /** + * Runs on init to setup the table header default columns. + * + * @method setColumnDefaults + */ setColumnDefaults: Ember.on('init', function () { this.get('columns').map(function (column) { // if column does not have a sort property defined set to true @@ -249,6 +683,11 @@ export default Ember.Component.extend({ }); }), + /** + * Runs on init to set the default sort param. + * + * @method defaultSort + */ defaultSort: Ember.on('init', function () { const sort = this.get('sort'); if (!sort) { @@ -269,6 +708,13 @@ export default Ember.Component.extend({ } }, + /** + * Constructs the query object to be used in `request`. + * + * @property query + * @type Object + * @return {Object} + */ query: Ember.computed('page', 'limit', 'offset', 'sort', 'filter.@each.value', 'staticParams', function () { let query = {}; @@ -289,6 +735,13 @@ export default Ember.Component.extend({ return query; }), + /** + * Make request to API for data. + * + * @method request + * @param params {Object} Serialized query parameters. + * @param modelName {String} + */ request(params, modelName) { params = this.serialize(params); @@ -308,6 +761,11 @@ export default Ember.Component.extend({ ); }, + /** + * Sets the `record` after the `request` is resolved. + * + * @method setModel + */ setModel: Ember.on('init', Ember.observer('query', 'makeRequest', function () { Ember.run.once(this, function () { // If makeRequest is false do not make request and setModel @@ -332,6 +790,12 @@ export default Ember.Component.extend({ }, }, + /** + * Sets the active sort property. + * + * @method setSort + * @param sortProperty {String} + */ setSort: Ember.on('didInsertElement', function (sortProperty) { if (this.get('sort') || sortProperty) { let property; @@ -354,6 +818,12 @@ export default Ember.Component.extend({ } }), + /** + * Sets the proper classes on table headers when sorting. + * + * @method updateSortUI + * @param sortProperty {String} + */ updateSortUI: Ember.on('didInsertElement', function (sortProperty) { if (this.get('sort') || sortProperty) { const _this = this; @@ -385,6 +855,10 @@ export default Ember.Component.extend({ } }), + /** + * @method failure + * @param response {Object} + */ failure(response) { this.reset(); this.setProperties({ @@ -398,6 +872,11 @@ export default Ember.Component.extend({ } }, + /** + * Resets all state specific properties. + * + * @method reset + */ reset() { this.setProperties({ isLoading: false, From c533440294b6a958ab56836f0e2204618ff6aaf2 Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Fri, 26 May 2017 00:50:22 -0400 Subject: [PATCH 6/8] Add further docs --- .../ember-tabular-dropdown-limit.js | 24 ++++++++++++++++++- addon/components/ember-tabular.js | 10 ++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/addon/components/ember-tabular-dropdown-limit.js b/addon/components/ember-tabular-dropdown-limit.js index e69f03a..77b4df3 100644 --- a/addon/components/ember-tabular-dropdown-limit.js +++ b/addon/components/ember-tabular-dropdown-limit.js @@ -1,12 +1,34 @@ import Ember from 'ember'; +/** +* Sets up component for changing the table row count/limit. +* +* @class EmberTabularDropdownLimit +*/ export default Ember.Component.extend({ + /** + * @property tagName + * @type String + * @default 'div' + */ tagName: 'div', classNames: ['ember-tabular-dropdown-limit'], - // populates limit dropdown + /** + * @property limits + * @type Array + * @default [10, 25, 50, 100, 500] + */ limits: [10, 25, 50, 100, 500], + /** + * Computed Property to determine if the result set is large enough to display the dropdown limit component. + * + * @property autoHide + * @param record + * @param count + * @return Boolean + */ autoHide: Ember.computed('record', 'count', function() { let record = this.get('record'); let count = this.get('count'); diff --git a/addon/components/ember-tabular.js b/addon/components/ember-tabular.js index aca6671..26774f8 100644 --- a/addon/components/ember-tabular.js +++ b/addon/components/ember-tabular.js @@ -83,7 +83,13 @@ export default Ember.Component.extend({ * @default false */ showFilterRow: false, - // requires sharing the `filter`/`sort` property with the controller/service/etc to persist + /** + * Requires sharing the `filter`/`sort` property with the controller/service/etc to persist filter data + * + * @property persistFiltering + * @type Boolean + * @default false + */ persistFiltering: false, /** * @property sortableClass @@ -701,7 +707,7 @@ export default Ember.Component.extend({ willDestroy() { this._super(...arguments); - // clear any filters if we are persisting filtering + // clear any filters if we are not persisting filtering const persistFiltering = this.get('persistFiltering'); if (!persistFiltering) { this.set('filter', null); From 75a3e791ec1850491ce92f939f878a280f07be6c Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Thu, 5 Oct 2017 12:42:20 -0400 Subject: [PATCH 7/8] Fix improper reference of Ember.observer, should be plain method --- addon/components/ember-tabular-filter.js | 4 ++-- package.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/addon/components/ember-tabular-filter.js b/addon/components/ember-tabular-filter.js index f34dcbb..e27c11a 100644 --- a/addon/components/ember-tabular-filter.js +++ b/addon/components/ember-tabular-filter.js @@ -72,9 +72,9 @@ export default Ember.Component.extend({ * * @method filterBy */ - filterBy: Ember.observer(function () { + filterBy() { Ember.run.debounce(this, 'filterName', 750); - }), + }, /** * @property isClearable */ diff --git a/package.json b/package.json index ef57f85..0918982 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "ember-cli-test-loader": "^1.1.0", "ember-cli-uglify": "^1.2.0", "ember-cli-yuidoc": "0.8.8", + "yuidoc-bootstrap-theme": "1.0.6", "ember-data": "^2.10.0", "ember-disable-prototype-extensions": "^1.1.0", "ember-disable-proxy-controllers": "1.0.1", From 751c1e5edd5850564727babf5fa8b94189af379c Mon Sep 17 00:00:00 2001 From: Aaron Fischer Date: Thu, 19 Oct 2017 13:27:06 -0400 Subject: [PATCH 8/8] Remove font-awesome bower dependency --- bower.json | 1 - index.js | 19 ------------------- package.json | 14 ++++++++------ 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/bower.json b/bower.json index 58f6155..69cd78c 100644 --- a/bower.json +++ b/bower.json @@ -9,7 +9,6 @@ "lodash": "3.7.0", "Faker": "~3.0.0", "bootstrap": "3.3.5", - "font-awesome": "4.6.2", "DOM-shim": "Raynos/DOM-shim" }, "resolutions": { diff --git a/index.js b/index.js index eaa4865..0ca9f66 100644 --- a/index.js +++ b/index.js @@ -9,24 +9,5 @@ module.exports = { this._super.included.apply(this, arguments); app.import('vendor/app.css'); - app.import(app.bowerDirectory + '/font-awesome/css/font-awesome.css'); }, - postprocessTree: function( type, tree ) { - // extract font-awesome fonts and place into /fonts directory - var fonts = new Funnel( 'bower_components/font-awesome', { - srcDir: 'fonts', - destDir: '/fonts', - include: [ 'fontawesome-webfont.*' ] - }); - - return mergeTrees( - [ - tree, - fonts - ], - { - overwrite: true - } - ); - } }; diff --git a/package.json b/package.json index 0918982..a7e2404 100644 --- a/package.json +++ b/package.json @@ -38,16 +38,17 @@ "ember-cli-test-loader": "^1.1.0", "ember-cli-uglify": "^1.2.0", "ember-cli-yuidoc": "0.8.8", - "yuidoc-bootstrap-theme": "1.0.6", "ember-data": "^2.10.0", "ember-disable-prototype-extensions": "^1.1.0", "ember-disable-proxy-controllers": "1.0.1", "ember-export-application-global": "^1.0.5", + "ember-font-awesome": "4.0.0-alpha.13", "ember-load-initializers": "^0.5.1", "ember-moment": "7.3.0", "ember-power-select": "1.4.3", "ember-resolver": "^2.0.3", - "loader.js": "4.0.9" + "loader.js": "4.0.9", + "yuidoc-bootstrap-theme": "1.0.6" }, "keywords": [ "ember-addon", @@ -57,12 +58,13 @@ "json api" ], "dependencies": { + "broccoli-funnel": "^1.0.0", + "broccoli-merge-trees": "^0.2.3", "ember-cli-babel": "^5.1.7", - "pagination-pager": "2.4.1", - "ember-truth-helpers": "1.2.0", "ember-power-select": "1.4.3", - "broccoli-merge-trees": "^0.2.3", - "broccoli-funnel": "^1.0.0" + "ember-truth-helpers": "1.2.0", + "font-awesome": "^4.7.0", + "pagination-pager": "2.4.1" }, "ember-addon": { "configPath": "tests/dummy/config"
{{row.username}}{{row.emailAddress}}{{row.firstName}}{{row.lastName}} + {{#link-to "index" class="btn btn-xs" role="button"}} + Edit + {{/link-to}} +