From 988b91a50dada17df3f79ce8bc101d32dc9b2a91 Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 13 Mar 2025 15:57:18 +0100 Subject: [PATCH 01/45] add dummy application example An example dummy using rawDataTable. Uses basics sorting, filtering and pagination. Note: to avoid having to convert field names (firstName <-> first-name), capitalization is avoided for field names. Data is generated locally in the adapter (so it generates ember-data objects), but with a 300ms delay to simulate some waiting for a request. --- package.json | 1 + tests/dummy/app/adapters/person.js | 96 ++++++++++++++++++++++ tests/dummy/app/controllers/application.js | 79 +----------------- tests/dummy/app/models/person.js | 9 ++ tests/dummy/app/routes/application.js | 5 ++ tests/dummy/app/services/store.js | 11 +++ tests/dummy/app/templates/application.hbs | 78 ++++-------------- 7 files changed, 139 insertions(+), 140 deletions(-) create mode 100644 tests/dummy/app/adapters/person.js create mode 100644 tests/dummy/app/models/person.js create mode 100644 tests/dummy/app/routes/application.js create mode 100644 tests/dummy/app/services/store.js diff --git a/package.json b/package.json index e5f4664..529a4ae 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@glimmer/tracking": "^1.0.4", "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", + "ember-cached-decorator-polyfill": "^1.0.2", "ember-cli": "~3.28.5", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-inject-live-reload": "^2.1.0", diff --git a/tests/dummy/app/adapters/person.js b/tests/dummy/app/adapters/person.js new file mode 100644 index 0000000..1c8d608 --- /dev/null +++ b/tests/dummy/app/adapters/person.js @@ -0,0 +1,96 @@ +import Adapter from '@ember-data/adapter'; + +export default class LocalAdapter extends Adapter { + generatePeople(amount) { + const firstNames = [ + 'John', + 'Jane', + 'Alice', + 'Bob', + 'Charlie', + 'David', + 'Eve', + 'Frank', + 'Grace', + ]; + const lastNames = [ + 'Doe', + 'Smith', + 'Johnson', + 'Brown', + 'Williams', + 'Jones', + 'Miller', + 'Davis', + 'Garcia', + 'Martinez', + ]; + const list = []; + + for (let i = 0; i < amount; i++) { + const firstName = firstNames[i % firstNames.length]; + const lastName = lastNames[i % lastNames.length]; + const age = 20 + (i % 30); + const created = new Date(Date.now() - 1000000000 * i); + const modified = new Date(Date.now() - 1000000000 * i); + + list.push({ + id: i, + firstname: firstName, + lastname: lastName, + age: age, + created: created, + modified: modified, + }); + } + + return list; + } + + generatePaginationMeta(page, size, count) { + const pages = Math.floor(count / size); + return { + count: count, + pagination: { + first: { number: 0, size: size }, + prev: { number: Math.max(0, page - 1), size: size }, + next: { number: Math.min(page + 1, pages), size: size }, + last: { number: pages }, + }, + }; + } + + compareAny(a, b) { + return a > b ? 1 : a < b ? -1 : 0; + } + + query(store, type, query) { + let people = this.generatePeople(100); + if (query.sort) { + const [sortOrder, sortType] = query.sort.startsWith('-') + ? [-1, query.sort.slice(1)] + : [1, query.sort]; + people.sort( + (a, b) => sortOrder * this.compareAny(a[sortType], b[sortType]) + ); + } + if (query.filter) { + people = people.filter((p) => + `${p.firstname} ${p.lastname}` + .toLowerCase() + .includes(query.filter.toLowerCase().trim()) + ); + } + const count = people.length; + if (query.page) { + const start = query.page.number * query.page.size; + people = people.slice(start, start + query.page.size); + people.meta = this.generatePaginationMeta( + query.page.number, + query.page.size, + count + ); + } + return Promise.resolve(people); + } +} diff --git a/tests/dummy/app/controllers/application.js b/tests/dummy/app/controllers/application.js index 075e96d..afe24ce 100644 --- a/tests/dummy/app/controllers/application.js +++ b/tests/dummy/app/controllers/application.js @@ -1,78 +1,3 @@ -import EmberObject from '@ember/object'; -import ArrayProxy from '@ember/array/proxy'; -import Controller from '@ember/controller'; -import DefaultQueryParams from 'ember-data-table/mixins/default-query-params'; +import DataTableController from 'ember-data-table/controller'; -var ApplicationController = Controller.extend(DefaultQueryParams, { - model: ArrayProxy.create({ - content: [ - EmberObject.create({ - firstName: 'John', - lastName: 'Doe', - age: 20, - created: Date.now(), - modified: Date.now(), - }), - EmberObject.create({ - firstName: 'Jane', - lastName: 'Doe', - age: 25, - created: Date.now(), - modified: Date.now(), - }), - ], - meta: { - count: 63, - pagination: { - first: { - number: 0, - size: 5, - }, - prev: { - number: 1, - size: 5, - }, - self: { - number: 2, - size: 5, - }, - next: { - number: 3, - size: 5, - }, - last: { - number: 12, - size: 5, - }, - }, - }, - }), - page: 2, - size: 5, - sort: 'first-name', - actions: { - test(row) { - console.info( - 'Hi, you reached the test action for row: ' + JSON.stringify(row) - ); - }, - menuTest() { - console.info('Hi, you reached the general menu test action'); - }, - selectionTest(selection, datatable) { - datatable.clearSelection(); - console.info( - 'Hi, you reached the selection test action for selection: ' + - JSON.stringify(selection) - ); - selection.forEach(function (item) { - item.set('age', item.get('age') + 1); - }); - }, - clickRow(row) { - console.info('Custom row click action on item ' + JSON.stringify(row)); - }, - }, -}); - -export default ApplicationController; +export default class ApplicationController extends DataTableController {} diff --git a/tests/dummy/app/models/person.js b/tests/dummy/app/models/person.js new file mode 100644 index 0000000..1e26310 --- /dev/null +++ b/tests/dummy/app/models/person.js @@ -0,0 +1,9 @@ +import Model, { attr } from '@ember-data/model'; + +export default class PersonModel extends Model { + @attr('string') firstname; + @attr('string') lastname; + @attr('number') age; + @attr() created; + @attr() modified; +} diff --git a/tests/dummy/app/routes/application.js b/tests/dummy/app/routes/application.js new file mode 100644 index 0000000..357eefc --- /dev/null +++ b/tests/dummy/app/routes/application.js @@ -0,0 +1,5 @@ +import DataTableRoute from 'ember-data-table/route'; + +export default class ApplicationRoute extends DataTableRoute { + modelName = 'person'; +} diff --git a/tests/dummy/app/services/store.js b/tests/dummy/app/services/store.js new file mode 100644 index 0000000..cbb4b5c --- /dev/null +++ b/tests/dummy/app/services/store.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line ember/use-ember-data-rfc-395-imports +import Store from 'ember-data/store'; + +export default class StoreService extends Store { + async query(modelName, query, options) { + if (modelName === 'person') { + await new Promise(r => setTimeout(r, 300)); + } + return super.query(...arguments); + } +} diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index bf06226..4aee5a0 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -5,68 +5,20 @@

Ember Data Table demo

Simple table

Generated table header and body based on given fields
- {{data-table content=model fields="firstName lastName age created modified" sort=sort page=page size=size filter=filter}} - -

Semi-complex table

-
Customized table header and body
- - - - {{th-sortable field="firstName" currentSorting=sort label="First name"}} - {{th-sortable field="lastName" currentSorting=sort label="Last name"}} - Age - {{th-sortable field="created" currentSorting=sort label="Created"}} - Modified - - - {{row.firstName}} - {{row.lastName}} - {{row.age}} - {{row.created}} - {{row.modified}} - - - - -

Complex table

-
Customized table including an action menu on top
- - - - - - - - - - - - - {{th-sortable field="firstName" currentSorting=sort label="First name"}} - {{th-sortable field="lastName" currentSorting=sort label="Last name"}} - Age - {{th-sortable field="created" currentSorting=sort label="Created"}} - Modified - - - - - {{row.firstName}} - {{row.lastName}} - {{row.age}} - {{row.created}} - {{row.modified}} - - - - - -

Internal variables

- + + + From 36498d258a19462694092a4eb5259ecc771cec2b Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 13 Mar 2025 16:08:58 +0100 Subject: [PATCH 02/45] tutorial example: add specific info for working pagination - the `model` was the `@` - `@total={{this.total}}` had no meaning in the tutorial, as it was not set previously. Exact instructions to get working pagination depends on the backend implementation. Some users might not need to set `@total`. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ac7da2..9546edf 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,13 @@ These steps are the same for any Ember Data Table flavour, the following visuali ```hbs Date: Thu, 13 Mar 2025 16:13:25 +0100 Subject: [PATCH 03/45] fix `@hasMenu` config (renamed to @showMenu) `@hasMenu` did not have an implementation yet. Renamed to `@showMenu` to make a clear distinction what it does for the component it is used on. currently the functionality is in `raw-data-table`, as this is where the menu itself is build. --- README.md | 2 +- addon/components/raw-data-table.hbs | 62 +++++++++++++++-------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 9546edf..3b42676 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ How to show different things in Ember Data Table without explicitly pressing search. If a number is provided, this is the time in milliseconds before sending the request. If no number is supplied a default is used. -- `@hasMenu` :: If not truthy, the component will show the supplied +- `@showMenu` :: If false, the component will hide the supplied menu. This allows controlling whether the menu should be shown dynamically. The menu may contain actions which act on the current selection. diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 35deedc..e09f427 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -10,7 +10,7 @@ @sort={{@sort}} @filter={{@filter}} @meta={{@meta}} - @hasMenu={{@hasMenu}} + @showMenu={{@showMenu}} @enableSearch={{@enableSearch}} @enableSelection={{@enableSelection}} @noDataMessage={{@noDataMessage}} @@ -67,36 +67,38 @@ {{!-- END: search --}} {{!-- START: menu --}} - - {{#if (has-block "menu")}} - {{yield (hash General Selected enableSelection) to="menu"}} - {{else}} -
- {{!-- either we have a general block or we have to have a menu --}} - - {{!-- TODO: shouldn't this be rendered when the result is empty too? Update docs! --}} - {{#if general.selectionIsEmpty}} - {{yield general to="general-menu"}} - {{/if}} - - {{#if enableSelection}} - - {{#unless selected.selectionIsEmpty}} - {{#if (has-block "selection-menu")}} - {{yield selected to="selection-menu"}} - {{else}} - {{#if (has-block "selection-menu-actions")}} - {{selected.selectionCount}} item(s) selected - - {{yield selected to="selection-menu-actions"}} + {{#if (not-eq @showMenu false)}} + + {{#if (has-block "menu")}} + {{yield (hash General Selected enableSelection) to="menu"}} + {{else}} +
+ {{!-- either we have a general block or we have to have a menu --}} + + {{!-- TODO: shouldn't this be rendered when the result is empty too? Update docs! --}} + {{#if general.selectionIsEmpty}} + {{yield general to="general-menu"}} + {{/if}} + + {{#if enableSelection}} + + {{#unless selected.selectionIsEmpty}} + {{#if (has-block "selection-menu")}} + {{yield selected to="selection-menu"}} + {{else}} + {{#if (has-block "selection-menu-actions")}} + {{selected.selectionCount}} item(s) selected + + {{yield selected to="selection-menu-actions"}} + {{/if}} {{/if}} - {{/if}} - {{/unless}} - - {{/if}} -
- {{/if}} -
+ {{/unless}} +
+ {{/if}} +
+ {{/if}} +
+ {{/if}} {{!-- END: menu --}} {{!-- START: content --}} From a37efad6329bd7953bc28515042353c71b7270ab Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 13 Mar 2025 18:09:27 +0100 Subject: [PATCH 04/45] cleanup example route - fix eslint error - transition itself is a promise-like, so finally can be used directly. --- addon/route.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/addon/route.js b/addon/route.js index d670568..970aa45 100644 --- a/addon/route.js +++ b/addon/route.js @@ -35,12 +35,16 @@ export default class DataTableRoute extends Route { @action loading(transition) { + // eslint-disable-next-line ember/no-controller-access-in-routes let controller = this.controllerFor(this.routeName); - controller.isLoadingModel = true; - transition.promise.finally(function () { - controller.isLoadingModel = false; - }); + if(controller) { + controller.isLoadingModel = true; + + transition.finally(function () { + controller.isLoadingModel = false; + }); + } return true; // bubble the loading event } From 46e05713476f70608083382fcec4b3a518a404b3 Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 13 Mar 2025 18:10:38 +0100 Subject: [PATCH 05/45] cleanup controller.js: remove unused functions from view --- addon/controller.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/addon/controller.js b/addon/controller.js index a8063f4..ca0c3db 100644 --- a/addon/controller.js +++ b/addon/controller.js @@ -17,10 +17,6 @@ export default class DataTableController extends Controller { filter: this.filter, sort: this.sort, isLoading: this.isLoadingModel, - updatePage: (page) => this.page = page, - updatePageSize: (size) => this.size = size, - updateFilter: (filter) => this.filter = filter, - updateSort: (sort) => this.sort = sort } } } From a0068a1759a777ea41377aa2e187a1dfe24d54b2 Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 13 Mar 2025 18:11:48 +0100 Subject: [PATCH 06/45] specify possibility to pass pagination info in `@meta.pagination` --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b42676..cf3ded3 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,8 @@ The passing of data from route and controller, and moving data back up. - `@meta` :: Meta may be provided in `@content.meta` or it may be provided in a separate property. If supplied, it may be used to determine the back-end pagination offset from - `@meta.links.first.number` (often `0` but sometimes `1`) and + `@meta.links.first.number` (often `0` but sometimes `1`), pagination in + `@meta.pagination` and amount of results as alternative to `@total` from `@meta.count`. From 442e6f25ac8bf52b39b88eea5ef442497be145df Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 13 Mar 2025 18:44:54 +0100 Subject: [PATCH 07/45] remove code for `@searchDebounceTime` + cleanup of debounce logic - the debounceTime can be passed using `@autoSearch`. `@searchDebounceTime` is a parameter that does not work anymore (not wired and readme does not mention it). Having one parameter is easier to understand too. - if `@autoSearch` is set explicitly to false, do not autoSearch, even if `handleAutoInput` was used. This is a case of bad configuration, so might need to update the code so it throws a warning in this case... --- README.md | 4 ++-- addon/components/data-table.js | 8 +++----- addon/components/data-table/text-search.js | 4 +++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cf3ded3..d570d4a 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,8 @@ How to show different things in Ember Data Table - `@autoSearch` :: If truthy, search is automatically triggered without explicitly pressing search. If a number is provided, this is - the time in milliseconds before sending the request. If no number is - supplied a default is used. + the time in milliseconds to wait for input before sending the request (input douncing). + If no number is supplied a default is used. - `@showMenu` :: If false, the component will hide the supplied menu. This allows controlling whether the menu should be shown dynamically. The menu may contain actions which act on the current diff --git a/addon/components/data-table.js b/addon/components/data-table.js index ea157d3..d8ffee2 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -5,6 +5,7 @@ import { typeOf } from '@ember/utils'; import { toComponentSpecifications, splitDefinitions } from "../utils/string-specification-helpers"; import attributeToSortParams from "../utils/attribute-to-sort-params"; +const DEFAULT_DEBOUNCE_TIME = 2000; export default class DataTable extends Component { @tracked _selection = undefined; @@ -48,16 +49,13 @@ export default class DataTable extends Component { /** * Calculates the search debounce time. * - * If the user supplies searchDebounceTime, that is what we should - * use. A shorthand form is supported in which the user supplies a + * A shorthand form is supported in which the user supplies a * number to autoSearch in which case we use that. This would not * work with 0 (which is a strange debounce time in itself) so this * option exists for now. */ get searchDebounceTime() { - return this.args.searchDebounceTime === undefined - ? isNaN(this.args.autoSearch) ? 2000 : this.args.autoSearch - : this.args.searchDebounceTime; + return isNaN(this.args.autoSearch) ? DEFAULT_DEBOUNCE_TIME : this.args.autoSearch } get enableSelection() { diff --git a/addon/components/data-table/text-search.js b/addon/components/data-table/text-search.js index 8000fa0..778a488 100644 --- a/addon/components/data-table/text-search.js +++ b/addon/components/data-table/text-search.js @@ -10,7 +10,9 @@ export default class TextSearchComponent extends Component { @action handleAutoInput(event) { this.enteredValue = event.target.value; - this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); + if(this.args.autoSearch !== false) { + this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); + } } submitCurrent() { From 5249bf14bb5bcb36c1898d668cda22b3911beac8 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 14 Mar 2025 16:04:33 +0100 Subject: [PATCH 08/45] add `@initialSelection` to set up the initial selection in a table This gives the possibility to already select some items by default (when loading the table). However, it does not allow to change the selection after the user has already done any selection. So that feature is still missing and could replace this feature entirely. --- README.md | 1 + addon/components/data-table.js | 4 ++-- addon/components/raw-data-table.hbs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d570d4a..320e55e 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ How to show different things in Ember Data Table - `@enableSelection` :: Whether items should be selectable. Items are selectable across pages and may be acted on using the `:selection-menu-actions` or `:selection-menu` named blocks. +- `@initialSelection` :: The selection to use as long as the user has not changed the selection yet. - `@linksModelProperty` :: When a link is clicked the row must supply information to the link to indicate which item was clicked. By default the `id` property is used but another attribute may be diff --git a/addon/components/data-table.js b/addon/components/data-table.js index d8ffee2..6041b3e 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -22,12 +22,12 @@ export default class DataTable extends Component { } get selection() { - if (this._selection === undefined && this.args.selection === undefined) + if (this._selection === undefined && this.args.initialSelection === undefined) return []; else if (this._selection !== undefined) return this._selection; else - return this.args.selection; + return this.args.initialSelection; } set selection(newSelection) { diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index e09f427..6968fd5 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -13,6 +13,7 @@ @showMenu={{@showMenu}} @enableSearch={{@enableSearch}} @enableSelection={{@enableSelection}} + @initialSelection={{@initialSelection}} @noDataMessage={{@noDataMessage}} @isLoading={{@isLoading}} @enableLineNumbers={{@enableLineNumbers}} From 968a4e92f70cc63ae6f515dc4bce85b86ab2ba85 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 14:40:13 +0100 Subject: [PATCH 09/45] clarify `_` converted to space syntax for `@links` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 320e55e..20df638 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ How to show different things in Ember Data Table - `@links` :: Each row may contain a number of links. Different links are split by a space in the configuration. Each link consists of one to three parts split by a colon. The first part is the route, the - second is the label, the third is an icon to use instead of the label + second is the label (`_` are rendered as spaces), the third is an icon to use instead of the label if supported (screen readers should see the label still). E.g.: `@links="products.edit:edit:pencil products.show:open:file-earmark-richtext"`. Note that only the route is required in which case the label is From 75f31d4bd678c501a4cb9c1f2ae15671b4071807 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 14:44:23 +0100 Subject: [PATCH 10/45] add if/else for handling icon-links in example raw-data-table, making it more obvious this should be supported The readme mentions that icons are supported if the icon name is passed, but nothing is actually done with the data. An example if/else is set for raw-data-table, so the user has an obvious place to change if they want to support this. Doing this differently (e.g. just a comment explaining what to add to support icons) might be better. --- addon/components/raw-data-table.hbs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 6968fd5..072c699 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -241,9 +241,17 @@ {{#if row.linkedRoutes}} {{#each row.linkedRoutes as |linkedRoute|}} - - {{or linkedRoute.label linkedRoute.route}} - + {{#if linkedRoute.icon}} + {{!-- NOTE: Change if icons should be supported, use rawIcon if icon name contain _ --}} + {{!-- NOTE: keep the label intact for screenreaders --}} + + {{or linkedRoute.label linkedRoute.route}} + + {{else}} + + {{or linkedRoute.label linkedRoute.route}} + + {{/if}} {{/each}} {{/if}} From 61ce7c3635e633d210e2bf70c1910a1765991bd8 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 14:44:38 +0100 Subject: [PATCH 11/45] fix typo in documentation of splitDefinitions --- addon/utils/string-specification-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/utils/string-specification-helpers.js b/addon/utils/string-specification-helpers.js index 0818b45..913b5f9 100644 --- a/addon/utils/string-specification-helpers.js +++ b/addon/utils/string-specification-helpers.js @@ -1,7 +1,7 @@ import { upperFirst } from "lodash"; /** - * Splits a string of defitinions by space. + * Splits a string of definitions by space. */ export function splitDefinitions(string) { return (string || "") From 2d96bdfdcaf45d423809d4974e8eb78f798c3087 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 14:45:42 +0100 Subject: [PATCH 12/45] move `@rowLink` above `@onClickRow` so the text referring to each other makes more sense --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 20df638..3ef203c 100644 --- a/README.md +++ b/README.md @@ -277,13 +277,13 @@ How to show different things in Ember Data Table Ember Data Table (and used in the sort hash to the back-end). More options than `'asc'` and `'desc'` can be provided if the back-end understands different sorting strategies. -- `@onClickRow` :: Action to be triggered when the row is clicked. This - is an alternative for the row link but it triggers an action rather - than following a route. - `@rowLink` :: Link to be used when users click on the full row. This is an easier click target for users than an icon on the side. Ideally - the that target is provided too. `@onClickRow` may be provided to + that target is provided too. `@onClickRow` may be provided to call a function instead but this is less accessible. +- `@onClickRow` :: Action to be triggered when the row is clicked. This + is an alternative for the row link but it triggers an action rather + than following a route. - `@rowLinkModelProperty` :: When `@rowLink` is used, the `id` property of the model rendered in the row will be supplied to the link. The property may be overridden by this property. Set to `uuid` when using From 67db96eab6790287ca7b1c922156c32b1a686080 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 15:06:37 +0100 Subject: [PATCH 13/45] Also allow passing an array to `@customHeaders`, `@customFields` and `@sortableFields` This allows greater flexibility, for example if the configuration is coming from code where saving it as an array might make more sense. This does not force the user to have to serialize their input to a string. --- README.md | 17 +++++++++-------- addon/components/data-table.js | 8 ++++---- addon/utils/string-specification-helpers.js | 11 +++++++++++ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3ef203c..ba8e77c 100644 --- a/README.md +++ b/README.md @@ -187,10 +187,8 @@ The passing of data from route and controller, and moving data back up. How to show different things in Ember Data Table - `@fields` :: List of fields to render with extra options. The fields are split by spaces. Splitting a field with a colon (`:`) makes the first element be the attribute and the second be the label. Use an `_` to render a space in the label. E.g.: `@fields="label:Name priceInEuros:Euro_price"`. -- `@sortableFields` :: List of fields by which the user may sort. - Fields should use the attribute names of `@fields` and are split by - spaces. By default all fields are sortable. Set to an empty list to - disable sorting. +- `@sortableFields` :: List or space-separated string of fields by which the user may sort. + Fields should use the attribute names of `@fields`. By default all fields are sortable. Set to an empty list or empty string to disable sorting. - `@noDataMessage` :: Custom message to show when no data is available. The `:no-data-message` block can be used as an alternative to provide styling. @@ -207,7 +205,8 @@ How to show different things in Ember Data Table derived and no icon is shown. The link by default receives the `id` of the item but this is configurable using the `@linksModelProperty` attribute (see below). -- `@customHeaders` :: List of attributes for which a custom header will +- `@customHeaders` :: List or space-separated string of attributes + for which a custom header will be rendered through the `:data-header` named block. Each of the attributes mentioned here won't render the default header but will instead dispatch to the named block. Check which attribute is being @@ -218,7 +217,8 @@ How to show different things in Ember Data Table ```hbs ...> <:data-header as |header|> {{#if (eq header.attribute "label")}} @@ -230,7 +230,7 @@ How to show different things in Ember Data Table ``` -- `@customFields` :: List of attributes for which the fields will +- `@customFields` :: List or space-separated string of attributes for which the fields will receive a custom rendering. This will render the individual cell values based on the `:data-cell` custom block. You may use the attribute name to verify which attribute the custom block is rendering @@ -239,7 +239,8 @@ How to show different things in Ember Data Table ```hbs ...> <:data-cell as |cell|> {{#if (eq cell.attribute "label")}} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 6041b3e..a5e99d1 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -2,7 +2,7 @@ import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import Component from '@glimmer/component'; import { typeOf } from '@ember/utils'; -import { toComponentSpecifications, splitDefinitions } from "../utils/string-specification-helpers"; +import { toComponentSpecifications, definitionsToArray } from "../utils/string-specification-helpers"; import attributeToSortParams from "../utils/attribute-to-sort-params"; const DEFAULT_DEBOUNCE_TIME = 2000; @@ -159,17 +159,17 @@ export default class DataTable extends Component { } get customHeaders() { - return splitDefinitions(this.args.customHeaders); + return definitionsToArray(this.args.customHeaders); } get customFields() { - return splitDefinitions(this.args.customFields); + return definitionsToArray(this.args.customFields); } get sortableFields() { const sortableFields = this.args.sortableFields; if (sortableFields || sortableFields === "") - return splitDefinitions(sortableFields); + return definitionsToArray(sortableFields); else // default: all fields are sortable return null; diff --git a/addon/utils/string-specification-helpers.js b/addon/utils/string-specification-helpers.js index 913b5f9..bbb23e6 100644 --- a/addon/utils/string-specification-helpers.js +++ b/addon/utils/string-specification-helpers.js @@ -9,6 +9,17 @@ export function splitDefinitions(string) { .filter((x) => x !== ""); } +/** + * Splits a string of definitions by space, or returns the array directly. + */ +export function definitionsToArray(stringOrArray) { + if(Array.isArray(stringOrArray)) { + return stringOrArray; + } else { + return splitDefinitions(stringOrArray); + } +} + /** * Transforms __ to _ and _ to space. */ From 4e8bf31e8986014f3a20ea6197d70d2bda98b1ce Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 16:07:13 +0100 Subject: [PATCH 14/45] specify in readme that `@fields` defines the order of fields in the table This is the only argument where the order is important, so it is nice to specify this explicitly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba8e77c..0fac9fb 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ The passing of data from route and controller, and moving data back up. How to show different things in Ember Data Table -- `@fields` :: List of fields to render with extra options. The fields are split by spaces. Splitting a field with a colon (`:`) makes the first element be the attribute and the second be the label. Use an `_` to render a space in the label. E.g.: `@fields="label:Name priceInEuros:Euro_price"`. +- `@fields` :: List of fields to render (in given order) with extra options. The fields are split by spaces. Splitting a field with a colon (`:`) makes the first element be the attribute and the second be the label. Use an `_` to render a space in the label. E.g.: `@fields="label:Name priceInEuros:Euro_price"`. - `@sortableFields` :: List or space-separated string of fields by which the user may sort. Fields should use the attribute names of `@fields`. By default all fields are sortable. Set to an empty list or empty string to disable sorting. - `@noDataMessage` :: Custom message to show when no data is available. From b39c7288d0fd0dbd3534aca57be9bf17b0182938 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 16:08:50 +0100 Subject: [PATCH 15/45] remove duplicate attribute hash in row.hbs `fields` was passed two times, which is undefined? as data-table/row passed @fields, it is safe to assume that `fields=@fields` is the correct pass. --- addon/components/data-table/data-cells.hbs | 1 - 1 file changed, 1 deletion(-) diff --git a/addon/components/data-table/data-cells.hbs b/addon/components/data-table/data-cells.hbs index a0bfebb..83f25bd 100644 --- a/addon/components/data-table/data-cells.hbs +++ b/addon/components/data-table/data-cells.hbs @@ -1,6 +1,5 @@ {{!-- Used in: data-table/row --}} {{yield (hash - fields=@dataTable.fields firstColumn=this.firstColumn otherColumns=this.otherColumns wrapper=@wrapper From 6501136c56a06e0f259ff6b6fad24f8eecb83010 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 16:14:50 +0100 Subject: [PATCH 16/45] Allow passing components directly as custom fields via `@customFieldComponents`. Uses the same logical flow for `@customFields`. Instead, `@customFields` could just accept both a string or an object. The component is provided in `fields` (and by way of having the same meaning, `firstColumn` and `otherColumns`). The user should never do something with this info, as the ember-data implementation will handle the rendering via the custom component. This avoids having to program if/else structures when having custom rendering for some attributes. Instead, the correct component can be passed. It is important to note that `` is not added and the customComponent should add this. This means you can't directly pass a specific component used in other parts of the app (e.g. formattedDate.hbs), as `` needs to be added. However, this problem is easily solved, without any extra files, by using the template syntax and passing ``. This way @customFieldComponents can use the same logic @customFields does (which also does not set `td` tag, but expects the passed block to use it). --- README.md | 13 +++++++++++++ addon/components/data-table.js | 8 +++++++- addon/components/raw-data-table.hbs | 9 +++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fac9fb..8560c84 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,15 @@ The `@customFields` property lists which fields which receive custom rendering. This configuration renders `label` as usual. `price` and `available` render through the named slot. Note that the order of the columns is still the order of `@fields`. +Alternatively the components to use for rendering can be passed directly via `@customFieldComponents`: +```hbs + +``` + ### Overwrite the header labels Supply column headers by adding extra properties to the fields attribute, split by a colon. A single `_` gets replaced by a space and two underscores get replaced by a single underscore @@ -251,6 +260,8 @@ How to show different things in Ember Data Table ``` +- `@customFieldComponents`: an object (hash) with key the attribute of the field to render via a custom component and + value the component to render. The component will receive in `@cell` the same object given to a `:data-cell` block. #### Ember Data Table functional configuration @@ -371,6 +382,8 @@ Various named blocks are offered, check your Ember Data Table design implementat named block) - `isCustom` :: whether the field rendering should be custom or not (meaning data cells should be rendered through `:data-cell`). + - `isCustomComponent` :: Whether the field rendering should use the `customComponent` to render. + - `customComponent` :: Available if the field rendering should use this custom component for rendering. - `dataHeadersInfo` :: information for the data headers. Supplied to `:data-headers` named block. - `ThSortable` :: Contextual component. When calling this component diff --git a/addon/components/data-table.js b/addon/components/data-table.js index a5e99d1..5821474 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -154,7 +154,9 @@ export default class DataTable extends Component { hasCustomHeader: hasCustomHeader || this.customHeaders.includes(attribute), isCustom: isCustom - || this.customFields.includes(attribute) + || this.customFields.includes(attribute), + isCustomComponent: (attribute in this.customFieldComponents), + customComponent: this.customFieldComponents[attribute] || null })); } @@ -166,6 +168,10 @@ export default class DataTable extends Component { return definitionsToArray(this.args.customFields); } + get customFieldComponents() { + return this.args.customFieldComponents || {}; + } + get sortableFields() { const sortableFields = this.args.sortableFields; if (sortableFields || sortableFields === "") diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 072c699..9725f69 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -28,6 +28,7 @@ @rowLinkModelProperty={{@rowLinkModelProperty}} @customHeaders={{@customHeaders}} @customFields={{@customFields}} + @customFieldComponents={{@customFieldComponents}} @sortableFields={{@sortableFields}} @attributeToSortParams={{@attributeToSortParams}} as |dt|> @@ -196,6 +197,10 @@ {{#if (has-block "first-data-cell")}} {{yield cell to="first-data-cell"}} + {{else if dataCells.firstColumn.isCustomComponent}} + {{#let dataCells.firstColumn.customComponent as |CustomComponent|}} + + {{/let}} {{else if cell.renderCustomBlock}} {{yield cell to="data-cell"}} {{else}} @@ -217,6 +222,10 @@ {{#if (has-block "rest-data-cell")}} {{yield cell to="rest-data-cell"}} + {{else if column.isCustomComponent}} + {{#let column.customComponent as |CustomComponent|}} + + {{/let}} {{else if cell.renderCustomBlock}} {{yield cell to="data-cell"}} {{else}} From 1d040042f1694dc930ebd7f3ed7b36aaea2a8839 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 19 Mar 2025 21:26:16 +0100 Subject: [PATCH 17/45] add `@selectionProperty` to support row selection for non-ember-data objects The default selection will compare based on the whole object (reference), which will not work for paginated results that are POJOs (e.g. mu-search). For this, a comparison should be made on the unique ID, like id for ember-data or uuid for mu-search. This will still work for ember-data objects when specifying correctly with "id", it is just not needed. avoids using emberArray's findBy, as this will be deprecated in ember 5. Should be a plain function helper after updating beyond ember 4.5 This is the only place ember-composable-helpers was used, which means this dependency can be removed. This package got deprecated too, so removing it is positive too. --- README.md | 1 + addon/components/data-table.hbs | 1 + addon/components/data-table.js | 9 ++++++--- .../data-table/data-table-content-body.hbs | 1 + .../components/data-table/data-table-content-body.js | 3 ++- addon/components/data-table/data-table-content.hbs | 1 + addon/components/data-table/row.hbs | 4 ++-- addon/components/raw-data-table.hbs | 1 + addon/helpers/includes-by.js | 12 ++++++++++++ addon/utils/get.js | 8 ++++++++ app/helpers/includes-by.js | 1 + package.json | 1 - 12 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 addon/helpers/includes-by.js create mode 100644 addon/utils/get.js create mode 100644 app/helpers/includes-by.js diff --git a/README.md b/README.md index 8560c84..ff1b8a8 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ How to show different things in Ember Data Table selectable across pages and may be acted on using the `:selection-menu-actions` or `:selection-menu` named blocks. - `@initialSelection` :: The selection to use as long as the user has not changed the selection yet. +- `@selectionProperty` :: By default equality will be checked by comparison of objects, which works for e.g. ember-data records. If a specific key is needed for comparison (e.g. uuid when using mu-search), this can be specified here by providing the path to the attribute. - `@linksModelProperty` :: When a link is clicked the row must supply information to the link to indicate which item was clicked. By default the `id` property is used but another attribute may be diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index c401c1e..09a85a9 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -12,6 +12,7 @@ content=@content noDataMessage=this.noDataMessage enableSelection=@enableSelection + selectionProperty=@selectionProperty enableLineNumbers=@enableLineNumbers onClickRow=@onClickRow sort=this.sort diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 5821474..1a912e7 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -4,6 +4,7 @@ import Component from '@glimmer/component'; import { typeOf } from '@ember/utils'; import { toComponentSpecifications, definitionsToArray } from "../utils/string-specification-helpers"; import attributeToSortParams from "../utils/attribute-to-sort-params"; +import get from '../utils/get'; const DEFAULT_DEBOUNCE_TIME = 2000; export default class DataTable extends Component { @@ -31,7 +32,7 @@ export default class DataTable extends Component { } set selection(newSelection) { - this._selection = newSelection; // also trigers dependent properties + this._selection = newSelection; // also triggers dependent properties } get noDataMessage() { @@ -242,11 +243,13 @@ export default class DataTable extends Component { @action addItemToSelection(item) { - this.selection = [...new Set([item, ...this.selection])]; + this.removeItemFromSelection(item); // in case the item was already selected + this.selection = [item, ...this.selection]; // create new array to trigger setter if `selection` } @action removeItemFromSelection(item) { - this.selection = this.selection.filter((x) => x !== item); + const byPath = this.args.selectionProperty; + this.selection = this.selection.filter((x) => get(x, byPath) !== get(item, byPath)); } @action clearSelection() { diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 5891161..a5a1ef0 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -19,6 +19,7 @@ dataTable=@dataTable enableLineNumbers=@enableLineNumbers enableSelection=@enableSelection + selectionProperty=@selectionProperty selection=@dataTable.selection offset=this.offset hasClickRowAction=(and (or @onClickRow @rowLink) true) diff --git a/addon/components/data-table/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js index 3b27459..9c65eef 100644 --- a/addon/components/data-table/data-table-content-body.js +++ b/addon/components/data-table/data-table-content-body.js @@ -3,6 +3,7 @@ import { get } from '@ember/object'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import Component from '@glimmer/component'; +import { includesBy } from '../../helpers/includes-by'; export default class DataTableContentBodyComponent extends Component { @service router; @@ -24,7 +25,7 @@ export default class DataTableContentBodyComponent extends Component { return content.map((item) => { return { item: item, - isSelected: selection.includes(item), + isSelected: includesBy(selection, item, this.args.selectionProperty), rowLink: this.args.rowLink, rowLinkModel: this.rowLinkModel(item) }; diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 38570f9..8a86e20 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -13,6 +13,7 @@ Body=(component "data-table/data-table-content-body" content=@content enableSelection=@enableSelection + selectionProperty=@selectionProperty enableLineNumbers=@enableLineNumbers noDataMessage=@noDataMessage onClickRow=@onClickRow diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index 60c2bc8..e1a94e0 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -8,8 +8,8 @@ enableSelection=@enableSelection hasClickRowAction=@hasClickRowAction onClickRow=(fn @onClickRow @wrapper.item) - isSelected=(includes @wrapper.item @selection) - selected=(includes @wrapper.item @selection) + isSelected=(includes-by @selection @wrapper.item @selectionProperty) + selected=(includes-by @selection @wrapper.item @selectionProperty) toggleSelected=(fn @toggleSelected @wrapper) linkedRoutes=this.linkedRoutes fields=@fields diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 9725f69..277cbaf 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -14,6 +14,7 @@ @enableSearch={{@enableSearch}} @enableSelection={{@enableSelection}} @initialSelection={{@initialSelection}} + @selectionProperty={{@selectionProperty}} @noDataMessage={{@noDataMessage}} @isLoading={{@isLoading}} @enableLineNumbers={{@enableLineNumbers}} diff --git a/addon/helpers/includes-by.js b/addon/helpers/includes-by.js new file mode 100644 index 0000000..8d160ef --- /dev/null +++ b/addon/helpers/includes-by.js @@ -0,0 +1,12 @@ +import { helper } from '@ember/component/helper'; +import get from '../utils/get'; + +// todo: change to normal function helper, instead of separate function, in ember 4 +export function includesBy(array, obj, byPath) { + const valueByPath = get(obj, byPath); + return !!array.find(a => get(a, byPath) === valueByPath); +} + +export default helper(function includesByHelper([array, obj, byPath]) { + return includesBy(array, obj, byPath); +}); diff --git a/addon/utils/get.js b/addon/utils/get.js new file mode 100644 index 0000000..05ea568 --- /dev/null +++ b/addon/utils/get.js @@ -0,0 +1,8 @@ +import { isEmpty } from '@ember/utils'; +import { get as emberGet } from '@ember/object'; + +// Same as the ember `get`, but returns the original object if the path does not exist. +export default function get(obj, path) { + if(isEmpty(path)) return obj; + return emberGet(obj, path); +} diff --git a/app/helpers/includes-by.js b/app/helpers/includes-by.js new file mode 100644 index 0000000..207bef7 --- /dev/null +++ b/app/helpers/includes-by.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/helpers/includes-by'; diff --git a/package.json b/package.json index 529a4ae..d6c4d3d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "ember-auto-import": "^2.0.0", "ember-cli-babel": "^7.26.10", "ember-cli-htmlbars": "^5.7.2", - "ember-composable-helpers": "^5.0.0", "ember-data": ">=3.28.0", "ember-math-helpers": "^2.18.0", "ember-truth-helpers": "^3.0.0", From 966deb5b2af4b1660784f2eb691052ebef421ccf Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 20 Mar 2025 16:02:09 +0100 Subject: [PATCH 18/45] specify `fields` item is an array, not object Before it was confusing if this was the field information for the current column, or an array of all fields information --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff1b8a8..7130254 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ Various named blocks are offered, check your Ember Data Table design implementat per the `@links` argument to DataTable). - `customHeaders` :: Headers which should be rendered in a custom way as an array or strings. - - `fields` :: A complex fields object containing the information about + - `fields` :: An array of complex fields object containing the information about each column to be rendered: - `attribute` :: the attribute to be rendered - `label` :: the label of the header From ff30b57e91db57099b8916e6ba5386f0a9f960ec Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 20 Mar 2025 16:03:17 +0100 Subject: [PATCH 19/45] rephrase isAscending/isDescending In code it explicitely checks for asc and desc, so these elements will not work if `@attributeToSortParams` is set with different sorting values. Now this is more explicit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7130254..fcd7401 100644 --- a/README.md +++ b/README.md @@ -416,8 +416,8 @@ Various named blocks are offered, check your Ember Data Table design implementat - `nextSort` :: Next way of sorting. This is clear for `["asc","desc",""]` but users may have provided other sorting methods through `@attributeToSortParams`. - - `isAscending` :: Are we sorting ascending now? - - `isDescending` :: Are we sorting descending now? + - `isAscending` :: Wether the current sorting is ascending (`'asc'`). + - `isDescending` :: Wether the current sorting is descending (`'desc'`). - `sortDirection` :: What's the key on which we're sorting now (e.g.: `"desc"`) - `renderCustomBlock` :: Should a custom block be rendered for this data header? - `isCustom` :: Is the header explicitly marked to render custom? From 674b56cf7014918612552c9caac558589b6d9294 Mon Sep 17 00:00:00 2001 From: Ruben Date: Wed, 26 Mar 2025 16:29:59 +0100 Subject: [PATCH 20/45] reword explanation and some terms in readme and code - add some more explicit explanation and more consistency of usage of terms in readme - rename some variables that were confusing - remove code for variables that are removed from the readme (`view`, `selected` and `enableSizes` which was not yet wired) - add variables where needed: `handleInput`, `sizes` (code existed, but was not wired) --- README.md | 298 +++++++++--------- addon/components/data-table.js | 55 +--- addon/components/data-table/data-cell.hbs | 4 +- addon/components/data-table/data-cells.hbs | 8 +- addon/components/data-table/data-cells.js | 4 +- .../components/data-table/data-table-menu.hbs | 4 +- .../data-table/number-pagination.hbs | 4 +- .../data-table/number-pagination.js | 6 +- addon/components/data-table/row.hbs | 2 - addon/components/data-table/text-search.hbs | 1 + addon/components/data-table/text-search.js | 6 + addon/components/raw-data-table.hbs | 38 +-- 12 files changed, 201 insertions(+), 229 deletions(-) diff --git a/README.md b/README.md index fcd7401..63bd3da 100644 --- a/README.md +++ b/README.md @@ -168,60 +168,61 @@ These arguments should be supported by specific design implementations too. The passing of data from route and controller, and moving data back up. -- `@content` :: Data to be rendered. In case this has a `meta` +- `@content` :: Data (an array of items) to be rendered. In case this has a `meta` property, this is used as default to derive amount of results and back-end pagination offset. -- `@page` and `@updatePage` :: Indicates the current page number and the +- `@page` and `@updatePage` :: Set the current page number and the function called to update the current page. -- `@size` and `@updatePageSize` :: Indicates the current page size and +- `@size` and `@updatePageSize` :: Set the current page size and the function called to update the current page size. -- `@sort` and `@updateSort` :: Returns current sorting for data table - and a function to update the sorting. -- `@filter` and `@updateFilter` :: Supplies the user filter string and - the function to call for updating that string. +- `@sort` and `@updateSort` :: Set the current sorting for data table + and the function called to update the sorting. +- `@filter` and `@updateFilter` :: Set the user search string and + the function called to update that string. - `@total` :: The total amount of results across all pages. If not set, `@meta.count` or `@content.meta.count` is tried. -- `@isLoading` :: Truthy if the Data Table is currently loading data. +- `@isLoading` :: Whether to show the Data Table in its loading state. - `@meta` :: Meta may be provided in `@content.meta` or it may be - provided in a separate property. If supplied, it may be used to + provided in this property. If supplied, it may be used to determine the back-end pagination offset from `@meta.links.first.number` (often `0` but sometimes `1`), pagination in `@meta.pagination` and - amount of results as alternative to `@total` from `@meta.count`. + amount of results from `@meta.count` as an alternative to `@total`. #### Ember Data Table visualization configuration How to show different things in Ember Data Table -- `@fields` :: List of fields to render (in given order) with extra options. The fields are split by spaces. Splitting a field with a colon (`:`) makes the first element be the attribute and the second be the label. Use an `_` to render a space in the label. E.g.: `@fields="label:Name priceInEuros:Euro_price"`. -- `@sortableFields` :: List or space-separated string of fields by which the user may sort. +- `@fields` :: Space-separated string of fields to render (in given order) with extra options. Splitting a field with a colon (`:`) makes the first element be the attribute and the second be the label. Use an `_` to render a space in the label. E.g.: `@fields="label:Name priceInEuros:Euro_price"`. +- `@sortableFields` :: Array or space-separated string of fields by which the user may sort. Fields should use the attribute names of `@fields`. By default all fields are sortable. Set to an empty list or empty string to disable sorting. - `@noDataMessage` :: Custom message to show when no data is available. The `:no-data-message` block can be used as an alternative to provide styling. - `@enableSearch` :: Set to false to disable search in the table. +- `@searchPlaceholder` :: Custom placeholder text for the search input box. Defaults to 'Search input'. - `@enableLineNumbers` :: Set to truthy to show line numbers in the table. -- `@links` :: Each row may contain a number of links. Different links - are split by a space in the configuration. Each link consists of one +- `@sizes` :: Array or space-separated string of page size choices that should be shown in the pagination block. Defaults to `[5, 10, 25, 50, 100]`. Set to an empty list or empty string to hide. +- `@links` :: A space-separated string of links with extra options. + Each row may contain a number of links. Each link consists of one to three parts split by a colon. The first part is the route, the second is the label (`_` are rendered as spaces), the third is an icon to use instead of the label if supported (screen readers should see the label still). E.g.: `@links="products.edit:edit:pencil products.show:open:file-earmark-richtext"`. Note that only the route is required in which case the label is - derived and no icon is shown. The link by default receives the `id` - of the item but this is configurable using the `@linksModelProperty` + derived and no icon is shown. By default the link receives the `id` + of the item, but is configurable using the `@linksModelProperty` attribute (see below). -- `@customHeaders` :: List or space-separated string of attributes +- `@customHeaders` :: Array or space-separated string of attributes for which a custom header will be rendered through the `:data-header` named block. Each of the attributes mentioned here won't render the default header but will instead dispatch to the named block. Check which attribute is being - rendered in the named block to render the right label. Verify in the - implementation you override which actions are set on the columns how - to support sorting if needed. + rendered in the named block to render the right label. Check in the + implementation you override how sorting is supported, if sorting is needed for this header. ```hbs ``` -- `@customFields` :: List or space-separated string of attributes for which the fields will +- `@customFields` :: Array or space-separated string of attributes for which the fields will receive a custom rendering. This will render the individual cell values based on the `:data-cell` custom block. You may use the attribute name to verify which attribute the custom block is rendering @@ -260,15 +261,15 @@ How to show different things in Ember Data Table ``` -- `@customFieldComponents`: an object (hash) with key the attribute of the field to render via a custom component and - value the component to render. The component will receive in `@cell` the same object given to a `:data-cell` block. +- `@customFieldComponents`: An object (hash) with key the attribute of the field and + value the component to use for rendering. The component will receive in `@cell` the same hash given to a `:data-cell` block (see below). #### Ember Data Table functional configuration - `@autoSearch` :: If truthy, search is automatically triggered without explicitly pressing search. If a number is provided, this is the time in milliseconds to wait for input before sending the request (input douncing). - If no number is supplied a default is used. + If no number is supplied a default of 2000ms is used. - `@showMenu` :: If false, the component will hide the supplied menu. This allows controlling whether the menu should be shown dynamically. The menu may contain actions which act on the current @@ -277,24 +278,23 @@ How to show different things in Ember Data Table selectable across pages and may be acted on using the `:selection-menu-actions` or `:selection-menu` named blocks. - `@initialSelection` :: The selection to use as long as the user has not changed the selection yet. -- `@selectionProperty` :: By default equality will be checked by comparison of objects, which works for e.g. ember-data records. If a specific key is needed for comparison (e.g. uuid when using mu-search), this can be specified here by providing the path to the attribute. +- `@selectionProperty` :: By default equality will be checked by direct comparison of objects, which works for e.g. ember-data records. If a specific property should be used for comparison (e.g. `uuid` when using mu-search), a property name or path can be supplied. - `@linksModelProperty` :: When a link is clicked the row must supply information to the link to indicate which item was clicked. By - default the `id` property is used but another attribute may be + default the `id` property is used, but another property name or path may be supplied if desired (such as `uuid` when using mu-search). An empty string will provide the full object. -- `@attributeToSortParams` :: Function which translates an attribute to - its sort parameters. The sort parameters is currently a hash which - contains a key (default `'asc'` and `'desc'` to indicate sorting up - and down) and the corresponding sort key which should be sent out of - Ember Data Table (and used in the sort hash to the back-end). More - options than `'asc'` and `'desc'` can be provided if the back-end - understands different sorting strategies. +- `@attributeToSortParams` :: Function which translates an attribute name to + its sort parameters. The sort parameters are a hash + with key the sort name to use in the table and value the corresponding sort key to sent out of Ember Data Table (and used in the sort hash to the back-end via `updateSort`). + By default, for input `attributeName`, it returns `{ 'asc': 'attribute-name', 'desc': '-attribute-name'}`. + More options can be provided if the back-end understands different sorting strategies. - `@rowLink` :: Link to be used when users click on the full row. This is an easier click target for users than an icon on the side. Ideally - that target is provided too. `@onClickRow` may be provided to + that target is provided too. `@onClickRow` may be provided to call a function instead but this is less accessible. -- `@onClickRow` :: Action to be triggered when the row is clicked. This +- `@onClickRow` :: Callback to be triggered when the row is clicked. + Receives the clicked item (from `@content`) as its first argument. This is an alternative for the row link but it triggers an action rather than following a route. - `@rowLinkModelProperty` :: When `@rowLink` is used, the `id` property @@ -305,39 +305,40 @@ How to show different things in Ember Data Table #### Overriding Ember Data Table parts using named blocks -Various named blocks are offered, check your Ember Data Table design implementation to see which part needs to be overridden. A list is provided here for reference. - -- `search` :: Overrides the full search component. Receives a search hash with properties: - - `filter` :: User's filter - - `placeholder` :: Placeholder for the text search - - `autoSearch` :: Value for autoSearch as supplied by the user - (subject to change) - - `submitForm` :: Action which can be used to submit the search form - and trigger search update - - `handleAutoInput` :: Action which can handle auto input by - debouncing and updating the search string - - `handleDirectInput` :: Action which handles the event where a user - types, gets value from `event.target.value`. - -- `menu` :: Overrides the full menu rendering. Receives three positional arguments: +Various named blocks are offered, check your Ember Data Table design implementation to see which part needs to be overridden. A list is provided here for reference as used in `raw-data-table.hbs`. + +- `:search` :: Overrides the full search block. Receives a hash containing: + - `filter` :: User's filter string + - `placeholder` :: Placeholder for the text search input + - `autoSearch` :: Value for autoSearch as supplied by the user (boolean or number). + - `submitSearch` :: Action which can be used to trigger a search string update (`@updateFilter`) + - `handleInput` :: Action which expects and event (with value in `event.target.value`) + and updates the search string immediately or after a debounce time, depending on `@autoSearch` value. + - `handleAutoInput` :: Like `handleInput`, but always uses a debounce time (even if `@autoSearch` is falsy). + - `handleDirectInput` :: Like `handleInput`, but always updates immediately (ignoring `@autoSearch` value). + + +- `:menu` :: Overrides the full menu block. Receives a hash containing: - `General` :: Component with information about the General menu which is rendered when nothing is selected. The block given to General - receives an argument which should be passed to `:general-menu`. + receives one block parameter which should be passed to `:general-menu`. + See `:general-menu` for the parameter details of `General`. - `Selected` :: Component with information on handling selected items. - The block given to Selected receives `selected` which should be - passed to `:selection-menu`. + The block given to Selected receives one block parameter which should be + passed to `:selection-menu`. + See `:selection-menu` for the parameter details of `Selected`. + - `enableSelection` :: Whether selection is enabled. -- `general-menu` :: Implements the menu with actions which is shown when - no items are selected. Receives a hash with two items: +- `:general-menu` :: Implements the menu with actions which is shown when + no items are selected. Receives a hash containing: - `dataTable` :: The main DataTable object on which actions can be called. - `selectionIsEmpty` :: Whether items are currently selected or not. -- `selection-menu` :: This menu is rendered only when items have been +- `:selection-menu` :: This menu is rendered only when items have been selected. It's the main wrapper which contains `:selection-menu-actions` (which you'd likely want to override - instead) as well as some visual information on the selected items. It - receives a hash with four elements: + instead) as well as some visual information on the selected items. Receives a hash containing: - `selectionIsEmpty` :: Whether the selection is currently empty. - `selectionCount` :: The amount of items which are selected at this point. - `clearSelection` :: An action to clear the whole selection. @@ -349,37 +350,35 @@ Various named blocks are offered, check your Ember Data Table design implementat Table (versus the template). Receives the same argument as `:selection-menu`. -- `content` :: This block is the full table but without search, actions - or pagination. It must render the table tag and everything in it. It - receives a hash with three elements. +- `:content` :: This block is the full table but without search, menu actions + or pagination. It must render the table tag and everything in it. + Receives a hash containing: - `Header` :: The Header logical component which contains information - to render the header row. Supplying a block to Header will yield - with the content for the `:header` named block. + to render the header row. Has the same block parameter hash as `:full-header` below. - `Body` :: The Body logical component which contains information to - render each of the body rows. Supplying a block to Body will yield - with the content for the `:body` named block. + render each of the body rows. Has the same block parameter hash as `:body` below. - `dataTable` :: The DataTable object. -- `full-header` :: This block should render the `` with the header row - inside of it. Receives a hash with the following items: - - `enableSelection` :: Whether or not selection is enabled. - - `enableLineNumbers` :: Whether or not line numbers are enabled. - - `sort` :: Sort parameter. - - `updateSort` :: Function to update sorting. +- `:full-header` :: This block should render the `` with the header row + inside of it. Receives a hash containing: + - `enableSelection` :: Whether selection is enabled. + - `enableLineNumbers` :: Whether line numbers are enabled. + - `sort` :: Current sort parameter. + - `updateSort` :: Function to update sorting (see `@updateSort`). - `hasLinks` :: Whether custom links are provided for this table (as per the `@links` argument to DataTable). - `customHeaders` :: Headers which should be rendered in a custom way - as an array or strings. + as an array of strings. - `fields` :: An array of complex fields object containing the information about - each column to be rendered: + each data column to be rendered: - `attribute` :: the attribute to be rendered - `label` :: the label of the header - `isSortable` :: whether this column is sortable or not - `sortParameters` :: hash which indicates in which ways this field - can be sorted (ascending, descending, something else). See + can be sorted (ascending, descending, something else). See output of `@attributeToSortParams`. - `hasCustomHeader` :: whether this column has a custom header or - not (meaning we should render it through the `:data-header` + not (meaning it should be rendered through the `:data-header` named block) - `isCustom` :: whether the field rendering should be custom or not (meaning data cells should be rendered through `:data-cell`). @@ -388,68 +387,64 @@ Various named blocks are offered, check your Ember Data Table design implementat - `dataHeadersInfo` :: information for the data headers. Supplied to `:data-headers` named block. - `ThSortable` :: Contextual component. When calling this component - `@field` must be supplied (to generate info for a given field when + `@field` must be supplied (to generate info for the specific field when looping over `header.fields`) and `@hasCustomBlock` which should - indicate whether a `:data-header` is given. Supplying a block to - ThSortable will yield with the content for the `:data-header` - named block. The aforementioned content also has a + indicate whether a `:data-header` block is given. Has the same block parameter hash as `:data-header` below. This block parameter contains `renderCustomBlock` which can be used to detect whether a custom block should be rendered for this block or not. -- `data-headers` :: This is inside the `` of the `` and +- `:data-headers` :: This is inside the `` of the `` and should render all headers for the attributes. Thus ignoring the - headers for selection, numbers and actions. It receives a hash - containing the following elements: - - `fields` :: The fields to be rendered (see `fields` above for all - the attributes). + headers for selection, numbers and actions. Receives a hash + containing: + - `fields` :: The fields to be rendered (same `fields` as `:full-header`). - `customHeaders` :: Headers which should be rendered in a custom way - as an array or strings. + as an array of strings. - `sort` :: Sort parameter. - - `updateSort` :: Function to update sorting. -- `data-header` :: Renders a custom header which should handle sorting - etc. Receives a hash with the following elements: + - `updateSort` :: Function to update sorting (see `@updateSort`). +- `:data-header` :: Renders a custom header for headers specified in `@customHeaders`, which should handle sorting etc. Receives a hash containing: - `label` :: Label of the header. - `attribute` :: Attribute which will be rendered in this column. - `isSortable` :: Whether this column is sortable or not. - `isSorted` :: Whether sorting is applied to this header or not. - `toggleSort` :: Action which switches to the next sorting method - (e.g.: from `'asc'` to `'desc'` or from `'desc'` to nothing). + (e.g.: from `'asc'` to `'desc'` or from `'desc'` to nothing by default). - `nextSort` :: Next way of sorting. This is clear for - `["asc","desc",""]` but users may have provided other sorting - methods through `@attributeToSortParams`. - - `isAscending` :: Wether the current sorting is ascending (`'asc'`). - - `isDescending` :: Wether the current sorting is descending (`'desc'`). - - `sortDirection` :: What's the key on which we're sorting now (e.g.: `"desc"`) - - `renderCustomBlock` :: Should a custom block be rendered for this data header? - - `isCustom` :: Is the header explicitly marked to render custom? - - `hasCustomHeaders` :: Are there any custom headers to be rendered? - -- `actions-header` :: Header which will contain all actions. Receives no arguments. - -- `body` :: Renders the full body of the table, including the `` + `['asc','desc','']` but users may have provided other sorting + methods through `@attributeToSortParams`. The order is always alphabetically. + - `isAscending` :: Whether the current sorting is ascending (`'asc'`). + - `isDescending` :: Whether the current sorting is descending (`'desc'`). + - `sortDirection` :: What's the key on which we're sorting now (e.g.: `'desc'`) + - `renderCustomBlock` :: Whether a custom block should be rendered for this data header. + - `isCustom` :: Truthy if the header is explicitly marked to render custom. + - `hasCustomHeaders` :: Truthy if there are any custom headers to be rendered. + +- `:actions-header` :: Header which will contain all actions. Receives no arguments. + +- `:body` :: This block renders the full body of the table, and should include the `` tag. Receives a hash containing: - - `isLoading` :: Is the data being loaded at this point? Probably - need to render `:body-loading` named block then. - - `content` :: The actual content of this Data Table. - - `offset` :: The index of the first element in this data table. + - `isLoading` :: Whether the data is being loaded. + Need to render `:body-loading` named block then. + - `content` :: The actual content of this Data Table (all items). + - `offset` :: The absolute index of the first element of the current page. - `wrappedItems` :: Rows of the data table in a way through which they can be selected. - `enableLineNumbers` :: Whether line numbers are enabled or not. - `hasClickRowAction` :: Whether something needs to happen when the row is clicked. Either because there is an `@onClickRow` or because there is a `@rowLink`. - - `onClickRow` :: Action to be called when user clicked on a row, if - supplied by user of this Data Table. + - `onClickRow` :: Function to be called when user clicked on a row, if + supplied by user of this Data Table. Should pass the clicked item. - `toggleSelected` :: Action which allows to toggle the selection - state of the current row. Should receive the an element from + state of the current row. Should receive the element from `wrappedItems` as first element and the event that caused it (will - check `event.target.fetched`) as second argument. + check `event.target.checked`) as second argument. - `selection` :: Currently selected items. - `enableSelection` :: Whether selection of items is enabled. - `linkedRoutes` :: Array of objects describing each of the routes which should be linked as custom links per row. Each item is a hash with the following elements: - `route` :: The route to which we should link. - - `label` :: The human-readable label for the route if supplied. + - `label` :: The human-readable label for the route, if supplied. - `icon` :: The icon which should be rendered for the link, if supplied. - `linksModelProperty` :: The property of the model which should be supplied to the route (e.g.: `id` for the id or `""` if the whole @@ -471,82 +466,73 @@ Various named blocks are offered, check your Ember Data Table design implementat regardless of the page, so you can use `{{#each body.wrappedItems as |wrapper index|}}...{{/each}}`. -- `body-loading` :: Renders a custom body loading message supplied in - this invocation of Ember Data Table. -- `row` :: Renders an individual row, including the `` tag. This is - the row with both the data elements as well as with the meta elements - such as selection of items and links. Receives a hash with the - following elements: +- `:body-loading` :: Block to show a custom loading message block. +- `:row` :: Renders an individual row, including the `` tag. This is + the row with both the data columns as well as the meta columns + such as selection of items and links. Receives a hash containing: - `wrapper` :: An object containing the item and the selection status. - `item` :: Actual item to be rendered in this row. - `enableLineNumbers` :: See above. - `lineNumber` :: See above. - `enableSelection` :: See above. - - `selected` :: Whether this row is selected or not. - - `isSelected` :: Whether this item is selected or not (same as - selected). + - `isSelected` :: Whether this item is selected or not. - `toggleSelected` :: See above. - `hasClickRowAction` :: See above. - - `onClickRow` :: See above. + - `onClickRow` :: See above, but already has the row item passed. - `linkedRoutes` :: A copy of `linkedRoutes` as mentioned above but adding the `model` key which contains the specific model to supply to the linked route for this row (e.g.: the `id`, `uuid` or the full `item`) - `fields` :: See above. - `DataCells` :: Contextual component which provides information for - rendering the data cells of a row. Supplying a block to DataCells - will yield a block which is used for rendering the `:dataCells` named - block. -- `data-cells` :: Renders all the cells containing real data in a row. - This includes selection of the row and links. Receives a hash with - the following elements: - - `fields` :: See above. - - `firstColumn` :: The field of the first column to be rendered. Good + rendering the data cells of a row. Has the same block parameter hash as `:data-cells` below. +- `:data-cells` :: Renders all the cells containing real data (fields) in a row. + This excludes cells for meta columns (like selection and links). Receives a hash containing: + - `fields` :: All fields to be rendered. See above. + - `firstColumnField` :: The field of the first column to be rendered. Good for designs where the first column should receive different styling. - - `otherColumns` :: The fields of all columns but the first one to be + - `otherColumnFields` :: The fields of all columns but the first one to be rendered. Good for designs where the first column should receive different styling. - `wrapper` :: See above. - `item` :: See above. - `rowLink` :: See above. - `rowLinkModel` :: Model to supply to the route specified by `rowLink` for this specific row. # =@wrapper.rowLinkModel - - `fields` :: See above. - `DataCell` :: Contextual component which provides information for rendering an individual cell. Should receive `@column` with the field to render and `@hasCustomBlock` with `{{has-block "data-cell"}}` so we know whether a custom block was provided for the `data-cell` named slot. -- `data-cell` :: Renders a custom data cell regardless of whether it's - first or any other. Receives a hash with the following elements: - - `firstColumn` :: See above. - - `otherColumns` :: See above. +- `:data-cell` :: Renders a custom data cell regardless of whether it's + first or any other. Receives a hash containing: + - `fields` :: See above. + - `firstColumnField` :: See above. + - `otherColumnFields` :: See above. - `item` :: See above. - `rowLink` :: See above. - `rowLinkModel` :: See above. - `label` :: See above. - - `fields` :: See above. - - `isCustom` :: Is the cell explicitly marked to render custom? - - `hasCustomFields` :: Whether there are custom fields to be - rendered. + - `isCustom` :: Wether this cell is explicitly marked to render custom. + - `hasCustomFields` :: Whether there are any cells that are marked to render custom. - `attribute` :: The attribute which will be rendered. - `renderCustomBlock` :: Whether a custom block should be rendered - for this field. This is the named slot `:data-cell`. - - `value` :: The value which should be rendered. -- `first-data-cell` :: In designs which care about the first data cell + for this field. This block is the named slot `:data-cell`. + - `value` :: The data value which should be rendered. +- `:first-data-cell` :: In designs which care about the first data cell versus the others, this will render a custom design for the first data - column of the table. Receives the same arguments as `data-cell`. -- `rest-data-cell` :: In designs which care about the first data cell + column of the table. Receives the same block parameter hash as `:data-cell`. +- `:rest-data-cell` :: In designs which care about the first data cell versus the others, this will render a custom design for the other data - columns of the table. Receives the same arguments as `data-cell`. -- `actions` :: Renders the links next to each row specified through - `@links`. Receives the same arguments as `row`. -- `no-data-message` :: Rendered when no data was available in the data + columns of the table. Receives the block parameter hash as `:data-cell`. +- `:actions` :: Renders the links next to each row specified through + `@links`. Receives the same arguments as `:row`. +- `:no-data-message` :: Rendered when no data was available in the data cell. When no styling is needed, `@noDataMessage` can be used instead. -- `pagination` :: Renders everything needed to handle pagination. - Receives a hash with the following elements: - - `startItem` :: Number of the first item rendered on this page. - - `endItem` :: Number of the last item rendered on this page. +- `:pagination` :: This block contains everything needed to handle pagination. + Receives a hash containing: + - `startIndex` :: Absolute index of the first item rendered on this page. + - `endIndex` :: Absolute index of the last item rendered on this page. - `total` :: Total amount of items on all pages of this table. - `hasTotal` :: Whether the total amount of items is known. - `pageSize` :: Amount of items per page (though the last page may have fewer items). @@ -563,7 +549,7 @@ Various named blocks are offered, check your Ember Data Table design implementat - followed by up to three pages after the current page number, - followed by 'more' if empty spots follow, - followed by the last page number. - - `sizeOptions` :: The different sizes (as an array) for pages of this Data Table. + - `sizeOptions` :: The different sizes (as an array of numbers) for pages of this Data Table. `null` if size should not be changeable (defined by `@sizes`). - `firstPage` :: The first page number in this Data Table. - `lastPage` :: The last page number in this Data Table. - `nextPage` :: The next page number in this view, `undefined` if this @@ -571,11 +557,11 @@ Various named blocks are offered, check your Ember Data Table design implementat - `previousPage` :: The previous page number in this view, `undefined` if this is the first page. - `updatePage` :: Function which takes a back-end page number and - updates it (this is the raw function supplied to `DataTable`. - - `humanPage` :: Thu current page in human form. - - `updateHumanPage` :: Updates the human page number (this will call + updates it (this is the raw function supplied to `DataTable`). + - `humanPage` :: The current page in human form. + - `updateHumanPage` :: Updates the human page number. This will call `updatePage` after mapping the human page number through the back-end - page number offset). + page number offset. All page numbers defined here are human page numbers. - `selectSizeOption` :: Selects a new size option, takes `event` as input and gets the new value from `event.target.value`. - `setSizeOption` :: Selects a new size, takes the `size` as either diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 1a912e7..ade5878 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -1,5 +1,6 @@ import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; +import { isEmpty } from '@ember/utils'; import Component from '@glimmer/component'; import { typeOf } from '@ember/utils'; import { toComponentSpecifications, definitionsToArray } from "../utils/string-specification-helpers"; @@ -11,15 +12,11 @@ export default class DataTable extends Component { @tracked _selection = undefined; get filter() { - return this.args.filter !== undefined - ? this.args.filter - : this.args.view?.filter; + return this.args.filter; } get sort() { - return this.args.sort !== undefined - ? this.args.sort - : this.args.view?.sort; + return this.args.sort; } get selection() { @@ -42,9 +39,7 @@ export default class DataTable extends Component { } get isLoading() { - return this.args.isLoading !== undefined - ? this.args.isLoading - : this.args.view?.isLoading; + return this.args.isLoading; } /** @@ -67,31 +62,24 @@ export default class DataTable extends Component { return this.selection.length === 0; } - get enableSizes() { - return this.args.enableSizes === undefined ? true : this.args.enableSizes; - } - get page() { - const page = this.args.page !== undefined - ? this.args.page - : this.args.view?.page; - return page || 0; + return this.args.page || 0; } get size() { - if ( this.args.size ) - return this.args.size; - else if ( this.args.view?.size ) - return this.args.view.size; - else - return 5; + return this.args.size || 5; } get sizeOptions() { - if (!this.enableSizes) { + const sizeOptions = + this.args.sizes === undefined + ? [5, 10, 25, 50, 100] + : definitionsToArray(this.args.sizes).map((nrOrStr) => + parseInt(nrOrStr) + ); + if (isEmpty(sizeOptions)) { return null; } else { - const sizeOptions = this.args.sizes || [5, 10, 25, 50, 100]; if (!sizeOptions.includes(this.size) && this.size) { sizeOptions.push(this.size); } @@ -190,10 +178,7 @@ export default class DataTable extends Component { @action updatePageSize(size) { - const updater = this.args.updatePageSize !== undefined - ? this.args.updatePageSize - : this.args.view?.updatePageSize; - + const updater = this.args.updatePageSize; if( !updater ) { console.error(`Could not update page size to ${size} because @updatePageSize was not supplied to data table`); } else { @@ -204,7 +189,7 @@ export default class DataTable extends Component { @action updateFilter(filter) { - const updater = this.args.updateFilter || this.args.view?.updateFilter; + const updater = this.args.updateFilter; if( !updater ) { console.error(`Could not update filter to '${filter}' because @updateFilter was not supplied to data table`); @@ -216,10 +201,7 @@ export default class DataTable extends Component { @action updateSort(sort) { - const updater = this.args.updateSort !== undefined - ? this.args.updateSort - : this.args.view?.updateSort; - + const updater = this.args.updateSort; if( !updater ) { console.error(`Could not update sorting to '${sort}' because @updateSort was not supplied to data table`); } else { @@ -230,10 +212,7 @@ export default class DataTable extends Component { @action updatePage(page) { - const updater = this.args.updatePage !== undefined - ? this.args.updatePage - : this.args.view?.updatePage; - + const updater = this.args.updatePage; if( !updater ) { console.error(`Could not update page to ${page} because @updatePage was not supplied to data table`); } else { diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index 877b297..c09e8af 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table/data-cells --}} {{yield (hash - firstColumn=@firstColumn - otherColumns=@otherColumns + firstColumnField=@firstColumnField + otherColumnFields=@otherColumnFields item=@wrapper.item rowLink=@wrapper.rowLink rowLinkModel=@wrapper.rowLinkModel diff --git a/addon/components/data-table/data-cells.hbs b/addon/components/data-table/data-cells.hbs index 83f25bd..55dbed3 100644 --- a/addon/components/data-table/data-cells.hbs +++ b/addon/components/data-table/data-cells.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table/row --}} {{yield (hash - firstColumn=this.firstColumn - otherColumns=this.otherColumns + firstColumnField=this.firstColumnField + otherColumnFields=this.otherColumnFields wrapper=@wrapper item=@wrapper.item rowLink=@wrapper.rowLink @@ -9,7 +9,7 @@ fields=@fields DataCell=(component "data-table/data-cell" - firstColumn=this.firstColumn - otherColumns=this.otherColumns + firstColumnField=this.firstColumnField + otherColumnFields=this.otherColumnFields wrapper=@wrapper fields=@fields))}} diff --git a/addon/components/data-table/data-cells.js b/addon/components/data-table/data-cells.js index 3b607d1..fc515d9 100644 --- a/addon/components/data-table/data-cells.js +++ b/addon/components/data-table/data-cells.js @@ -1,11 +1,11 @@ import Component from '@glimmer/component'; export default class DataTableDataCellsComponent extends Component { - get firstColumn() { + get firstColumnField() { return this.args.fields?.[0] || null; } - get otherColumns() { + get otherColumnFields() { if (this.args.fields?.length) { let [, ...fields] = this.args.fields; return fields; diff --git a/addon/components/data-table/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs index 635a3ff..76c6928 100644 --- a/addon/components/data-table/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -3,5 +3,7 @@ (component "data-table/data-table-menu-general" dataTable=@dataTable) (component "data-table/data-table-menu-selected" dataTable=@dataTable) as |general selected|}} - {{yield general selected @dataTable.enableSelection}} + {{yield + (hash General=general Selected=selected enableSelection=@dataTable.enableSelection) + }} {{/let}} \ No newline at end of file diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index 769b7e3..6f00637 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table.hbs --}} {{yield (hash - startItem=this.startItem - endItem=this.endItem + startIndex=this.startIndex + endIndex=this.endIndex total=this.total hasTotal=this.hasTotal pageSize=@size diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index 61516b9..8119b0c 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -126,7 +126,7 @@ export default class NumberPaginationComponent extends Component { return this.lastPage > this.firstPage; } - get startItem() { + get startIndex() { // note, you might want to use this.args.page instead, but given // that comes from the backend, it's *not* guaranteed to be // zero-based either. @@ -137,7 +137,7 @@ export default class NumberPaginationComponent extends Component { return zeroToHumanBased(this.args.size * humanToZeroBased( this.humanPage )); } - get endItem() { + get endIndex() { // this one is exactly the same number as humanPageOffset yet it has // a different meaning. When summing up lists, it's effectively // removing one regardless of the offset. @@ -145,7 +145,7 @@ export default class NumberPaginationComponent extends Component { // human probably expects to see 0-0 when no items exist. return 0; else - return this.startItem - 1 + this.args.itemsOnCurrentPage; + return this.startIndex - 1 + this.args.itemsOnCurrentPage; } get numberOfPages() { diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index e1a94e0..ecf204d 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -1,5 +1,4 @@ {{!-- Used in: data-table/data-table-content-body --}} -{{!-- TODO: do we want both selected and isSelected? --}} {{yield (hash wrapper=@wrapper item=@wrapper.item @@ -9,7 +8,6 @@ hasClickRowAction=@hasClickRowAction onClickRow=(fn @onClickRow @wrapper.item) isSelected=(includes-by @selection @wrapper.item @selectionProperty) - selected=(includes-by @selection @wrapper.item @selectionProperty) toggleSelected=(fn @toggleSelected @wrapper) linkedRoutes=this.linkedRoutes fields=@fields diff --git a/addon/components/data-table/text-search.hbs b/addon/components/data-table/text-search.hbs index a204a12..2c1235e 100644 --- a/addon/components/data-table/text-search.hbs +++ b/addon/components/data-table/text-search.hbs @@ -4,5 +4,6 @@ placeholder=@placeholder autoSearch=@autoSearch submitForm=this.submitForm + handleInput=this.handleInput handleAutoInput=this.handleAutoInput handleDirectInput=this.handleDirectInput)}} diff --git a/addon/components/data-table/text-search.js b/addon/components/data-table/text-search.js index 778a488..aa8829b 100644 --- a/addon/components/data-table/text-search.js +++ b/addon/components/data-table/text-search.js @@ -9,6 +9,12 @@ export default class TextSearchComponent extends Component { @action handleAutoInput(event) { + this.enteredValue = event.target.value; + this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); + } + + @action + handleInput(event) { this.enteredValue = event.target.value; if(this.args.autoSearch !== false) { this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 277cbaf..5be8e4b 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -3,9 +3,9 @@ @content={{@content}} @fields={{@fields}} @autoSearch={{@autoSearch}} - @view={{@view}} @page={{@page}} @size={{@size}} + @sizes={{@sizes}} @total={{@total}} @sort={{@sort}} @filter={{@filter}} @@ -46,18 +46,18 @@ {{on "submit" search.submitForm}} class="data-table-search"> {{#if search.autoSearch}} -