From a8ea4ae974da65f26447bb1bf286d17c65374fc8 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 17:34:20 +0100 Subject: [PATCH 01/56] Conversion of data-table component This conversion cannot be checked in its functionality, as we must convert the underlying components too in order to check the current setup. --- addon/components/data-table.hbs | 30 ++++ addon/components/data-table.js | 171 ++++++++++++++++------ addon/templates/components/data-table.hbs | 30 ---- 3 files changed, 156 insertions(+), 75 deletions(-) create mode 100644 addon/components/data-table.hbs delete mode 100644 addon/templates/components/data-table.hbs diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs new file mode 100644 index 0000000..ecfdad6 --- /dev/null +++ b/addon/components/data-table.hbs @@ -0,0 +1,30 @@ +{{#if (has-block)}} +
+ {{#if this.enableSearch}} + {{text-search filter=this.filter auto=this.autoSearch wait=this.searchDebounceTime}} + {{/if}} + {{yield (hash + menu=(component "data-table-menu" data-table=this) + ) + this}} +
+ {{yield (hash + content=(component "data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection enableLineNumbers=this.enableLineNumbers onClickRow=@onClickRow data-table=this) + ) + this}} {{!-- TODO: not sure about onClickRow --}} +{{else}} + {{#if this.enableSearch}} +
+
+ {{!-- text-search filter=this.filter auto=this.autoSearch --}} +
+
+ {{/if}} + {{!-- component "data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection enableLineNumbers=this.enableLineNumbers onClickRow=@onClickRow data-table=this --}} {{!-- TODO: not sure about onClickRow --}} +{{/if}} + +{{#if @content}} + {{!-- number-pagination + page=this.page size=this.size nbOfItems=@content.length sizeOptions=this.sizeOptions + total=@content.meta.count links=@content.meta.pagination --}} +{{/if}} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 743f4d6..8e959a4 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -1,64 +1,145 @@ +import { tracked } from '@glimmer/tracking'; +import Component from '@glimmer/component'; + import { typeOf } from '@ember/utils'; -import { observer } from '@ember/object'; -import { computed } from '@ember/object'; -import { oneWay } from '@ember/object/computed'; -import { bool } from '@ember/object/computed'; -import Component from '@ember/component'; -import layout from '../templates/components/data-table'; - -export default Component.extend({ - init() { - this._super(...arguments); - if (this.selection === undefined) this.set('selection', []); - }, - layout, - noDataMessage: 'No data', - isLoading: false, - lineNumbers: false, - searchDebounceTime: 2000, - enableLineNumbers: bool('lineNumbers'), - enableSelection: oneWay('hasMenu'), - selectionIsEmpty: computed.equal('selection.length', 0), - enableSizes: true, - size: 5, - sizeOptions: computed('size', 'sizes', 'enableSizes', function () { + +export default class DataTable extends Component { + @tracked _selection = undefined; + + get selection() { + if (this._selection === undefined + && !('selection' in this.args)) + return []; + else if (this._selection !== undefined) + return this._selection; + else + return this.args.selection; + } + + set selection(newSelection) { + this._selection = newSelection; // also trigers dependent properties + } + + get noDataMessage() { + return 'noDataMessage' in this.args + ? this.args.noDataMessage + : 'No data'; + } + + get isLoading() { + return this.args.isLoading; + } + + get enableLineNumbers() { + return this.args.lineNumbers; + } + + get searchDebounceTime() { + return 'searchDebounceTime' in this.args + ? this.args.searchDebounceTime + : 2000; + } + + get enableLineNumbers() { + return this.args.enableLineNumbers; + } + + @tracked + _enableSelection = undefined; + + get enableSelection() { + return this._enableSelection === undefined + ? this.args.hasMenu + : this._enableSelection; + } + + set enableSelection(value) { + this._enableSelection = value; + } + + get selectionIsEmpty() { + return this.selection.length === 0; + } + + get enableSizes() { + return 'enableSizes' in this.args + ? this.args.enableSizes + : true; + } + + _size = undefined; + get size() { + if (this._size === undefined && this.args.size) + return this.args.size; + else if (this._size) + return this._size; + else + return 5; + } + set size(newSize) { + this._size = newSize; + } + + get sizeOptions() { if (!this.enableSizes) { return null; } else { - const sizeOptions = this.sizes || [5, 10, 25, 50, 100]; + const sizeOptions = this.args.sizes || [5, 10, 25, 50, 100]; if (!sizeOptions.includes(this.size)) { sizeOptions.push(this.size); } sizeOptions.sort((a, b) => a - b); return sizeOptions; } - }), - hasMenu: false, // set from inner component, migth fail with nested if - enableSearch: computed('filter', function () { - return this.filter || this.filter === ''; - }), - autoSearch: true, - filterChanged: observer('filter', function () { - this.set('page', 0); - }), - sizeChanged: observer('size', function () { - this.set('page', 0); - }), - parsedFields: computed('fields', function () { + } + + @tracked hasMenu = false; // old comment: set from inner component, migth fail with nested if + + get enableSearch() { + return 'filter' in this.args; + } + + get autoSearch() { + return 'autoSearch' in this.args + ? this.args.autoSearch + : true; + } + + // TODO: make sure onFilterChanged is called and pushes its values through + onFilterChanged() { + this.page = 0; + } + + // TODO: make sure onSizeChanged is called and pushes its values through + onSizeChanged() { + this.page = 0; + } + + get parsedFields() { const fields = this.fields; if (typeOf(fields) === 'string') { return fields.split(' '); } else { return fields || []; } - }), + } + + // filterChanged: observer('filter', function () { + // this.set('page', 0); + // }), + // sizeChanged: observer('size', function () { + // this.set('page', 0); + // }), + // parsedFields: computed('fields', function () { + // }), + addItemToSelection(item) { - this.selection.addObject(item); - }, + this.selection = [item, ...this.selection]; + } removeItemFromSelection(item) { - this.selection.removeObject(item); - }, + this.selection = this.selection.filter((x) => x !== item); + } clearSelection() { - this.selection.clear(); - }, -}); + this.selection = []; + } +} diff --git a/addon/templates/components/data-table.hbs b/addon/templates/components/data-table.hbs deleted file mode 100644 index d530ed1..0000000 --- a/addon/templates/components/data-table.hbs +++ /dev/null @@ -1,30 +0,0 @@ -{{#if (has-block)}} -
- {{#if enableSearch}} - {{text-search filter=filter auto=autoSearch wait=searchDebounceTime}} - {{/if}} - {{yield (hash - menu=(component "data-table-menu" data-table=this) - ) - this}} -
- {{yield (hash - content=(component "data-table-content" content=content noDataMessage=noDataMessage enableSelection=enableSelection enableLineNumbers=enableLineNumbers onClickRow=(optional onClickRow) data-table=this) - ) - this}} -{{else}} - {{#if enableSearch}} -
-
- {{text-search filter=filter auto=autoSearch}} -
-
- {{/if}} - {{component "data-table-content" content=content noDataMessage=noDataMessage enableSelection=enableSelection enableLineNumbers=enableLineNumbers onClickRow=(optional onClickRow) data-table=this}} -{{/if}} - -{{#if content}} - {{number-pagination - page=page size=size nbOfItems=content.length sizeOptions=sizeOptions - total=content.meta.count links=content.meta.pagination}} -{{/if}} From e42c8d14dc7eaeac02dae943af6a59b975af7ac7 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 19:49:09 +0100 Subject: [PATCH 02/56] Working version of pagination component The NumberPagination component now also consumes content and sends updates. --- addon/components/data-table.hbs | 13 ++- addon/components/data-table.js | 26 +++-- addon/components/number-pagination.hbs | 25 +++++ addon/components/number-pagination.js | 97 +++++++++++-------- .../components/number-pagination.hbs | 27 ------ 5 files changed, 107 insertions(+), 81 deletions(-) create mode 100644 addon/components/number-pagination.hbs delete mode 100644 addon/templates/components/number-pagination.hbs diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index ecfdad6..3f575dc 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -24,7 +24,14 @@ {{/if}} {{#if @content}} - {{!-- number-pagination - page=this.page size=this.size nbOfItems=@content.length sizeOptions=this.sizeOptions - total=@content.meta.count links=@content.meta.pagination --}} + {{/if}} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 8e959a4..bf8e4d9 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -1,3 +1,4 @@ +import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import Component from '@glimmer/component'; @@ -124,14 +125,23 @@ export default class DataTable extends Component { } } - // filterChanged: observer('filter', function () { - // this.set('page', 0); - // }), - // sizeChanged: observer('size', function () { - // this.set('page', 0); - // }), - // parsedFields: computed('fields', function () { - // }), + @action + updatePageSize(size) { + this.args.updatePage(0); + this.args.updatePageSize(size); + } + + @action + updateFilter(filter) { + this.args.updatePage(0); + this.args.updateFilter(filter); + } + + @action + updateSort(sort) { + this.args.updatePage(0); + this.args.updateSort(sort); + } addItemToSelection(item) { this.selection = [item, ...this.selection]; diff --git a/addon/components/number-pagination.hbs b/addon/components/number-pagination.hbs new file mode 100644 index 0000000..6f93fed --- /dev/null +++ b/addon/components/number-pagination.hbs @@ -0,0 +1,25 @@ +
+
+ Displaying {{this.startItem}}-{{this.endItem}} + {{#if @total}} of {{@total}}{{/if}} + {{#if @sizeOptions}} + | + + {{/if}} +
+ {{#if this.hasMultiplePages}} +
+ + + + + +
+ {{/if}} +
\ No newline at end of file diff --git a/addon/components/number-pagination.js b/addon/components/number-pagination.js index 901ade2..e408790 100644 --- a/addon/components/number-pagination.js +++ b/addon/components/number-pagination.js @@ -1,49 +1,60 @@ -import { computed } from '@ember/object'; -import Component from '@ember/component'; -import layout from '../templates/components/number-pagination'; - -export default Component.extend({ - layout, - classNames: ['data-table-pagination'], - currentPage: computed('page', { - get() { - return this.page ? parseInt(this.page) + 1 : 1; - }, - set(key, value) { - this.set('page', value - 1); - return value; - }, - }), - firstPage: computed('links.first.number', function () { - return this.get('links.first.number') || 1; - }), - lastPage: computed('links.last.number', function () { - const max = this.get('links.last.number') || -1; - return max ? max + 1 : max; - }), - isFirstPage: computed('firstPage', 'currentPage', function () { +import { action } from '@ember/object'; +import Component from '@glimmer/component'; + +export default class NumberPaginationComponent extends Component { + get currentPage() { + return this.args.page + ? parseInt(this.args.page) + 1 // TODO: verify this needs to parse the page, if the default is a number input should be parsed as a number too + : 1; + } + + set currentPage(newPage) { + this.args.updatePage( newPage - 1 ); + } + + get firstPage() { + return this.args.links?.first?.number || 1; + } + + get lastPage() { + return this.args.links?.last?.number || 1; + } + + get isFirstPage() { return this.firstPage == this.currentPage; - }), - isLastPage: computed('lastPage', 'currentPage', function () { + } + + get isLastPage() { return this.lastPage == this.currentPage; - }), - hasMultiplePages: computed.gt('lastPage', 0), - startItem: computed('size', 'currentPage', function () { - return this.size * (this.currentPage - 1) + 1; - }), - endItem: computed('startItem', 'nbOfItems', function () { - return this.startItem + this.nbOfItems - 1; - }), - pageOptions: computed('firstPage', 'lastPage', function () { + } + + get hasMultiplePages() { + return this.lastPage > 0; // TODO: is this right? All numbers seem to default to 1. + } + + get startItem() { + return this.args.size * (this.currentPage - 1) + 1; + } + + get endItem() { + return this.startItem + this.args.nbOfItems - 1; + } + + get pageOptions() { const nbOfPages = this.lastPage - this.firstPage + 1; return Array.from( new Array(nbOfPages), - (val, index) => this.firstPage + index + (_val, index) => this.firstPage + index ); - }), - actions: { - changePage(link) { - this.set('page', link['number'] || 0); - }, - }, -}); + } + + @action + updatePage(link) { + this.args.updatePage( link?.number || 0 ); + } + + @action + selectSizeOption(event) { + this.args.updateSize(parseInt(event.target.value)); + } +} diff --git a/addon/templates/components/number-pagination.hbs b/addon/templates/components/number-pagination.hbs deleted file mode 100644 index fd93b80..0000000 --- a/addon/templates/components/number-pagination.hbs +++ /dev/null @@ -1,27 +0,0 @@ -
-
- Displaying {{startItem}}-{{endItem}} - {{#if total}} of {{total}}{{/if}} - {{#if sizeOptions}} - | - per page - {{/if}} -
- {{#if hasMultiplePages}} -
- - - - - -
- {{/if}} -
From 891d5801baa9a571a861ccca4cc21ef1e7b7566a Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 20:54:52 +0100 Subject: [PATCH 03/56] Implementation of DataTableContent up to ThSortable This is the path that allows us to render a sortable header. Yet nothing else is currently being rendered. --- .../components/data-table-content-header.hbs | 17 +++++ addon/components/data-table-content-header.js | 12 +--- addon/components/data-table-content.hbs | 26 +++++++ addon/components/data-table-content.js | 10 +-- addon/components/data-table.hbs | 12 +++- addon/components/data-table.js | 6 +- addon/components/th-sortable.hbs | 6 ++ addon/components/th-sortable.js | 67 +++++++++---------- .../components/data-table-content-header.hbs | 15 ----- .../components/data-table-content.hbs | 12 ---- addon/templates/components/th-sortable.hbs | 4 -- 11 files changed, 101 insertions(+), 86 deletions(-) create mode 100644 addon/components/data-table-content-header.hbs create mode 100644 addon/components/data-table-content.hbs create mode 100644 addon/components/th-sortable.hbs delete mode 100644 addon/templates/components/data-table-content-header.hbs delete mode 100644 addon/templates/components/data-table-content.hbs delete mode 100644 addon/templates/components/th-sortable.hbs diff --git a/addon/components/data-table-content-header.hbs b/addon/components/data-table-content-header.hbs new file mode 100644 index 0000000..35efca6 --- /dev/null +++ b/addon/components/data-table-content-header.hbs @@ -0,0 +1,17 @@ + + + {{#if @enableSelection}} + {{!-- Checkbox --}} + {{/if}} + {{#if @enableLineNumbers}} + {{!-- Linenumbers --}} + {{/if}} + {{#if (has-block)}} + {{yield}} + {{else}} + {{#each @data-table.parsedFields as |field|}} + + {{/each}} + {{/if}} + + \ No newline at end of file diff --git a/addon/components/data-table-content-header.js b/addon/components/data-table-content-header.js index 7fd2dae..b1690c5 100644 --- a/addon/components/data-table-content-header.js +++ b/addon/components/data-table-content-header.js @@ -1,11 +1,3 @@ -import { oneWay } from '@ember/object/computed'; -import { alias } from '@ember/object/computed'; -import Component from '@ember/component'; -import layout from '../templates/components/data-table-content-header'; +import Component from '@glimmer/component'; -export default Component.extend({ - layout, - tagName: 'thead', - sort: alias('data-table.sort'), - fields: oneWay('data-table.parsedFields'), -}); +export default class DataTableContentHeaderComponent extends Component { } diff --git a/addon/components/data-table-content.hbs b/addon/components/data-table-content.hbs new file mode 100644 index 0000000..124e585 --- /dev/null +++ b/addon/components/data-table-content.hbs @@ -0,0 +1,26 @@ +{{! template-lint-disable table-groups }} +
+ + {{#if (has-block)}} + {{yield (hash + header=(component "data-table-content-header" enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers data-table=@data-table) + body=(component "data-table-content-body" content=@content enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers noDataMessage=@noDataMessage onClickRow=@onClickRow data-table=@data-table) + )}} + {{else}} + + {{!-- TODO: implement me + + --}} + {{/if}} +
+
\ No newline at end of file diff --git a/addon/components/data-table-content.js b/addon/components/data-table-content.js index 7236c2d..90d2001 100644 --- a/addon/components/data-table-content.js +++ b/addon/components/data-table-content.js @@ -1,9 +1,3 @@ -import Component from '@ember/component'; -import { alias } from '@ember/object/computed'; -import layout from '../templates/components/data-table-content'; +import Component from '@glimmer/component'; -export default Component.extend({ - layout, - classNames: ['data-table-content'], - tableClass: alias('data-table.tableClass'), -}); +export default class DataTableContentComponent extends Component { } diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 3f575dc..32b6a13 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -20,7 +20,17 @@ {{/if}} - {{!-- component "data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection enableLineNumbers=this.enableLineNumbers onClickRow=@onClickRow data-table=this --}} {{!-- TODO: not sure about onClickRow --}} + + {{!-- TODO: not sure about onClickRow --}} + + {{!-- component "data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection enableLineNumbers=this.enableLineNumbers onClickRow=@onClickRow data-table=this --}} {{/if}} {{#if @content}} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index bf8e4d9..06874d4 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -68,6 +68,10 @@ export default class DataTable extends Component { : true; } + get sort() { + return this.args.sort; + } + _size = undefined; get size() { if (this._size === undefined && this.args.size) @@ -117,7 +121,7 @@ export default class DataTable extends Component { } get parsedFields() { - const fields = this.fields; + const fields = this.args.fields; if (typeOf(fields) === 'string') { return fields.split(' '); } else { diff --git a/addon/components/th-sortable.hbs b/addon/components/th-sortable.hbs new file mode 100644 index 0000000..62134ba --- /dev/null +++ b/addon/components/th-sortable.hbs @@ -0,0 +1,6 @@ + + + {{#if this.order}}[{{this.order}}]{{/if}} + {{@label}} + + diff --git a/addon/components/th-sortable.js b/addon/components/th-sortable.js index 41ede1f..5a0ade1 100644 --- a/addon/components/th-sortable.js +++ b/addon/components/th-sortable.js @@ -1,15 +1,11 @@ -import { computed } from '@ember/object'; -import Component from '@ember/component'; -import layout from '../templates/components/th-sortable'; +import { action } from '@ember/object'; +import Component from '@glimmer/component'; + +export default class ThSortableComponent extends Component { + get dasherizedField() { + return this.args.field.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + } -export default Component.extend({ - layout: layout, - tagName: 'th', - classNames: ['sortable'], - classNameBindings: ['isSorted:sorted'], - dasherizedField: computed('field', function () { - return this.field.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); - }), /** Inverses the sorting parameter E.g. inverseSorting('title') returns '-title' @@ -21,40 +17,41 @@ export default Component.extend({ } else { return '-' + sorting; } - }, - isSorted: computed('dasherizedField', 'currentSorting', function () { + } + + get isSorted() { return ( - this.currentSorting === this.dasherizedField || - this.currentSorting === this._inverseSorting(this.dasherizedField) + this.args.currentSorting === this.dasherizedField || + this.args.currentSorting === this._inverseSorting(this.dasherizedField) ); - }), - order: computed('dasherizedField', 'currentSorting', function () { - if (this.currentSorting === this.dasherizedField) { + } + + get order() { + if (this.args.currentSorting === this.dasherizedField) { return 'asc'; - } else if (this.currentSorting === `-${this.dasherizedField}`) { + } else if (this.args.currentSorting === `-${this.dasherizedField}`) { return 'desc'; } else { return ''; } - }), + } - actions: { - /** + /** Sets the current sorting parameter. Note: the current sorting parameter may contain another field than the given field. In case the given field is currently sorted ascending, change to descending. In case the given field is currently sorted descending, clean the sorting. Else, set the sorting to ascending on the given field. - */ - inverseSorting() { - if (this.order === 'asc') { - this.set('currentSorting', this._inverseSorting(this.currentSorting)); - } else if (this.order === 'desc') { - this.set('currentSorting', ''); - } else { - // if currentSorting is not set to this field - this.set('currentSorting', this.dasherizedField); - } - }, - }, -}); + */ + @action + inverseSorting() { + if (this.order === 'asc') { + this.args.updateSort(this._inverseSorting(this.args.currentSorting)); + } else if (this.order === 'desc') { + this.args.updateSort(""); + } else { + // if currentSorting is not set to this field + this.args.updateSort(this.dasherizedField); + } + } +} diff --git a/addon/templates/components/data-table-content-header.hbs b/addon/templates/components/data-table-content-header.hbs deleted file mode 100644 index f075637..0000000 --- a/addon/templates/components/data-table-content-header.hbs +++ /dev/null @@ -1,15 +0,0 @@ - - {{#if enableSelection}} - {{!-- Checkbox --}} - {{/if}} - {{#if enableLineNumbers}} - {{!-- Linenumbers --}} - {{/if}} - {{#if (has-block)}} - {{yield}} - {{else}} - {{#each fields as |field|}} - {{th-sortable field=field label=field currentSorting=sort}} - {{/each}} - {{/if}} - diff --git a/addon/templates/components/data-table-content.hbs b/addon/templates/components/data-table-content.hbs deleted file mode 100644 index 782f366..0000000 --- a/addon/templates/components/data-table-content.hbs +++ /dev/null @@ -1,12 +0,0 @@ -{{! template-lint-disable table-groups }} - - {{#if (has-block)}} - {{yield (hash - header=(component "data-table-content-header" enableSelection=enableSelection enableLineNumbers=enableLineNumbers data-table=data-table) - body=(component "data-table-content-body" content=content enableSelection=enableSelection enableLineNumbers=enableLineNumbers noDataMessage=noDataMessage onClickRow=(optional onClickRow) data-table=data-table) - )}} - {{else}} - {{component "data-table-content-header" enableSelection=enableSelection enableLineNumbers=enableLineNumbers data-table=data-table}} - {{component "data-table-content-body" content=content enableSelection=enableSelection enableLineNumbers=enableLineNumbers noDataMessage=noDataMessage onClickRow=(optional onClickRow) data-table=data-table}} - {{/if}} -
diff --git a/addon/templates/components/th-sortable.hbs b/addon/templates/components/th-sortable.hbs deleted file mode 100644 index b17b6f1..0000000 --- a/addon/templates/components/th-sortable.hbs +++ /dev/null @@ -1,4 +0,0 @@ - - {{#if order}}[{{order}}]{{/if}} - {{label}} - From 15208f5e89d1a0ddc11c34e2efe405cc3b44e3ce Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 21:26:06 +0100 Subject: [PATCH 04/56] Upgrades rendering of data table content The content can now be rendered in an Ember 4.x application. --- addon/components/data-table-content-body.hbs | 29 +++++++ addon/components/data-table-content-body.js | 76 +++++++++---------- addon/components/data-table-content.hbs | 4 +- addon/components/data-table.hbs | 2 - .../default-data-table-content-body.hbs | 15 ++++ .../default-data-table-content-body.js | 48 +++++++----- .../components/data-table-content-body.hbs | 25 ------ .../default-data-table-content-body.hbs | 15 ---- 8 files changed, 108 insertions(+), 106 deletions(-) create mode 100644 addon/components/data-table-content-body.hbs create mode 100644 addon/components/default-data-table-content-body.hbs delete mode 100644 addon/templates/components/data-table-content-body.hbs delete mode 100644 addon/templates/components/default-data-table-content-body.hbs diff --git a/addon/components/data-table-content-body.hbs b/addon/components/data-table-content-body.hbs new file mode 100644 index 0000000..9d69def --- /dev/null +++ b/addon/components/data-table-content-body.hbs @@ -0,0 +1,29 @@ + + {{#if @data-table.isLoading}} + Loading... + {{else}} + {{#if @content}} + {{#each this.wrappedItems as |wrapper index|}} + + {{#if @enableSelection}} + + {{input type="checkbox" checked=wrapper.isSelected click=(fn this.updateSelection wrapper)}} + + {{/if}} + {{#if @enableLineNumbers}} + {{add index offset}} + {{/if}} + {{#if (has-block)}} + {{yield wrapper.item}} + {{else}} + + {{/if}} + + {{/each}} + {{else}} +

{{@noDataMessage}}

+ {{/if}} + {{/if}} + diff --git a/addon/components/data-table-content-body.js b/addon/components/data-table-content-body.js index f143fac..8b663e1 100644 --- a/addon/components/data-table-content-body.js +++ b/addon/components/data-table-content-body.js @@ -1,47 +1,41 @@ -import { set } from '@ember/object'; -import { computed } from '@ember/object'; -import Component from '@ember/component'; -import layout from '../templates/components/data-table-content-body'; +import { action } from '@ember/object'; +import Component from '@glimmer/component'; -export default Component.extend({ - tagName: 'tbody', - init() { - this._super(...arguments); - if (!this['data-table']) this.set('data-table', {}); - if (!this['content']) this.set('content', []); - }, - layout, - offset: computed('data-table.{page,size}', function () { +export default class DataTableContentBodyComponent extends Component { + get offset() { var offset = 1; //to avoid having 0. row - var page = this.get('data-table.page'); - var size = this.get('data-table.size'); + var page = this.args["data-table"].page; // TODO: pass on page directly? + var size = this.args["data-table"].size; // TODO: pass on size directly? if (page && size) { offset += page * size; } return offset; - }), - wrappedItems: computed( - 'content', - 'content.[]', - 'data-table.selection.[]', - function () { - const selection = this.get('data-table.selection') || []; - const content = this.content || []; - return content.map(function (item) { - return { item: item, isSelected: selection.includes(item) }; - }); - } - ), - actions: { - updateSelection(selectedWrapper, event) { - set(selectedWrapper, 'isSelected', event.target.checked); - this.wrappedItems.forEach((wrapper) => { - if (wrapper.isSelected) { - this.get('data-table').addItemToSelection(wrapper.item); - } else { - this.get('data-table').removeItemFromSelection(wrapper.item); - } - }); - }, - }, -}); + } + + get wrappedItems() { + const selection = this.args["data-table"].selection || []; // TODO: should the data-table ensure this is an array? + const content = this.args.content; + return content.map(function(item) { + return { item: item, isSelected: selection.includes(item) }; + }); + } + + @action + updateSelection(selectedWrapper, event) { + selectedWrapper.isSelected = event.target.checked; + + this.wrappedItems.forEach((wrapper) => { + if (wrapper.isSelected) { + this.args["data-table"].addItemToSelection(wrapper.item); // TODO: pass on addItemToSelection directly? + } else { + this.arg["data-table"].removeItemFromSelection(wrapper.item); // TODO: pass on removeItemFromSelection directly? + } + }); + } + + @action + onClickRow() { + if( this.args.onClickRow ) + this.args.onClickRow(...arguments); + } +} diff --git a/addon/components/data-table-content.hbs b/addon/components/data-table-content.hbs index 124e585..985c886 100644 --- a/addon/components/data-table-content.hbs +++ b/addon/components/data-table-content.hbs @@ -12,15 +12,13 @@ @enableLineNumbers={{@enableLineNumbers}} @updateSort={{@updateSort}} @data-table={{@data-table}} /> - {{!-- TODO: implement me - - --}} {{/if}} \ No newline at end of file diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 32b6a13..93a0403 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -29,8 +29,6 @@ @onClickRow={{@onClickRow}} @updateSort={{this.updateSort}} @data-table={{this}} /> {{!-- TODO: not sure about onClickRow --}} - - {{!-- component "data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection enableLineNumbers=this.enableLineNumbers onClickRow=@onClickRow data-table=this --}} {{/if}} {{#if @content}} diff --git a/addon/components/default-data-table-content-body.hbs b/addon/components/default-data-table-content-body.hbs new file mode 100644 index 0000000..6c8ecf3 --- /dev/null +++ b/addon/components/default-data-table-content-body.hbs @@ -0,0 +1,15 @@ +{{#if this.firstColumn}} + {{#if this.linkedRoute}} + {{#link-to this.linkedRoute @item tagName="td"}}{{get @item this.firstColumn}}{{/link-to}} + {{else}} + {{get @item this.firstColumn}} + {{/if}} +{{/if}} +{{#each this.otherColumns as |field|}} + + {{!-- TODO: This should be based on the type of the field --}} + {{get @item field}} + +{{/each}} +{{yield}} + diff --git a/addon/components/default-data-table-content-body.js b/addon/components/default-data-table-content-body.js index 30d0242..4c4522a 100644 --- a/addon/components/default-data-table-content-body.js +++ b/addon/components/default-data-table-content-body.js @@ -1,21 +1,29 @@ -import { A } from '@ember/array'; -import { computed } from '@ember/object'; -import { oneWay } from '@ember/object/computed'; -import Component from '@ember/component'; -import layout from '../templates/components/default-data-table-content-body'; +import Component from '@glimmer/component'; -export default Component.extend({ - layout, - tagName: '', - allFields: oneWay('data-table.parsedFields'), - firstColumn: computed('data-table.parsedFields', function () { - const parsedFields = A(this.get('data-table.parsedFields')); - return parsedFields.get('firstObject'); - }), - otherColumns: computed('data-table.parsedFields', function () { - let fields; - [, ...fields] = this.get('data-table.parsedFields'); - return fields; - }), - linkedRoute: oneWay('data-table.link'), -}); +export default class DefaultDataTableContentBodyComponent extends Component { + get allFields() { + return this.args["data-table"].parsedFields; // TODO: pass directly? + } + + get firstColumn() { + const parsedFields = this.args["data-table"].parsedFields; + if( parsedFields.length > 0 ) + return parsedFields[0]; + else + return null; + } + + get otherColumns() { + const parsedFields = this.args["data-table"].parsedFields; + if( parsedFields.length > 0 ) { + let [, ...fields] = parsedFields; + return fields; + } else { + return []; + } + } + + get linkedRoute() { + return this.args["data-table"].link; + } +} diff --git a/addon/templates/components/data-table-content-body.hbs b/addon/templates/components/data-table-content-body.hbs deleted file mode 100644 index 775ede4..0000000 --- a/addon/templates/components/data-table-content-body.hbs +++ /dev/null @@ -1,25 +0,0 @@ -{{#if data-table.isLoading}} - Loading... -{{else}} - {{#if content}} - {{#each wrappedItems as |wrapper index|}} - - {{#if enableSelection}} - - {{input type="checkbox" checked=wrapper.isSelected click=(action "updateSelection" wrapper)}} - - {{/if}} - {{#if enableLineNumbers}} - {{add index offset}} - {{/if}} - {{#if (has-block)}} - {{yield wrapper.item}} - {{else}} - {{default-data-table-content-body item=wrapper.item data-table=data-table}} - {{/if}} - - {{/each}} - {{else}} -

{{noDataMessage}}

- {{/if}} -{{/if}} diff --git a/addon/templates/components/default-data-table-content-body.hbs b/addon/templates/components/default-data-table-content-body.hbs deleted file mode 100644 index f13a9c0..0000000 --- a/addon/templates/components/default-data-table-content-body.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if firstColumn}} - {{#if linkedRoute}} - {{#link-to linkedRoute item tagName="td"}}{{get item firstColumn}}{{/link-to}} - {{else}} - {{get item firstColumn}} - {{/if}} -{{/if}} -{{#each otherColumns as |field|}} - - {{!-- This should be based on the type of the field --}} - {{get item field}} - -{{/each}} -{{yield}} - From 42fff8acea66e21ec8bf23229a9b561d15285dea Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 22:14:31 +0100 Subject: [PATCH 05/56] Upgrade search to Ember 4 The base building blocks the search used are quite neat. We've continued to use those and were able to simplify a tiny bit due to new abstractions. This component can likely benefit from some shuffling of the code but that would require a second reading. --- addon/components/data-table.hbs | 8 ++- addon/components/data-table.js | 6 ++ addon/components/text-search.hbs | 16 +++++ addon/components/text-search.js | 74 +++++++++++----------- addon/templates/components/text-search.hbs | 6 -- 5 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 addon/components/text-search.hbs delete mode 100644 addon/templates/components/text-search.hbs diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 93a0403..39627df 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -16,7 +16,13 @@ {{#if this.enableSearch}}
- {{!-- text-search filter=this.filter auto=this.autoSearch --}} +
{{/if}} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 06874d4..bb5efbe 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -129,6 +129,12 @@ export default class DataTable extends Component { } } + get searchPlaceholder() { + return "searchPlaceholder" in this.args + ? this.args.searchPlaceholder + : "Search input"; + } + @action updatePageSize(size) { this.args.updatePage(0); diff --git a/addon/components/text-search.hbs b/addon/components/text-search.hbs new file mode 100644 index 0000000..4625c9b --- /dev/null +++ b/addon/components/text-search.hbs @@ -0,0 +1,16 @@ + diff --git a/addon/components/text-search.js b/addon/components/text-search.js index c7c09bb..22cd9a8 100644 --- a/addon/components/text-search.js +++ b/addon/components/text-search.js @@ -1,41 +1,39 @@ -import { isEqual } from '@ember/utils'; +import { action } from '@ember/object'; import { cancel, debounce } from '@ember/runloop'; -import { observer } from '@ember/object'; -import { oneWay } from '@ember/object/computed'; -import Component from '@ember/component'; -import layout from '../templates/components/text-search'; - -export default Component.extend({ - layout, - filter: '', - classNames: ['data-table-search'], - internalValue: oneWay('filter'), - auto: true, - placeholder: 'Search', - init() { - this._super(...arguments); - this.set('value', this.filter); - }, - onValueChange: observer('value', function () { - this._valuePid = debounce(this, this._setFilter, this.wait); - }), - onFilterChange: observer('filter', function () { - // update value if filter is update manually outsite this component - if ( - !this.isDestroying && - !this.isDestroyed && - !isEqual(this.filter, this.value) - ) { - this.set('value', this.filter); - } - }), - _setFilter() { - if (!this.isDestroying && !this.isDestroyed) { - this.set('filter', this.value); +import Component from '@glimmer/component'; + +export default class TextSearchComponent extends Component { + enteredValue = undefined; + + autoDebouncePid = undefined; + + @action + handleAutoInput(event) { + this.enteredValue = event.target.value; + this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.wait); + } + + submitCurrent() { + if( !this.isDestroying && !this.isDestroyed ) { + this.args.updateFilter( this.enteredValue ); + this.autoDebouncePid = undefined; } - }, + } + willDestroy() { - this._super(...arguments); - cancel(this._valuePid); - }, -}); + super.willDestroy(...arguments); + cancel(this.autoDebouncePid); + } + + + @action + handleDirectInput(event) { + this.enteredValue = event.target.value; + } + + @action + submitForm(event) { + event.preventDefault(); + this.submitCurrent(); + } +} diff --git a/addon/templates/components/text-search.hbs b/addon/templates/components/text-search.hbs deleted file mode 100644 index 0ae311f..0000000 --- a/addon/templates/components/text-search.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{#if auto}} - {{input value=value placeholder=placeholder}} -{{else}} - - -{{/if}} From 852536d2252f1534a3a374ae02797b4cb5a5b1b1 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 22:22:43 +0100 Subject: [PATCH 06/56] Get filter from route Filter was not passed correctly. --- addon/components/data-table.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 39627df..33fcafe 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -17,7 +17,7 @@
Date: Sat, 22 Jan 2022 22:22:56 +0100 Subject: [PATCH 07/56] Remove unused functions in data-table These had some leftover todos attached to them. Not needed. --- addon/components/data-table.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/addon/components/data-table.js b/addon/components/data-table.js index bb5efbe..e9a8f94 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -110,16 +110,6 @@ export default class DataTable extends Component { : true; } - // TODO: make sure onFilterChanged is called and pushes its values through - onFilterChanged() { - this.page = 0; - } - - // TODO: make sure onSizeChanged is called and pushes its values through - onSizeChanged() { - this.page = 0; - } - get parsedFields() { const fields = this.args.fields; if (typeOf(fields) === 'string') { From e0501d936163f46b09b52323d6868fa6d2bd5e88 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 22 Jan 2022 22:30:01 +0100 Subject: [PATCH 08/56] Blind upgrading of data-table-menu The test cases may help us find a way out of this, but this feels like it's a sensible step to at least get the branch into a possibly sensible state. --- addon/components/data-table-menu-general.hbs | 3 ++ addon/components/data-table-menu-general.js | 7 ++-- addon/components/data-table-menu-selected.hbs | 5 +++ addon/components/data-table-menu-selected.js | 33 ++++++++++--------- addon/components/data-table-menu.hbs | 6 ++++ addon/components/data-table-menu.js | 8 ++--- .../components/data-table-menu-general.hbs | 3 -- .../components/data-table-menu-selected.hbs | 5 --- .../templates/components/data-table-menu.hbs | 4 --- 9 files changed, 35 insertions(+), 39 deletions(-) create mode 100644 addon/components/data-table-menu-general.hbs create mode 100644 addon/components/data-table-menu-selected.hbs create mode 100644 addon/components/data-table-menu.hbs delete mode 100644 addon/templates/components/data-table-menu-general.hbs delete mode 100644 addon/templates/components/data-table-menu-selected.hbs delete mode 100644 addon/templates/components/data-table-menu.hbs diff --git a/addon/components/data-table-menu-general.hbs b/addon/components/data-table-menu-general.hbs new file mode 100644 index 0000000..bbe3ab5 --- /dev/null +++ b/addon/components/data-table-menu-general.hbs @@ -0,0 +1,3 @@ +{{#if @data-table.selectionIsEmpty}} + {{yield}} +{{/if}} diff --git a/addon/components/data-table-menu-general.js b/addon/components/data-table-menu-general.js index d843ece..c985733 100644 --- a/addon/components/data-table-menu-general.js +++ b/addon/components/data-table-menu-general.js @@ -1,6 +1,3 @@ -import Component from '@ember/component'; -import layout from '../templates/components/data-table-menu-general'; +import Component from '@glimmer/component'; -export default Component.extend({ - layout, -}); +export default class DataTableMenuGeneralComponent extends Component {} diff --git a/addon/components/data-table-menu-selected.hbs b/addon/components/data-table-menu-selected.hbs new file mode 100644 index 0000000..49bce12 --- /dev/null +++ b/addon/components/data-table-menu-selected.hbs @@ -0,0 +1,5 @@ +{{#unless @data-table.selectionIsEmpty}} + {{this.selectionCount}} item(s) selected + + {{yield (slice 0 this.selectionCount @data-table.selection) @data-table}} +{{/unless}} diff --git a/addon/components/data-table-menu-selected.js b/addon/components/data-table-menu-selected.js index 5854bcb..4ed3972 100644 --- a/addon/components/data-table-menu-selected.js +++ b/addon/components/data-table-menu-selected.js @@ -1,17 +1,18 @@ -import { computed } from '@ember/object'; -import Component from '@ember/component'; -import layout from '../templates/components/data-table-menu-selected'; +import { action } from '@ember/object'; +import Component from '@glimmer/component'; -export default Component.extend({ - layout, - init: function () { - this._super(...arguments); - this.set('data-table.enableSelection', true); - }, - selectionCount: computed.reads('data-table.selection.length'), - actions: { - clearSelection() { - this.get('data-table').clearSelection(); - }, - }, -}); +export default class DataTableMenuSelectedComponent extends Component { + constructor() { + super(...arguments); + this.args["data-table"].enableSelection = true; // TODO: is this the best way to handle such case? + } + + get selectionCount() { + return this.args["data-table"].selection.length; + } + + @action + clearSelection() { + this.args["data-table"].clearSelection(); + } +} diff --git a/addon/components/data-table-menu.hbs b/addon/components/data-table-menu.hbs new file mode 100644 index 0000000..7a16603 --- /dev/null +++ b/addon/components/data-table-menu.hbs @@ -0,0 +1,6 @@ +
+ {{yield (hash + general=(component "data-table-menu-general" data-table=@data-table) + selected=(component "data-table-menu-selected" data-table=@data-table) + )}} +
diff --git a/addon/components/data-table-menu.js b/addon/components/data-table-menu.js index 298f2ba..9d407b4 100644 --- a/addon/components/data-table-menu.js +++ b/addon/components/data-table-menu.js @@ -1,7 +1,3 @@ -import Component from '@ember/component'; -import layout from '../templates/components/data-table-menu'; +import Component from '@glimmer/component'; -export default Component.extend({ - layout, - classNames: ['data-table-menu'], -}); +export default class DataTableMenuComponent extends Component {} diff --git a/addon/templates/components/data-table-menu-general.hbs b/addon/templates/components/data-table-menu-general.hbs deleted file mode 100644 index 452c9a5..0000000 --- a/addon/templates/components/data-table-menu-general.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if data-table.selectionIsEmpty}} - {{yield}} -{{/if}} diff --git a/addon/templates/components/data-table-menu-selected.hbs b/addon/templates/components/data-table-menu-selected.hbs deleted file mode 100644 index b8ac2b2..0000000 --- a/addon/templates/components/data-table-menu-selected.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{#unless data-table.selectionIsEmpty}} - {{selectionCount}} item(s) selected - - {{yield (slice 0 selectionCount data-table.selection) data-table}} -{{/unless}} diff --git a/addon/templates/components/data-table-menu.hbs b/addon/templates/components/data-table-menu.hbs deleted file mode 100644 index db4ab7a..0000000 --- a/addon/templates/components/data-table-menu.hbs +++ /dev/null @@ -1,4 +0,0 @@ -{{yield (hash - general=(component "data-table-menu-general" data-table=data-table) - selected=(component "data-table-menu-selected" data-table=data-table) -)}} From 90b26520ecd23db780fab94a811692298ddb8fe4 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sun, 23 Jan 2022 18:24:49 +0100 Subject: [PATCH 09/56] Upgrade serializer to new construct This provides with a few options, as summarized in the docstrings. --- addon/mixins/serializer.js | 61 -------------------- addon/serializer/data-table.js | 102 +++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 61 deletions(-) delete mode 100644 addon/mixins/serializer.js create mode 100644 addon/serializer/data-table.js diff --git a/addon/mixins/serializer.js b/addon/mixins/serializer.js deleted file mode 100644 index f36939b..0000000 --- a/addon/mixins/serializer.js +++ /dev/null @@ -1,61 +0,0 @@ -import Mixin from '@ember/object/mixin'; - -export default Mixin.create({ - /** - Parse the links in the JSONAPI response and convert to a meta-object - */ - normalizeQueryResponse(store, clazz, payload) { - const result = this._super(...arguments); - result.meta = result.meta || {}; - - if (payload.links) { - result.meta.pagination = this.createPageMeta(payload.links); - } - if (payload.meta) { - result.meta.count = payload.meta.count; - } - - return result; - }, - - /** - Transforms link URLs to objects containing metadata - E.g. - { - previous: '/streets?page[number]=1&page[size]=10&sort=name - next: '/streets?page[number]=3&page[size]=10&sort=name - } - - will be converted to - - { - previous: { number: 1, size: 10 }, - next: { number: 3, size: 10 } - } - */ - createPageMeta(data) { - let meta = {}; - - Object.keys(data).forEach((type) => { - const link = data[type]; - meta[type] = {}; - - if (link) { - //extracts from '/path?foo=bar&baz=foo' the string: foo=bar&baz=foo - const query = link.split(/\?(.+)/)[1] || ''; - - query.split('&').forEach((pairs) => { - const [param, value] = pairs.split('='); - - if (decodeURIComponent(param) === 'page[number]') { - meta[type].number = parseInt(value); - } else if (decodeURIComponent(param) === 'page[size]') { - meta[type].size = parseInt(value); - } - }); - } - }); - - return meta; - }, -}); diff --git a/addon/serializer/data-table.js b/addon/serializer/data-table.js new file mode 100644 index 0000000..ac6b2b7 --- /dev/null +++ b/addon/serializer/data-table.js @@ -0,0 +1,102 @@ +import JSONAPISerializer from '@ember-data/serializer/json-api'; + +/** + * Transforms link URLs to objects containing metadata + * E.g. + * { + * previous: '/streets?page[number]=1&page[size]=10&sort=name + * next: '/streets?page[number]=3&page[size]=10&sort=name + * } + * will be converted to + * { + * previous: { number: 1, size: 10 }, + * next: { number: 3, size: 10 } + * } + */ +function createPageMeta(data) { + let meta = {}; + + Object.keys(data).forEach((type) => { + const link = data[type]; + meta[type] = {}; + + if (link) { + //extracts from '/path?foo=bar&baz=foo' the string: foo=bar&baz=foo + const query = link.split(/\?(.+)/)[1] || ''; + + query.split('&').forEach((pairs) => { + const [param, value] = pairs.split('='); + + if (decodeURIComponent(param) === 'page[number]') { + meta[type].number = parseInt(value); + } else if (decodeURIComponent(param) === 'page[size]') { + meta[type].size = parseInt(value); + } + }); + } + }); + + return meta; +} + + +/** + * Adds the meta content to the query result. + * + * This function can be used if you need to manually append the changes. + * For instance, if you also have other overrides in the serializer. + * + * @param result The result from normalizeQueryResponse. + * @param payload The payload supplied to normalizeQueryResponse. + * @return The manipulated result object. + */ +export function appendMetaToQueryResponse(result, payload) { + result.meta = result.meta || {}; + + if (payload.links) { + result.meta.pagination = createPageMeta(payload.links); + } + if (payload.meta) { + result.meta.count = payload.meta.count; + } + + return result; +} + +/** + * Decorator for the normalizeQueryResponse serializer method. + * + * Augments the call to the normalizeQueryResponse method with parsing + * of the payload to extract the page meta. This decorator can be used + * if the serializer itself could not be used directly. Alternatively, + * you can combine the calls yourself with appendMetaToQueryResponse + * (also exported from here) directly. + */ +export function withPageMeta(_target, _name, descriptor) { + const original = descriptor.value; + + descriptor.value = function(_store, _clazz, payload) { + const result = original.apply(this,arguments); + return appendMetaToQueryResponse(result, payload); + }; + + return descriptor; +} + +/** + * Serializer to be used for DataTable requests. + * + * By extending this, the query repsonses are parsed correctly. If you + * need to adapt further, or need to combine with other libraries, also + * take a peek at the withPageMeta decorator exported from here, as well + * as the appendMetaToQueryResponse function. + */ +export default class ApplicationSerializer extends JSONAPISerializer { + /** + * Parse the links in the JSONAPI response and convert to a meta-object + */ + @withPageMeta + normalizeQueryResponse() { + return super.normalizeQueryResponse(...arguments); + } +} From c7988595f3abf3230990db821eaeaddcb2be55d1 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sun, 23 Jan 2022 18:25:34 +0100 Subject: [PATCH 10/56] Named blocks for DataTable component This component is the first to receive support for named blocks. This allows a user to override only part of the component. The pattern that arrizes in the component is feasible to stare through and so we hope this may be a construction that we could iterate on for the other components too. --- addon/components/data-table.hbs | 98 +++++++++++++++++---------------- addon/serializer/data-table.js | 1 - 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 33fcafe..4779e91 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -1,51 +1,55 @@ -{{#if (has-block)}} -
+{{#let + (component "text-search" + filter=@filter + auto=this.autoSearch + updateFilter=this.updateFilter + wait=this.searchDebounceTime + placeholder=this.searchPlaceholder) + (component "data-table-content" + content=@content + noDataMessage=this.noDataMessage + enableSelection=this.enableSelection + enableLineNumbers=this.enableLineNumbers + onClickRow=@onClickRow + updateSort=this.updateSort + data-table=this) + (component "number-pagination" + page=@page + size=@size + nbOfItems=@content.length + sizeOptions=this.sizeOptions + total=@content.meta.count + links=@content.meta.pagination + updatePage=@updatePage + updateSize=this.updatePageSize) + as |search content pagination|}} + {{#if (has-block "default")}} + {{yield (hash search content pagination)}} + {{else}} {{#if this.enableSearch}} - {{text-search filter=this.filter auto=this.autoSearch wait=this.searchDebounceTime}} + {{#if (has-block "search")}} + {{yield search to="search"}} + {{else}} +
+
+ {{search}} +
+
+ {{/if}} {{/if}} - {{yield (hash - menu=(component "data-table-menu" data-table=this) - ) - this}} -
- {{yield (hash - content=(component "data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection enableLineNumbers=this.enableLineNumbers onClickRow=@onClickRow data-table=this) - ) - this}} {{!-- TODO: not sure about onClickRow --}} -{{else}} - {{#if this.enableSearch}} -
-
- -
-
- {{/if}} - {{!-- TODO: not sure about onClickRow --}} -{{/if}} + {{#if (has-block "content")}} + {{yield content to="content"}} + {{else}} + {{content}} + {{/if}} -{{#if @content}} - -{{/if}} + {{#if @content}} + {{#if (has-block "pagination")}} + {{yield pagination to="pagination"}} + {{else}} + {{pagination}} + {{/if}} + {{/if}} + {{/if}} +{{/let}} \ No newline at end of file diff --git a/addon/serializer/data-table.js b/addon/serializer/data-table.js index ac6b2b7..90bc5e4 100644 --- a/addon/serializer/data-table.js +++ b/addon/serializer/data-table.js @@ -39,7 +39,6 @@ function createPageMeta(data) { return meta; } - /** * Adds the meta content to the query result. * From 75cb4c98fc4d00f8977352b8d55c80862c1ad427 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sun, 23 Jan 2022 22:23:58 +0100 Subject: [PATCH 11/56] re-introduce menu This should allow users to use the menu once again. You don't need to extend the full component for using this anymore :) --- addon/components/data-table.hbs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 4779e91..7173de6 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -22,9 +22,11 @@ links=@content.meta.pagination updatePage=@updatePage updateSize=this.updatePageSize) - as |search content pagination|}} + (component "data-table-menu" + data-table=this) + as |search content pagination menu|}} {{#if (has-block "default")}} - {{yield (hash search content pagination)}} + {{yield (hash search content pagination menu)}} {{else}} {{#if this.enableSearch}} {{#if (has-block "search")}} @@ -38,6 +40,10 @@ {{/if}} {{/if}} + {{#if (has-block "menu")}} + {{yield menu to="menu"}} + {{/if}} + {{#if (has-block "content")}} {{yield content to="content"}} {{else}} From 0e36bc8c8d7f01e96d9d6cd2fc56e00d078f8799 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Mon, 24 Jan 2022 11:49:22 +0100 Subject: [PATCH 12/56] Allow search itself to be re-implemented based on parameters --- addon/components/text-search.hbs | 46 +++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/addon/components/text-search.hbs b/addon/components/text-search.hbs index 4625c9b..74c7994 100644 --- a/addon/components/text-search.hbs +++ b/addon/components/text-search.hbs @@ -1,16 +1,30 @@ - +{{#if (has-block)}} + {{yield (hash + submitForm=this.submitForm + placeholder=this.placeholder + handleAutoInput=this.handleAutoInput + handleDirectInput=this.handleDirectInput + filter=@filter + auto=@auto)}} +{{else}} + +{{/if}} From 96658e7a88283a35747dc39d479ccf01a2471d81 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Mon, 24 Jan 2022 16:27:44 +0100 Subject: [PATCH 13/56] Blindly allowing various components to be configured with overrides The idea is that we allow various slots to be used to override parts of the data table, allowing other parts to stay put. This structured could possibly be used as a guideline to styled versions of the ember-data-table. --- addon/components/data-table-content-body.hbs | 61 +++++++++++++------ .../components/data-table-content-header.hbs | 58 ++++++++++++------ addon/components/data-table-content.hbs | 56 ++++++++++------- addon/components/data-table-menu-selected.hbs | 20 ++++-- addon/components/data-table-menu.hbs | 20 ++++-- .../default-data-table-content-body.hbs | 61 +++++++++++++++---- addon/components/number-pagination.hbs | 61 ++++++++++++------- addon/components/th-sortable.hbs | 20 ++++-- 8 files changed, 245 insertions(+), 112 deletions(-) diff --git a/addon/components/data-table-content-body.hbs b/addon/components/data-table-content-body.hbs index 9d69def..1f71eab 100644 --- a/addon/components/data-table-content-body.hbs +++ b/addon/components/data-table-content-body.hbs @@ -1,29 +1,52 @@ {{#if @data-table.isLoading}} - Loading... + {{#if (has-block "loading")}} + {{yield to="loading"}} + {{else}} + Loading... + {{/if}} {{else}} {{#if @content}} {{#each this.wrappedItems as |wrapper index|}} - - {{#if @enableSelection}} - - {{input type="checkbox" checked=wrapper.isSelected click=(fn this.updateSelection wrapper)}} - - {{/if}} - {{#if @enableLineNumbers}} - {{add index offset}} - {{/if}} - {{#if (has-block)}} - {{yield wrapper.item}} - {{else}} - - {{/if}} - + {{#if (has-block "row")}} + {{yield (hash + wrapper=wrapper + item=item + enableLineNumbers=@enableLineNumbers + lineNumber=(add index offset) + selected=(includes wrapper.item @data-table.selection) + onClickRow=(and @onClickRow (fn this.onClickRow wrapper.item)) + toggleSelected=(fn this.updateSelection wrapper)) + to="row"}} + {{else}} + + {{#if @enableSelection}} + + {{input type="checkbox" checked=wrapper.isSelected click=(fn this.updateSelection wrapper)}} + + {{/if}} + {{#if @enableLineNumbers}} + {{add index offset}} + {{/if}} + {{#if (has-block "data-cells")}} + {{yield + (component "default-data-table-content-body" item=wrapper.item data-table=@data-table) + wrapper.item + to="data-cells"}} + {{else}} + + {{/if}} + + {{/if}} {{/each}} {{else}} -

{{@noDataMessage}}

+ {{#if (has-block "no-data-message")}} + {{yield to="no-data-message"}} + {{else}} +

{{@noDataMessage}}

+ {{/if}} {{/if}} {{/if}} diff --git a/addon/components/data-table-content-header.hbs b/addon/components/data-table-content-header.hbs index 35efca6..5b2fb09 100644 --- a/addon/components/data-table-content-header.hbs +++ b/addon/components/data-table-content-header.hbs @@ -1,17 +1,41 @@ - - - {{#if @enableSelection}} - {{!-- Checkbox --}} - {{/if}} - {{#if @enableLineNumbers}} - {{!-- Linenumbers --}} - {{/if}} - {{#if (has-block)}} - {{yield}} - {{else}} - {{#each @data-table.parsedFields as |field|}} - - {{/each}} - {{/if}} - - \ No newline at end of file +{{#if (has-block)}} + {{yield (hash + enableSelection=@enableSelection + enableLineNumbers=@enableLineNumbers + parsedFields=@data-table.parsedFields + currentSorting=@data-table.sort + updateSort=@updateSort)}} +{{else}} + + + {{#if @enableSelection}} + {{!-- Checkbox --}} + {{/if}} + {{#if @enableLineNumbers}} + {{!-- Linenumbers --}} + {{/if}} + {{#if (has-block "headers")}} + {{yield (hash + parsedFields=@data-table.parsedFields + currentSorting=@data-table.sort + updateSort=@updateSort) + to="headers"}} + {{else}} + {{#each @data-table.parsedFields as |field|}} + {{#let (component "th-sortable" + field=field + label=field + currentSorting=@data-table.sort + updateSort=@updateSort) + as |header|}} + {{#if (has-block "header")}} + {{yield header to="header"}} + {{else}} + {{header}} + {{/if}} + {{/let}} + {{/each}} + {{/if}} + + +{{/if}} diff --git a/addon/components/data-table-content.hbs b/addon/components/data-table-content.hbs index 985c886..795d5c3 100644 --- a/addon/components/data-table-content.hbs +++ b/addon/components/data-table-content.hbs @@ -1,24 +1,34 @@ +{{#let + (component "data-table-content-header" + enableSelection=@enableSelection + enableLineNumbers=@enableLineNumbers + updateSort=@updateSort + data-table=@data-table) + (component "data-table-content-body" + content=@content + enableSelection=@enableSelection + enableLineNumbers=@enableLineNumbers + noDataMessage=@noDataMessage + onClickRow=@onClickRow + data-table=@data-table) + as |header body|}} {{! template-lint-disable table-groups }} -
- - {{#if (has-block)}} - {{yield (hash - header=(component "data-table-content-header" enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers data-table=@data-table) - body=(component "data-table-content-body" content=@content enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers noDataMessage=@noDataMessage onClickRow=@onClickRow data-table=@data-table) - )}} - {{else}} - - - {{/if}} -
-
\ No newline at end of file + {{#if (has-block)}} + {{yield (hash header body)}} + {{else}} +
+ + {{#if (has-block "header")}} + {{yield header to="header"}} + {{else}} + {{header}} + {{/if}} + {{#if (has-block "body")}} + {{yield body to="body"}} + {{else}} + {{body}} + {{/if}} +
+
+ {{/if}} +{{/let}} \ No newline at end of file diff --git a/addon/components/data-table-menu-selected.hbs b/addon/components/data-table-menu-selected.hbs index 49bce12..684cfb8 100644 --- a/addon/components/data-table-menu-selected.hbs +++ b/addon/components/data-table-menu-selected.hbs @@ -1,5 +1,15 @@ -{{#unless @data-table.selectionIsEmpty}} - {{this.selectionCount}} item(s) selected - - {{yield (slice 0 this.selectionCount @data-table.selection) @data-table}} -{{/unless}} +{{#if (has-block)}} + {{yield (hash + selectionIsEmpty=@data-table.selectionIsEmpty + selectionCount=this.selectionCount + clearSelection=this.clearSelection + selection=@data-table.selection + data-table=@data-table)}} {{!-- TODO: must we pass the data table itself? --}} +{{else}} + {{#unless @data-table.selectionIsEmpty}} + {{this.selectionCount}} item(s) selected + + {{yield (slice 0 this.selectionCount @data-table.selection) @data-table + to="actions"}} + {{/unless}} +{{/if}} \ No newline at end of file diff --git a/addon/components/data-table-menu.hbs b/addon/components/data-table-menu.hbs index 7a16603..bb7b5a6 100644 --- a/addon/components/data-table-menu.hbs +++ b/addon/components/data-table-menu.hbs @@ -1,6 +1,14 @@ -
- {{yield (hash - general=(component "data-table-menu-general" data-table=@data-table) - selected=(component "data-table-menu-selected" data-table=@data-table) - )}} -
+{{#let + (component "data-table-menu-general" data-table=@data-table) + (component "data-table-menu-selected" data-table=@data-table) + as |general selected|}} + {{#if (has-block)}} + {{yield (hash general selected)}} + {{else}} +
+ {{!-- either we have a general block or we have to have a menu --}} + {{yield general to="general"}} + {{yield selected to="selected"}} +
+ {{/if}} +{{/let}} \ No newline at end of file diff --git a/addon/components/default-data-table-content-body.hbs b/addon/components/default-data-table-content-body.hbs index 6c8ecf3..97ae872 100644 --- a/addon/components/default-data-table-content-body.hbs +++ b/addon/components/default-data-table-content-body.hbs @@ -1,15 +1,50 @@ -{{#if this.firstColumn}} - {{#if this.linkedRoute}} - {{#link-to this.linkedRoute @item tagName="td"}}{{get @item this.firstColumn}}{{/link-to}} +{{#let (hash + columns=@data-table.parsedFields + firstColumn=this.firstColumn + otherColumns=this.otherColumns + item=@item + linkedRoute=this.linkedRoute) + as |d|}} + {{#if (has-block)}} + {{yield d}} {{else}} - {{get @item this.firstColumn}} + {{#if d.firstColumn}} + {{#if (has-block "data-cell")}} + {{yield (hash + columns=d.columns + firstColumn=d.firstColumn + otherColumns=d.otherColumns + item=d.item + linkedRoute=d.linkedRoute + field=(get @item d.firstColumn)) + to="data-cell"}} + {{else}} + {{#if d.linkedRoute}} + {{#link-to d.linkedRoute d.item tagName="td"}}{{get d.item d.firstColumn}}{{/link-to}} + {{else}} + {{get d.item d.firstColumn}} + {{/if}} + {{/if}} + {{/if}} + {{#each d.otherColumns as |field|}} + {{#if (has-block "data-cell")}} + {{#let + (hash + columns=d.columns + firstColumn=d.firstColumn + otherColumns=d.otherColumns + item=d.item + linkedRoute=d.linkedRoute + field=(get @item field)) + as |data-cell-data|}} + {{yield data-cell-data to="data-cell"}} + {{/let}} + {{else}} + + {{!-- TODO: This should be based on the type of the field --}} + {{get @item field}} + + {{/if}} + {{/each}} {{/if}} -{{/if}} -{{#each this.otherColumns as |field|}} - - {{!-- TODO: This should be based on the type of the field --}} - {{get @item field}} - -{{/each}} -{{yield}} - +{{/let}} \ No newline at end of file diff --git a/addon/components/number-pagination.hbs b/addon/components/number-pagination.hbs index 6f93fed..16108ef 100644 --- a/addon/components/number-pagination.hbs +++ b/addon/components/number-pagination.hbs @@ -1,25 +1,40 @@ -
-
- Displaying {{this.startItem}}-{{this.endItem}} - {{#if @total}} of {{@total}}{{/if}} - {{#if @sizeOptions}} - | - +{{#if (has-block)}} + {{yield (hash + startItem=this.startItem + endItem=this.endItem + total=@total + sizeOptions=@sizeOptions + updatePage=this.updatePage + selectSizeOption=this.selectSizeOption + sizeOptions=@sizeOptions + hasMultiplePages=this.hasMultiplePages + isFirstPage=this.isFirstPage + isLastPage=this.isLastPage + links=@links)}} +{{else}} +
+
+ Displaying {{this.startItem}}-{{this.endItem}} + {{#if @total}} of {{@total}}{{/if}} + {{#if @sizeOptions}} + | + + {{/if}} +
+ {{#if this.hasMultiplePages}} +
+ + + + + +
{{/if}}
- {{#if this.hasMultiplePages}} -
- - - - - -
- {{/if}} -
\ No newline at end of file +{{/if}} \ No newline at end of file diff --git a/addon/components/th-sortable.hbs b/addon/components/th-sortable.hbs index 62134ba..6af02ec 100644 --- a/addon/components/th-sortable.hbs +++ b/addon/components/th-sortable.hbs @@ -1,6 +1,14 @@ - - - {{#if this.order}}[{{this.order}}]{{/if}} - {{@label}} - - +{{#if (has-block)}} + {{yield (hash + isSorted=this.isSorted + inverseSorting=this.inverseSorting + label=@label + order=this.order)}} +{{else}} + + + {{#if this.order}}[{{this.order}}]{{/if}} + {{@label}} + + +{{/if}} From 317ed23e3262f80fa60c4428cfba02f4d26df381 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Mon, 24 Jan 2022 16:36:32 +0100 Subject: [PATCH 14/56] Scope data table subcomponents to data-table namespace --- addon/components/data-table.hbs | 8 ++++---- .../{ => data-table}/data-table-content-body.hbs | 4 ++-- .../{ => data-table}/data-table-content-body.js | 0 .../{ => data-table}/data-table-content-header.hbs | 2 +- .../{ => data-table}/data-table-content-header.js | 0 addon/components/{ => data-table}/data-table-content.hbs | 4 ++-- addon/components/{ => data-table}/data-table-content.js | 0 .../{ => data-table}/data-table-menu-general.hbs | 0 .../{ => data-table}/data-table-menu-general.js | 0 .../{ => data-table}/data-table-menu-selected.hbs | 0 .../{ => data-table}/data-table-menu-selected.js | 0 addon/components/{ => data-table}/data-table-menu.hbs | 4 ++-- addon/components/{ => data-table}/data-table-menu.js | 0 .../{ => data-table}/default-data-table-content-body.hbs | 0 .../{ => data-table}/default-data-table-content-body.js | 0 addon/components/{ => data-table}/number-pagination.hbs | 0 addon/components/{ => data-table}/number-pagination.js | 0 addon/components/{ => data-table}/text-search.hbs | 0 addon/components/{ => data-table}/text-search.js | 0 addon/components/{ => data-table}/th-sortable.hbs | 0 addon/components/{ => data-table}/th-sortable.js | 0 app/components/data-table-content-body.js | 1 - app/components/data-table-content-header.js | 1 - app/components/data-table-content.js | 1 - app/components/data-table-menu-general.js | 1 - app/components/data-table-menu-selected.js | 1 - app/components/data-table-menu.js | 1 - app/components/data-table/data-table-content-body.js | 1 + app/components/data-table/data-table-content-header.js | 1 + app/components/data-table/data-table-content.js | 1 + app/components/data-table/data-table-menu-general.js | 1 + app/components/data-table/data-table-menu-selected.js | 1 + app/components/data-table/data-table-menu.js | 1 + .../data-table/default-data-table-content-body.js | 1 + app/components/data-table/number-pagination.js | 1 + app/components/data-table/text-search.js | 1 + app/components/data-table/th-sortable.js | 1 + app/components/default-data-table-content-body.js | 1 - app/components/number-pagination.js | 1 - app/components/text-search.js | 1 - app/components/th-sortable.js | 1 - 41 files changed, 21 insertions(+), 21 deletions(-) rename addon/components/{ => data-table}/data-table-content-body.hbs (88%) rename addon/components/{ => data-table}/data-table-content-body.js (100%) rename addon/components/{ => data-table}/data-table-content-header.hbs (95%) rename addon/components/{ => data-table}/data-table-content-header.js (100%) rename addon/components/{ => data-table}/data-table-content.hbs (89%) rename addon/components/{ => data-table}/data-table-content.js (100%) rename addon/components/{ => data-table}/data-table-menu-general.hbs (100%) rename addon/components/{ => data-table}/data-table-menu-general.js (100%) rename addon/components/{ => data-table}/data-table-menu-selected.hbs (100%) rename addon/components/{ => data-table}/data-table-menu-selected.js (100%) rename addon/components/{ => data-table}/data-table-menu.hbs (67%) rename addon/components/{ => data-table}/data-table-menu.js (100%) rename addon/components/{ => data-table}/default-data-table-content-body.hbs (100%) rename addon/components/{ => data-table}/default-data-table-content-body.js (100%) rename addon/components/{ => data-table}/number-pagination.hbs (100%) rename addon/components/{ => data-table}/number-pagination.js (100%) rename addon/components/{ => data-table}/text-search.hbs (100%) rename addon/components/{ => data-table}/text-search.js (100%) rename addon/components/{ => data-table}/th-sortable.hbs (100%) rename addon/components/{ => data-table}/th-sortable.js (100%) delete mode 100644 app/components/data-table-content-body.js delete mode 100644 app/components/data-table-content-header.js delete mode 100644 app/components/data-table-content.js delete mode 100644 app/components/data-table-menu-general.js delete mode 100644 app/components/data-table-menu-selected.js delete mode 100644 app/components/data-table-menu.js create mode 100644 app/components/data-table/data-table-content-body.js create mode 100644 app/components/data-table/data-table-content-header.js create mode 100644 app/components/data-table/data-table-content.js create mode 100644 app/components/data-table/data-table-menu-general.js create mode 100644 app/components/data-table/data-table-menu-selected.js create mode 100644 app/components/data-table/data-table-menu.js create mode 100644 app/components/data-table/default-data-table-content-body.js create mode 100644 app/components/data-table/number-pagination.js create mode 100644 app/components/data-table/text-search.js create mode 100644 app/components/data-table/th-sortable.js delete mode 100644 app/components/default-data-table-content-body.js delete mode 100644 app/components/number-pagination.js delete mode 100644 app/components/text-search.js delete mode 100644 app/components/th-sortable.js diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 7173de6..c854443 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -1,11 +1,11 @@ {{#let - (component "text-search" + (component "data-table/text-search" filter=@filter auto=this.autoSearch updateFilter=this.updateFilter wait=this.searchDebounceTime placeholder=this.searchPlaceholder) - (component "data-table-content" + (component "data-table/data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection @@ -13,7 +13,7 @@ onClickRow=@onClickRow updateSort=this.updateSort data-table=this) - (component "number-pagination" + (component "data-table/number-pagination" page=@page size=@size nbOfItems=@content.length @@ -22,7 +22,7 @@ links=@content.meta.pagination updatePage=@updatePage updateSize=this.updatePageSize) - (component "data-table-menu" + (component "data-table/data-table-menu" data-table=this) as |search content pagination menu|}} {{#if (has-block "default")}} diff --git a/addon/components/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs similarity index 88% rename from addon/components/data-table-content-body.hbs rename to addon/components/data-table/data-table-content-body.hbs index 1f71eab..e86c3a2 100644 --- a/addon/components/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -32,11 +32,11 @@ {{/if}} {{#if (has-block "data-cells")}} {{yield - (component "default-data-table-content-body" item=wrapper.item data-table=@data-table) + (component "data-table/default-data-table-content-body" item=wrapper.item data-table=@data-table) wrapper.item to="data-cells"}} {{else}} - + {{/if}} {{/if}} diff --git a/addon/components/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js similarity index 100% rename from addon/components/data-table-content-body.js rename to addon/components/data-table/data-table-content-body.js diff --git a/addon/components/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs similarity index 95% rename from addon/components/data-table-content-header.hbs rename to addon/components/data-table/data-table-content-header.hbs index 5b2fb09..40f2e78 100644 --- a/addon/components/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -22,7 +22,7 @@ to="headers"}} {{else}} {{#each @data-table.parsedFields as |field|}} - {{#let (component "th-sortable" + {{#let (component "data-table/th-sortable" field=field label=field currentSorting=@data-table.sort diff --git a/addon/components/data-table-content-header.js b/addon/components/data-table/data-table-content-header.js similarity index 100% rename from addon/components/data-table-content-header.js rename to addon/components/data-table/data-table-content-header.js diff --git a/addon/components/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs similarity index 89% rename from addon/components/data-table-content.hbs rename to addon/components/data-table/data-table-content.hbs index 795d5c3..817461d 100644 --- a/addon/components/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -1,10 +1,10 @@ {{#let - (component "data-table-content-header" + (component "data-table/data-table-content-header" enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers updateSort=@updateSort data-table=@data-table) - (component "data-table-content-body" + (component "data-table/data-table-content-body" content=@content enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers diff --git a/addon/components/data-table-content.js b/addon/components/data-table/data-table-content.js similarity index 100% rename from addon/components/data-table-content.js rename to addon/components/data-table/data-table-content.js diff --git a/addon/components/data-table-menu-general.hbs b/addon/components/data-table/data-table-menu-general.hbs similarity index 100% rename from addon/components/data-table-menu-general.hbs rename to addon/components/data-table/data-table-menu-general.hbs diff --git a/addon/components/data-table-menu-general.js b/addon/components/data-table/data-table-menu-general.js similarity index 100% rename from addon/components/data-table-menu-general.js rename to addon/components/data-table/data-table-menu-general.js diff --git a/addon/components/data-table-menu-selected.hbs b/addon/components/data-table/data-table-menu-selected.hbs similarity index 100% rename from addon/components/data-table-menu-selected.hbs rename to addon/components/data-table/data-table-menu-selected.hbs diff --git a/addon/components/data-table-menu-selected.js b/addon/components/data-table/data-table-menu-selected.js similarity index 100% rename from addon/components/data-table-menu-selected.js rename to addon/components/data-table/data-table-menu-selected.js diff --git a/addon/components/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs similarity index 67% rename from addon/components/data-table-menu.hbs rename to addon/components/data-table/data-table-menu.hbs index bb7b5a6..171462c 100644 --- a/addon/components/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -1,6 +1,6 @@ {{#let - (component "data-table-menu-general" data-table=@data-table) - (component "data-table-menu-selected" data-table=@data-table) + (component "data-table/data-table-menu-general" data-table=@data-table) + (component "data-table/data-table-menu-selected" data-table=@data-table) as |general selected|}} {{#if (has-block)}} {{yield (hash general selected)}} diff --git a/addon/components/data-table-menu.js b/addon/components/data-table/data-table-menu.js similarity index 100% rename from addon/components/data-table-menu.js rename to addon/components/data-table/data-table-menu.js diff --git a/addon/components/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs similarity index 100% rename from addon/components/default-data-table-content-body.hbs rename to addon/components/data-table/default-data-table-content-body.hbs diff --git a/addon/components/default-data-table-content-body.js b/addon/components/data-table/default-data-table-content-body.js similarity index 100% rename from addon/components/default-data-table-content-body.js rename to addon/components/data-table/default-data-table-content-body.js diff --git a/addon/components/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs similarity index 100% rename from addon/components/number-pagination.hbs rename to addon/components/data-table/number-pagination.hbs diff --git a/addon/components/number-pagination.js b/addon/components/data-table/number-pagination.js similarity index 100% rename from addon/components/number-pagination.js rename to addon/components/data-table/number-pagination.js diff --git a/addon/components/text-search.hbs b/addon/components/data-table/text-search.hbs similarity index 100% rename from addon/components/text-search.hbs rename to addon/components/data-table/text-search.hbs diff --git a/addon/components/text-search.js b/addon/components/data-table/text-search.js similarity index 100% rename from addon/components/text-search.js rename to addon/components/data-table/text-search.js diff --git a/addon/components/th-sortable.hbs b/addon/components/data-table/th-sortable.hbs similarity index 100% rename from addon/components/th-sortable.hbs rename to addon/components/data-table/th-sortable.hbs diff --git a/addon/components/th-sortable.js b/addon/components/data-table/th-sortable.js similarity index 100% rename from addon/components/th-sortable.js rename to addon/components/data-table/th-sortable.js diff --git a/app/components/data-table-content-body.js b/app/components/data-table-content-body.js deleted file mode 100644 index 8f7fe73..0000000 --- a/app/components/data-table-content-body.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/data-table-content-body'; diff --git a/app/components/data-table-content-header.js b/app/components/data-table-content-header.js deleted file mode 100644 index 44b4a48..0000000 --- a/app/components/data-table-content-header.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/data-table-content-header'; diff --git a/app/components/data-table-content.js b/app/components/data-table-content.js deleted file mode 100644 index d1f2b66..0000000 --- a/app/components/data-table-content.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/data-table-content'; diff --git a/app/components/data-table-menu-general.js b/app/components/data-table-menu-general.js deleted file mode 100644 index 0b647fd..0000000 --- a/app/components/data-table-menu-general.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/data-table-menu-general'; diff --git a/app/components/data-table-menu-selected.js b/app/components/data-table-menu-selected.js deleted file mode 100644 index 0e2877e..0000000 --- a/app/components/data-table-menu-selected.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/data-table-menu-selected'; diff --git a/app/components/data-table-menu.js b/app/components/data-table-menu.js deleted file mode 100644 index 423d082..0000000 --- a/app/components/data-table-menu.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/data-table-menu'; diff --git a/app/components/data-table/data-table-content-body.js b/app/components/data-table/data-table-content-body.js new file mode 100644 index 0000000..8ce1b62 --- /dev/null +++ b/app/components/data-table/data-table-content-body.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-table-content-body'; diff --git a/app/components/data-table/data-table-content-header.js b/app/components/data-table/data-table-content-header.js new file mode 100644 index 0000000..ad8e187 --- /dev/null +++ b/app/components/data-table/data-table-content-header.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-table-content-header'; diff --git a/app/components/data-table/data-table-content.js b/app/components/data-table/data-table-content.js new file mode 100644 index 0000000..672dc51 --- /dev/null +++ b/app/components/data-table/data-table-content.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-table-content'; diff --git a/app/components/data-table/data-table-menu-general.js b/app/components/data-table/data-table-menu-general.js new file mode 100644 index 0000000..ecb0364 --- /dev/null +++ b/app/components/data-table/data-table-menu-general.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-table-menu-general'; diff --git a/app/components/data-table/data-table-menu-selected.js b/app/components/data-table/data-table-menu-selected.js new file mode 100644 index 0000000..d216170 --- /dev/null +++ b/app/components/data-table/data-table-menu-selected.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-table-menu-selected'; diff --git a/app/components/data-table/data-table-menu.js b/app/components/data-table/data-table-menu.js new file mode 100644 index 0000000..84a697d --- /dev/null +++ b/app/components/data-table/data-table-menu.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-table-menu'; diff --git a/app/components/data-table/default-data-table-content-body.js b/app/components/data-table/default-data-table-content-body.js new file mode 100644 index 0000000..bd3f185 --- /dev/null +++ b/app/components/data-table/default-data-table-content-body.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/default-data-table-content-body'; diff --git a/app/components/data-table/number-pagination.js b/app/components/data-table/number-pagination.js new file mode 100644 index 0000000..3232643 --- /dev/null +++ b/app/components/data-table/number-pagination.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/number-pagination'; diff --git a/app/components/data-table/text-search.js b/app/components/data-table/text-search.js new file mode 100644 index 0000000..26875e3 --- /dev/null +++ b/app/components/data-table/text-search.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/text-search'; diff --git a/app/components/data-table/th-sortable.js b/app/components/data-table/th-sortable.js new file mode 100644 index 0000000..057b751 --- /dev/null +++ b/app/components/data-table/th-sortable.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/th-sortable'; diff --git a/app/components/default-data-table-content-body.js b/app/components/default-data-table-content-body.js deleted file mode 100644 index d9892cf..0000000 --- a/app/components/default-data-table-content-body.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/default-data-table-content-body'; diff --git a/app/components/number-pagination.js b/app/components/number-pagination.js deleted file mode 100644 index 236d179..0000000 --- a/app/components/number-pagination.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/number-pagination'; diff --git a/app/components/text-search.js b/app/components/text-search.js deleted file mode 100644 index 43016fd..0000000 --- a/app/components/text-search.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/text-search'; diff --git a/app/components/th-sortable.js b/app/components/th-sortable.js deleted file mode 100644 index 54c6aec..0000000 --- a/app/components/th-sortable.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'ember-data-table/components/th-sortable'; From 1dafa43540db62baa1b960a0df655fe277f95f28 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Mon, 24 Jan 2022 22:57:52 +0100 Subject: [PATCH 15/56] Extracting all state from the current templates into a big one The big template would make replacing parts easier from a consumer's point of view. It does mean we have a single very large template with all visible options embedded in it. Question would then be if this is easier to maintain and manage than the alternative with many components. --- addon/components/data-table.hbs | 49 +-- addon/components/data-table.js | 43 ++- .../data-table/data-table-content-body.hbs | 65 +--- .../data-table/data-table-content-body.js | 15 +- .../data-table/data-table-content-header.hbs | 47 +-- .../data-table/data-table-content-header.js | 2 +- .../data-table/data-table-content.hbs | 49 +-- .../data-table/data-table-content.js | 2 +- .../data-table/data-table-menu-selected.hbs | 23 +- .../data-table/data-table-menu-selected.js | 6 +- .../components/data-table/data-table-menu.hbs | 10 +- .../default-data-table-content-body.hbs | 50 +-- .../default-data-table-content-body.js | 16 +- .../data-table/number-pagination.hbs | 54 +--- .../data-table/number-pagination.js | 4 +- addon/components/data-table/text-search.hbs | 37 +-- addon/components/data-table/text-search.js | 5 +- addon/components/data-table/th-sortable.hbs | 19 +- addon/components/data-table/th-sortable.js | 2 +- addon/components/raw-data-table.hbs | 288 ++++++++++++++++++ addon/serializer/data-table.js | 8 +- app/components/raw-data-table.js | 1 + .../components/raw-data-table-test.js | 26 ++ 23 files changed, 444 insertions(+), 377 deletions(-) create mode 100644 addon/components/raw-data-table.hbs create mode 100644 app/components/raw-data-table.js create mode 100644 tests/integration/components/raw-data-table-test.js diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index c854443..d132b3b 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -1,11 +1,12 @@ -{{#let - (component "data-table/text-search" +{{yield + (hash + Search=(component "data-table/text-search" filter=@filter auto=this.autoSearch updateFilter=this.updateFilter wait=this.searchDebounceTime placeholder=this.searchPlaceholder) - (component "data-table/data-table-content" + Content=(component "data-table/data-table-content" content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection @@ -13,7 +14,7 @@ onClickRow=@onClickRow updateSort=this.updateSort data-table=this) - (component "data-table/number-pagination" + Pagination=(component "data-table/number-pagination" page=@page size=@size nbOfItems=@content.length @@ -22,40 +23,8 @@ links=@content.meta.pagination updatePage=@updatePage updateSize=this.updatePageSize) - (component "data-table/data-table-menu" + Menu=(component "data-table/data-table-menu" data-table=this) - as |search content pagination menu|}} - {{#if (has-block "default")}} - {{yield (hash search content pagination menu)}} - {{else}} - {{#if this.enableSearch}} - {{#if (has-block "search")}} - {{yield search to="search"}} - {{else}} -
-
- {{search}} -
-
- {{/if}} - {{/if}} - - {{#if (has-block "menu")}} - {{yield menu to="menu"}} - {{/if}} - - {{#if (has-block "content")}} - {{yield content to="content"}} - {{else}} - {{content}} - {{/if}} - - {{#if @content}} - {{#if (has-block "pagination")}} - {{yield pagination to="pagination"}} - {{else}} - {{pagination}} - {{/if}} - {{/if}} - {{/if}} -{{/let}} \ No newline at end of file + content=@content + enableSearch=this.enableSearch + dataTable=this)}} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index e9a8f94..9701bcf 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -9,7 +9,7 @@ export default class DataTable extends Component { get selection() { if (this._selection === undefined - && !('selection' in this.args)) + && (this.args.selection === undefined)) return []; else if (this._selection !== undefined) return this._selection; @@ -22,9 +22,9 @@ export default class DataTable extends Component { } get noDataMessage() { - return 'noDataMessage' in this.args - ? this.args.noDataMessage - : 'No data'; + return this.args.noDataMessage === undefined + ? "No data" + : this.args.noDataMessage; } get isLoading() { @@ -36,9 +36,9 @@ export default class DataTable extends Component { } get searchDebounceTime() { - return 'searchDebounceTime' in this.args - ? this.args.searchDebounceTime - : 2000; + return this.args.searchDebounceTime === undefined + ? 2000 + : this.args.searchDebounceTime; } get enableLineNumbers() { @@ -63,9 +63,9 @@ export default class DataTable extends Component { } get enableSizes() { - return 'enableSizes' in this.args - ? this.args.enableSizes - : true; + return this.args.enableSizes === undefined + ? true + : this.args.enableSizes; } get sort() { @@ -74,12 +74,9 @@ export default class DataTable extends Component { _size = undefined; get size() { - if (this._size === undefined && this.args.size) - return this.args.size; - else if (this._size) - return this._size; - else - return 5; + if (this._size === undefined && this.args.size) return this.args.size; + else if (this._size) return this._size; + else return 5; } set size(newSize) { this._size = newSize; @@ -101,13 +98,13 @@ export default class DataTable extends Component { @tracked hasMenu = false; // old comment: set from inner component, migth fail with nested if get enableSearch() { - return 'filter' in this.args; + return this.args.filter !== undefined; } get autoSearch() { - return 'autoSearch' in this.args - ? this.args.autoSearch - : true; + return this.args.autoSearch === undefined + ? true + : this.args.autoSearch; } get parsedFields() { @@ -120,9 +117,9 @@ export default class DataTable extends Component { } get searchPlaceholder() { - return "searchPlaceholder" in this.args - ? this.args.searchPlaceholder - : "Search input"; + return this.args.searchPlaceholder === undefined + ? 'Search input' + : this.args.searchPlaceholder; } @action diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index e86c3a2..ad55a5f 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -1,52 +1,13 @@ - - {{#if @data-table.isLoading}} - {{#if (has-block "loading")}} - {{yield to="loading"}} - {{else}} - Loading... - {{/if}} - {{else}} - {{#if @content}} - {{#each this.wrappedItems as |wrapper index|}} - {{#if (has-block "row")}} - {{yield (hash - wrapper=wrapper - item=item - enableLineNumbers=@enableLineNumbers - lineNumber=(add index offset) - selected=(includes wrapper.item @data-table.selection) - onClickRow=(and @onClickRow (fn this.onClickRow wrapper.item)) - toggleSelected=(fn this.updateSelection wrapper)) - to="row"}} - {{else}} - - {{#if @enableSelection}} - - {{input type="checkbox" checked=wrapper.isSelected click=(fn this.updateSelection wrapper)}} - - {{/if}} - {{#if @enableLineNumbers}} - {{add index offset}} - {{/if}} - {{#if (has-block "data-cells")}} - {{yield - (component "data-table/default-data-table-content-body" item=wrapper.item data-table=@data-table) - wrapper.item - to="data-cells"}} - {{else}} - - {{/if}} - - {{/if}} - {{/each}} - {{else}} - {{#if (has-block "no-data-message")}} - {{yield to="no-data-message"}} - {{else}} -

{{@noDataMessage}}

- {{/if}} - {{/if}} - {{/if}} - +{{yield (hash + isLoading=@data-table.isLoading + content=@content + offset=this.offset + wrappedItems=this.wrappedItems + enableLineNumbers=@enableLineNumbers + hasClickRowAction=(and @onClickRow true) + onClickRow=this.onClickRow + toggleSelected=this.updateSelection + selection=@data-table.selection + enableSelection=@enableSelection + noDataMessage=@noDataMessage + DataCells=(component "data-table/default-data-table-content-body" data-table=@data-table))}} diff --git a/addon/components/data-table/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js index 8b663e1..81bfb0f 100644 --- a/addon/components/data-table/data-table-content-body.js +++ b/addon/components/data-table/data-table-content-body.js @@ -4,8 +4,8 @@ import Component from '@glimmer/component'; export default class DataTableContentBodyComponent extends Component { get offset() { var offset = 1; //to avoid having 0. row - var page = this.args["data-table"].page; // TODO: pass on page directly? - var size = this.args["data-table"].size; // TODO: pass on size directly? + var page = this.args['data-table'].page; // TODO: pass on page directly? + var size = this.args['data-table'].size; // TODO: pass on size directly? if (page && size) { offset += page * size; } @@ -13,9 +13,9 @@ export default class DataTableContentBodyComponent extends Component { } get wrappedItems() { - const selection = this.args["data-table"].selection || []; // TODO: should the data-table ensure this is an array? + const selection = this.args['data-table'].selection || []; // TODO: should the data-table ensure this is an array? const content = this.args.content; - return content.map(function(item) { + return content.map(function (item) { return { item: item, isSelected: selection.includes(item) }; }); } @@ -26,16 +26,15 @@ export default class DataTableContentBodyComponent extends Component { this.wrappedItems.forEach((wrapper) => { if (wrapper.isSelected) { - this.args["data-table"].addItemToSelection(wrapper.item); // TODO: pass on addItemToSelection directly? + this.args['data-table'].addItemToSelection(wrapper.item); // TODO: pass on addItemToSelection directly? } else { - this.arg["data-table"].removeItemFromSelection(wrapper.item); // TODO: pass on removeItemFromSelection directly? + this.arg['data-table'].removeItemFromSelection(wrapper.item); // TODO: pass on removeItemFromSelection directly? } }); } @action onClickRow() { - if( this.args.onClickRow ) - this.args.onClickRow(...arguments); + if (this.args.onClickRow) this.args.onClickRow(...arguments); } } diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index 40f2e78..fbd6ac1 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -1,41 +1,6 @@ -{{#if (has-block)}} - {{yield (hash - enableSelection=@enableSelection - enableLineNumbers=@enableLineNumbers - parsedFields=@data-table.parsedFields - currentSorting=@data-table.sort - updateSort=@updateSort)}} -{{else}} - - - {{#if @enableSelection}} - {{!-- Checkbox --}} - {{/if}} - {{#if @enableLineNumbers}} - {{!-- Linenumbers --}} - {{/if}} - {{#if (has-block "headers")}} - {{yield (hash - parsedFields=@data-table.parsedFields - currentSorting=@data-table.sort - updateSort=@updateSort) - to="headers"}} - {{else}} - {{#each @data-table.parsedFields as |field|}} - {{#let (component "data-table/th-sortable" - field=field - label=field - currentSorting=@data-table.sort - updateSort=@updateSort) - as |header|}} - {{#if (has-block "header")}} - {{yield header to="header"}} - {{else}} - {{header}} - {{/if}} - {{/let}} - {{/each}} - {{/if}} - - -{{/if}} +{{yield (hash + enableSelection=@enableSelection + enableLineNumbers=@enableLineNumbers + parsedFields=@data-table.parsedFields + currentSorting=@data-table.sort + updateSort=@updateSort)}} diff --git a/addon/components/data-table/data-table-content-header.js b/addon/components/data-table/data-table-content-header.js index b1690c5..e84986c 100644 --- a/addon/components/data-table/data-table-content-header.js +++ b/addon/components/data-table/data-table-content-header.js @@ -1,3 +1,3 @@ import Component from '@glimmer/component'; -export default class DataTableContentHeaderComponent extends Component { } +export default class DataTableContentHeaderComponent extends Component {} diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 817461d..0f25b37 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -1,34 +1,15 @@ -{{#let - (component "data-table/data-table-content-header" - enableSelection=@enableSelection - enableLineNumbers=@enableLineNumbers - updateSort=@updateSort - data-table=@data-table) - (component "data-table/data-table-content-body" - content=@content - enableSelection=@enableSelection - enableLineNumbers=@enableLineNumbers - noDataMessage=@noDataMessage - onClickRow=@onClickRow - data-table=@data-table) - as |header body|}} -{{! template-lint-disable table-groups }} - {{#if (has-block)}} - {{yield (hash header body)}} - {{else}} -
- - {{#if (has-block "header")}} - {{yield header to="header"}} - {{else}} - {{header}} - {{/if}} - {{#if (has-block "body")}} - {{yield body to="body"}} - {{else}} - {{body}} - {{/if}} -
-
- {{/if}} -{{/let}} \ No newline at end of file +{{yield + (hash + Header=(component "data-table/data-table-content-header" + enableSelection=@enableSelection + enableLineNumbers=@enableLineNumbers + updateSort=@updateSort + data-table=@data-table) + Body=(component "data-table/data-table-content-body" + content=@content + enableSelection=@enableSelection + enableLineNumbers=@enableLineNumbers + noDataMessage=@noDataMessage + onClickRow=@onClickRow + data-table=@data-table) + dataTable=@data-table)}} diff --git a/addon/components/data-table/data-table-content.js b/addon/components/data-table/data-table-content.js index 90d2001..b74cc53 100644 --- a/addon/components/data-table/data-table-content.js +++ b/addon/components/data-table/data-table-content.js @@ -1,3 +1,3 @@ import Component from '@glimmer/component'; -export default class DataTableContentComponent extends Component { } +export default class DataTableContentComponent extends Component {} diff --git a/addon/components/data-table/data-table-menu-selected.hbs b/addon/components/data-table/data-table-menu-selected.hbs index 684cfb8..f6606d1 100644 --- a/addon/components/data-table/data-table-menu-selected.hbs +++ b/addon/components/data-table/data-table-menu-selected.hbs @@ -1,15 +1,8 @@ -{{#if (has-block)}} - {{yield (hash - selectionIsEmpty=@data-table.selectionIsEmpty - selectionCount=this.selectionCount - clearSelection=this.clearSelection - selection=@data-table.selection - data-table=@data-table)}} {{!-- TODO: must we pass the data table itself? --}} -{{else}} - {{#unless @data-table.selectionIsEmpty}} - {{this.selectionCount}} item(s) selected - - {{yield (slice 0 this.selectionCount @data-table.selection) @data-table - to="actions"}} - {{/unless}} -{{/if}} \ No newline at end of file +{{yield (hash + selectionIsEmpty=@data-table.selectionIsEmpty + selectionCount=this.selectionCount + clearSelection=this.clearSelection + selection=@data-table.selection + data-table=@data-table)}} + +{{!-- TODO: must we pass the data table itself? It is shared with the consumers. --}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-menu-selected.js b/addon/components/data-table/data-table-menu-selected.js index 4ed3972..a627f38 100644 --- a/addon/components/data-table/data-table-menu-selected.js +++ b/addon/components/data-table/data-table-menu-selected.js @@ -4,15 +4,15 @@ import Component from '@glimmer/component'; export default class DataTableMenuSelectedComponent extends Component { constructor() { super(...arguments); - this.args["data-table"].enableSelection = true; // TODO: is this the best way to handle such case? + this.args['data-table'].enableSelection = true; // TODO: is this the best way to handle such case? } get selectionCount() { - return this.args["data-table"].selection.length; + return this.args['data-table'].selection.length; } @action clearSelection() { - this.args["data-table"].clearSelection(); + this.args['data-table'].clearSelection(); } } diff --git a/addon/components/data-table/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs index 171462c..9ad128d 100644 --- a/addon/components/data-table/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -2,13 +2,5 @@ (component "data-table/data-table-menu-general" data-table=@data-table) (component "data-table/data-table-menu-selected" data-table=@data-table) as |general selected|}} - {{#if (has-block)}} - {{yield (hash general selected)}} - {{else}} -
- {{!-- either we have a general block or we have to have a menu --}} - {{yield general to="general"}} - {{yield selected to="selected"}} -
- {{/if}} + {{yield general selected}} {{/let}} \ No newline at end of file diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 97ae872..102642a 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -1,50 +1,6 @@ -{{#let (hash - columns=@data-table.parsedFields +{{yield (hash + columns=@data-table.parsedFields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item - linkedRoute=this.linkedRoute) - as |d|}} - {{#if (has-block)}} - {{yield d}} - {{else}} - {{#if d.firstColumn}} - {{#if (has-block "data-cell")}} - {{yield (hash - columns=d.columns - firstColumn=d.firstColumn - otherColumns=d.otherColumns - item=d.item - linkedRoute=d.linkedRoute - field=(get @item d.firstColumn)) - to="data-cell"}} - {{else}} - {{#if d.linkedRoute}} - {{#link-to d.linkedRoute d.item tagName="td"}}{{get d.item d.firstColumn}}{{/link-to}} - {{else}} - {{get d.item d.firstColumn}} - {{/if}} - {{/if}} - {{/if}} - {{#each d.otherColumns as |field|}} - {{#if (has-block "data-cell")}} - {{#let - (hash - columns=d.columns - firstColumn=d.firstColumn - otherColumns=d.otherColumns - item=d.item - linkedRoute=d.linkedRoute - field=(get @item field)) - as |data-cell-data|}} - {{yield data-cell-data to="data-cell"}} - {{/let}} - {{else}} - - {{!-- TODO: This should be based on the type of the field --}} - {{get @item field}} - - {{/if}} - {{/each}} - {{/if}} -{{/let}} \ No newline at end of file + linkedRoute=this.linkedRoute)}} diff --git a/addon/components/data-table/default-data-table-content-body.js b/addon/components/data-table/default-data-table-content-body.js index 4c4522a..95913d6 100644 --- a/addon/components/data-table/default-data-table-content-body.js +++ b/addon/components/data-table/default-data-table-content-body.js @@ -2,20 +2,18 @@ import Component from '@glimmer/component'; export default class DefaultDataTableContentBodyComponent extends Component { get allFields() { - return this.args["data-table"].parsedFields; // TODO: pass directly? + return this.args['data-table'].parsedFields; // TODO: pass directly? } get firstColumn() { - const parsedFields = this.args["data-table"].parsedFields; - if( parsedFields.length > 0 ) - return parsedFields[0]; - else - return null; + const parsedFields = this.args['data-table'].parsedFields; + if (parsedFields.length > 0) return parsedFields[0]; + else return null; } get otherColumns() { - const parsedFields = this.args["data-table"].parsedFields; - if( parsedFields.length > 0 ) { + const parsedFields = this.args['data-table'].parsedFields; + if (parsedFields.length > 0) { let [, ...fields] = parsedFields; return fields; } else { @@ -24,6 +22,6 @@ export default class DefaultDataTableContentBodyComponent extends Component { } get linkedRoute() { - return this.args["data-table"].link; + return this.args['data-table'].link; } } diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index 16108ef..8cc12a7 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -1,40 +1,14 @@ -{{#if (has-block)}} - {{yield (hash - startItem=this.startItem - endItem=this.endItem - total=@total - sizeOptions=@sizeOptions - updatePage=this.updatePage - selectSizeOption=this.selectSizeOption - sizeOptions=@sizeOptions - hasMultiplePages=this.hasMultiplePages - isFirstPage=this.isFirstPage - isLastPage=this.isLastPage - links=@links)}} -{{else}} -
-
- Displaying {{this.startItem}}-{{this.endItem}} - {{#if @total}} of {{@total}}{{/if}} - {{#if @sizeOptions}} - | - - {{/if}} -
- {{#if this.hasMultiplePages}} -
- - - - - -
- {{/if}} -
-{{/if}} \ No newline at end of file +{{yield (hash + startItem=this.startItem + endItem=this.endItem + total=@total + size=@size + page=@page + sizeOptions=@sizeOptions + updatePage=this.updatePage + selectSizeOption=this.selectSizeOption + sizeOptions=@sizeOptions + hasMultiplePages=this.hasMultiplePages + isFirstPage=this.isFirstPage + isLastPage=this.isLastPage + links=@links)}} \ No newline at end of file diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index e408790..53785de 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -9,7 +9,7 @@ export default class NumberPaginationComponent extends Component { } set currentPage(newPage) { - this.args.updatePage( newPage - 1 ); + this.args.updatePage(newPage - 1); } get firstPage() { @@ -50,7 +50,7 @@ export default class NumberPaginationComponent extends Component { @action updatePage(link) { - this.args.updatePage( link?.number || 0 ); + this.args.updatePage(link?.number || 0); } @action diff --git a/addon/components/data-table/text-search.hbs b/addon/components/data-table/text-search.hbs index 74c7994..120eeae 100644 --- a/addon/components/data-table/text-search.hbs +++ b/addon/components/data-table/text-search.hbs @@ -1,30 +1,7 @@ -{{#if (has-block)}} - {{yield (hash - submitForm=this.submitForm - placeholder=this.placeholder - handleAutoInput=this.handleAutoInput - handleDirectInput=this.handleDirectInput - filter=@filter - auto=@auto)}} -{{else}} - -{{/if}} +{{yield (hash + submitForm=this.submitForm + placeholder=this.placeholder + handleAutoInput=this.handleAutoInput + handleDirectInput=this.handleDirectInput + filter=@filter + auto=@auto)}} diff --git a/addon/components/data-table/text-search.js b/addon/components/data-table/text-search.js index 22cd9a8..55d3c2f 100644 --- a/addon/components/data-table/text-search.js +++ b/addon/components/data-table/text-search.js @@ -14,8 +14,8 @@ export default class TextSearchComponent extends Component { } submitCurrent() { - if( !this.isDestroying && !this.isDestroyed ) { - this.args.updateFilter( this.enteredValue ); + if (!this.isDestroying && !this.isDestroyed) { + this.args.updateFilter(this.enteredValue); this.autoDebouncePid = undefined; } } @@ -25,7 +25,6 @@ export default class TextSearchComponent extends Component { cancel(this.autoDebouncePid); } - @action handleDirectInput(event) { this.enteredValue = event.target.value; diff --git a/addon/components/data-table/th-sortable.hbs b/addon/components/data-table/th-sortable.hbs index 6af02ec..ffe03e6 100644 --- a/addon/components/data-table/th-sortable.hbs +++ b/addon/components/data-table/th-sortable.hbs @@ -1,14 +1,5 @@ -{{#if (has-block)}} - {{yield (hash - isSorted=this.isSorted - inverseSorting=this.inverseSorting - label=@label - order=this.order)}} -{{else}} - - - {{#if this.order}}[{{this.order}}]{{/if}} - {{@label}} - - -{{/if}} +{{yield (hash + isSorted=this.isSorted + inverseSorting=this.inverseSorting + label=@label + order=this.order)}} diff --git a/addon/components/data-table/th-sortable.js b/addon/components/data-table/th-sortable.js index 5a0ade1..a8a2498 100644 --- a/addon/components/data-table/th-sortable.js +++ b/addon/components/data-table/th-sortable.js @@ -48,7 +48,7 @@ export default class ThSortableComponent extends Component { if (this.order === 'asc') { this.args.updateSort(this._inverseSorting(this.args.currentSorting)); } else if (this.order === 'desc') { - this.args.updateSort(""); + this.args.updateSort(''); } else { // if currentSorting is not set to this field this.args.updateSort(this.dasherizedField); diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs new file mode 100644 index 0000000..a8e6813 --- /dev/null +++ b/addon/components/raw-data-table.hbs @@ -0,0 +1,288 @@ + + {{!-- START: search --}} + {{#if dt.enableSearch}} + + {{#if (has-block "search")}} + {{yield search to="search"}} + {{else}} +
+
+ +
+
+ {{/if}} +
+ {{/if}} + {{!-- END: search --}} + + {{!-- START: menu --}} + + {{#if (has-block "menu")}} + {{yield (hash General Selected)}} + {{else}} + {{#if dt.dataTable.enableSelection}} +
+ {{!-- either we have a general block or we have to have a menu --}} + + {{#if general.data-table.selectionIsEmpty}} + {{yield general.data-table to="general-menu"}} + {{/if}} + + + {{#unless selected.data-table.selectionIsEmpty}} + {{#if (has-block "selection-menu")}} + {{yield selected to="selection-menu"}} + {{else}} + {{selected.selectionCount}} item(s) selected + + {{yield (slice 0 selected.selectionCount selected.selection) selected.data-table + to="selection-menu-actions"}} + {{/if}} + {{/unless}} + +
+ {{/if}} + {{/if}} +
+ + {{!-- END: menu --}} + + {{!-- START: content --}} + + + {{#if (has-block "content")}} + {{yield content to="content"}} + {{else}} +
+ + {{!-- START: headers --}} + + {{#if (has-block "full-header")}} + {{yield header to="full-header"}} + {{else}} + + + {{#if header.enableSelection}} + + {{/if}} + {{#if header.enableLineNumbers}} + + {{/if}} + {{#if (has-block "data-headers")}} + {{yield (hash + parsedFields=header.parsedFields + currentSorting=header.currentSorting + updateSort=header.updateSort) + to="data-headers"}} + {{else}} + {{#each header.parsedFields as |field|}} + + {{#if (has-block "data-header")}} + {{yield dataHeader to="data-header"}} + {{else}} + + {{/if}} + + {{/each}} + {{/if}} + + + {{/if}} + + {{!-- END: headers --}} + + {{!-- START: body --}} + + {{#if (has-block "body")}} + {{yield body to="body"}} + {{else}} + + {{#if body.isLoading}} + {{#if (has-block "body-loading")}} + {{yield to="body-loading"}} + {{else}} + + {{/if}} + {{else}} + {{#if body.content}} + {{#each body.wrappedItems as |wrapper index|}} + {{#if (has-block "row")}} + {{yield (hash + wrapper=wrapper + item=wrapper.item + enableLineNumbers=body.enableLineNumbers + lineNumber=(add index body.offset) + selected=(includes wrapper.item body.selection) + hasClickRowAction=body.hasClickRowAction + onClickRow=(fn body.onClickRow wrapper.item) + toggleSelected=(fn body.toggleSelected wrapper)) + to="row"}} + {{else}} + + {{#if body.enableSelection}} + + {{/if}} + {{#if body.enableLineNumbers}} + + {{/if}} + + {{#if (has-block "data-cells")}} + {{yield dataCells to="data-cells"}} + {{else}} + {{#if dataCells.firstColumn}} + {{#if (has-block "first-data-cell")}} + {{yield (hash + columns=dataCells.columns + firstColumn=dataCells.firstColumn + otherColumns=dataCells.otherColumns + item=dataCells.item + linkedRoute=dataCells.linkedRoute + field=(get dataCells.item dataCells.firstColumn)) + to="first-data-cell"}} + {{else if (has-block "data-cell")}} + {{yield (hash + columns=dataCells.columns + firstColumn=dataCells.firstColumn + otherColumns=dataCells.otherColumns + item=dataCells.item + linkedRoute=dataCells.linkedRoute + field=(get dataCells.item dataCells.firstColumn)) + to="data-cell"}} + {{else}} + {{#if dataCells.linkedRoute}} + {{#link-to dataCells.linkedRoute dataCells.item tagName="td"}}{{get dataCells.item dataCells.firstColumn}}{{/link-to}} + {{else}} + + {{/if}} + {{/if}} + {{/if}} + {{#each dataCells.otherColumns as |field|}} + {{#let (hash + columns=dataCells.columns + firstColumn=dataCells.firstColumn + otherColumns=dataCells.otherColumns + item=dataCells.item + linkedRoute=dataCells.linkedRoute + field=(get dataCells.item field)) + as |data-cell-data|}} + {{#if (has-block "rest-data-cell")}} + {{yield data-cell-data to="rest-data-cell"}} + {{else if (has-block "data-cell")}} + {{yield data-cell-data to="rest-data-cell"}} + {{else}} + + {{/if}} + {{/let}} + {{/each}} + {{/if}} + + + {{/if}} + {{/each}} + {{else}} + {{#if (has-block "no-data-message")}} + {{yield to="no-data-message"}} + {{else}} + + {{/if}} + {{/if}} + {{/if}} + + {{/if}} + + {{!-- END: body --}} +
{{!-- Checkbox --}}{{!-- Linenumbers --}} + + {{#if dataHeader.order}}[{{dataHeader.order}}]{{/if}} + {{dataHeader.label}} + +
Loading...
+ {{input type="checkbox" checked=wrapper.isSelected click=(fn body.toggleSelected wrapper)}} + {{add index body.offset}}{{get dataCells.item dataCells.firstColumn}} + {{!-- TODO: This should be based on the type of the field --}} + {{get dataCells.item field}} +

{{@noDataMessage}}

+
+ {{/if}} +
+ + {{!-- END: content --}} + + {{!-- START: pagination --}} + + {{#if @content}} + {{#if (has-block "pagination")}} + {{yield pagination to="pagination"}} + {{else}} +
+
+ Displaying {{pagination.startItem}}-{{pagination.endItem}} + {{#if pagination.total}} of {{pagination.total}}{{/if}} + {{#if pagination.sizeOptions}} + | + + {{/if}} +
+ {{#if pagination.hasMultiplePages}} +
+ + + + + +
+ {{/if}} +
+ {{/if}} + {{/if}} +
+ {{!-- END: pagination --}} +
\ No newline at end of file diff --git a/addon/serializer/data-table.js b/addon/serializer/data-table.js index 90bc5e4..2567f13 100644 --- a/addon/serializer/data-table.js +++ b/addon/serializer/data-table.js @@ -74,8 +74,8 @@ export function appendMetaToQueryResponse(result, payload) { export function withPageMeta(_target, _name, descriptor) { const original = descriptor.value; - descriptor.value = function(_store, _clazz, payload) { - const result = original.apply(this,arguments); + descriptor.value = function (_store, _clazz, payload) { + const result = original.apply(this, arguments); return appendMetaToQueryResponse(result, payload); }; @@ -92,8 +92,8 @@ export function withPageMeta(_target, _name, descriptor) { */ export default class ApplicationSerializer extends JSONAPISerializer { /** - * Parse the links in the JSONAPI response and convert to a meta-object - */ + * Parse the links in the JSONAPI response and convert to a meta-object + */ @withPageMeta normalizeQueryResponse() { return super.normalizeQueryResponse(...arguments); diff --git a/app/components/raw-data-table.js b/app/components/raw-data-table.js new file mode 100644 index 0000000..ea25044 --- /dev/null +++ b/app/components/raw-data-table.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/raw-data-table'; diff --git a/tests/integration/components/raw-data-table-test.js b/tests/integration/components/raw-data-table-test.js new file mode 100644 index 0000000..6c72a99 --- /dev/null +++ b/tests/integration/components/raw-data-table-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | raw-data-table', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom(this.element).hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); From 96c325f934fc3f9aa81d816c39cdcedc51563135 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 25 Jan 2022 11:09:33 +0100 Subject: [PATCH 16/56] Further wrapping in components This approach should make the template a tad easier to understand as we have removed more information-passing logic into components that don't render output themselves. --- addon/components/data-table.js | 19 +-- addon/components/data-table/data-cell.hbs | 7 + .../data-table/data-table-content-body.hbs | 14 +- .../data-table/data-table-content-header.hbs | 10 +- .../default-data-table-content-body.hbs | 15 +- addon/components/data-table/row.hbs | 14 ++ addon/components/raw-data-table.hbs | 143 +++++++----------- app/components/data-table/data-cell.js | 1 + app/components/data-table/row.js | 1 + .../components/data-table/data-cell-test.js | 26 ++++ .../components/data-table/row-test.js | 26 ++++ 11 files changed, 168 insertions(+), 108 deletions(-) create mode 100644 addon/components/data-table/data-cell.hbs create mode 100644 addon/components/data-table/row.hbs create mode 100644 app/components/data-table/data-cell.js create mode 100644 app/components/data-table/row.js create mode 100644 tests/integration/components/data-table/data-cell-test.js create mode 100644 tests/integration/components/data-table/row-test.js diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 9701bcf..edf2180 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -8,13 +8,10 @@ export default class DataTable extends Component { @tracked _selection = undefined; get selection() { - if (this._selection === undefined - && (this.args.selection === undefined)) + if (this._selection === undefined && this.args.selection === undefined) return []; - else if (this._selection !== undefined) - return this._selection; - else - return this.args.selection; + else if (this._selection !== undefined) return this._selection; + else return this.args.selection; } set selection(newSelection) { @@ -23,7 +20,7 @@ export default class DataTable extends Component { get noDataMessage() { return this.args.noDataMessage === undefined - ? "No data" + ? 'No data' : this.args.noDataMessage; } @@ -63,9 +60,7 @@ export default class DataTable extends Component { } get enableSizes() { - return this.args.enableSizes === undefined - ? true - : this.args.enableSizes; + return this.args.enableSizes === undefined ? true : this.args.enableSizes; } get sort() { @@ -102,9 +97,7 @@ export default class DataTable extends Component { } get autoSearch() { - return this.args.autoSearch === undefined - ? true - : this.args.autoSearch; + return this.args.autoSearch === undefined ? true : this.args.autoSearch; } get parsedFields() { diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs new file mode 100644 index 0000000..2b4f81a --- /dev/null +++ b/addon/components/data-table/data-cell.hbs @@ -0,0 +1,7 @@ +{{yield (hash + columns=@columns + firstColumn=@firstColumn + otherColumns=@otherColumns + item=@item + linkedRoute=@linkedRoute + value=(get @item @column))}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index ad55a5f..7303821 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -10,4 +10,16 @@ selection=@data-table.selection enableSelection=@enableSelection noDataMessage=@noDataMessage - DataCells=(component "data-table/default-data-table-content-body" data-table=@data-table))}} + Row=(component "data-table/row" + data-table=@data-table + enableLineNumbers=@enableLineNumbers + enableSelection=@enableSelection + selection=@data-table.selection + offset=this.offset + hasClickRowAction=(and @onClickRow true) + onClickRow=this.onClickRow + toggleSelected=this.updateSelection))}} + +{{!-- + + --}} diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index fbd6ac1..4d59d47 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -3,4 +3,12 @@ enableLineNumbers=@enableLineNumbers parsedFields=@data-table.parsedFields currentSorting=@data-table.sort - updateSort=@updateSort)}} + updateSort=@updateSort + dataHeadersInfo=(hash + parsedFields=@data-table.parsedFields + currentSorting=@data-table.sort + updateSort=@updateSort) + ThSortable=(component + "data-table/th-sortable" + currentSorting=@data-table.sort + updateSort=@updateSort))}} \ No newline at end of file diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 102642a..338160f 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -1,6 +1,13 @@ {{yield (hash columns=@data-table.parsedFields - firstColumn=this.firstColumn - otherColumns=this.otherColumns - item=@item - linkedRoute=this.linkedRoute)}} + firstColumn=this.firstColumn + otherColumns=this.otherColumns + item=@item + linkedRoute=this.linkedRoute + DataCell=(component + "data-table/data-cell" + columns=@data-table.parsedFields + firstColumn=this.firstColumn + otherColumns=this.otherColumns + item=@item + linkedRoute=this.linkedRoute))}} diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs new file mode 100644 index 0000000..d065615 --- /dev/null +++ b/addon/components/data-table/row.hbs @@ -0,0 +1,14 @@ +{{yield (hash + wrapper=@wrapper + item=@wrapper.item + enableLineNumbers=@enableLineNumbers + lineNumber=(add @index @offset) + enableSelection=@enableSelection + selected=(includes @wrapper.item @selection) + hasClickRowAction=@hasClickRowAction + onClickRow=(fn @onClickRow @wrapper.item) + toggleSelected=(fn @toggleSelected @wrapper) + isSelected=(includes @wrapper.item @selection) + DataCells=(component + "data-table/default-data-table-content-body" + data-table=@data-table))}} \ No newline at end of file diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index a8e6813..60853ba 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -104,19 +104,10 @@ {{!-- Linenumbers --}} {{/if}} {{#if (has-block "data-headers")}} - {{yield (hash - parsedFields=header.parsedFields - currentSorting=header.currentSorting - updateSort=header.updateSort) - to="data-headers"}} + {{yield headers.dataHeadersInfo to="data-headers"}} {{else}} {{#each header.parsedFields as |field|}} - + {{#if (has-block "data-header")}} {{yield dataHeader to="data-header"}} {{else}} @@ -127,7 +118,7 @@ {{/if}} - + {{/each}} {{/if}} @@ -151,85 +142,59 @@ {{else}} {{#if body.content}} {{#each body.wrappedItems as |wrapper index|}} - {{#if (has-block "row")}} - {{yield (hash - wrapper=wrapper - item=wrapper.item - enableLineNumbers=body.enableLineNumbers - lineNumber=(add index body.offset) - selected=(includes wrapper.item body.selection) - hasClickRowAction=body.hasClickRowAction - onClickRow=(fn body.onClickRow wrapper.item) - toggleSelected=(fn body.toggleSelected wrapper)) - to="row"}} - {{else}} - - {{#if body.enableSelection}} - - {{input type="checkbox" checked=wrapper.isSelected click=(fn body.toggleSelected wrapper)}} - - {{/if}} - {{#if body.enableLineNumbers}} - {{add index body.offset}} - {{/if}} - - {{#if (has-block "data-cells")}} - {{yield dataCells to="data-cells"}} - {{else}} - {{#if dataCells.firstColumn}} - {{#if (has-block "first-data-cell")}} - {{yield (hash - columns=dataCells.columns - firstColumn=dataCells.firstColumn - otherColumns=dataCells.otherColumns - item=dataCells.item - linkedRoute=dataCells.linkedRoute - field=(get dataCells.item dataCells.firstColumn)) - to="first-data-cell"}} - {{else if (has-block "data-cell")}} - {{yield (hash - columns=dataCells.columns - firstColumn=dataCells.firstColumn - otherColumns=dataCells.otherColumns - item=dataCells.item - linkedRoute=dataCells.linkedRoute - field=(get dataCells.item dataCells.firstColumn)) - to="data-cell"}} - {{else}} - {{#if dataCells.linkedRoute}} - {{#link-to dataCells.linkedRoute dataCells.item tagName="td"}}{{get dataCells.item dataCells.firstColumn}}{{/link-to}} - {{else}} - {{get dataCells.item dataCells.firstColumn}} - {{/if}} + + {{#if (has-block "row")}} + {{yield row to="row"}} + {{else}} + + {{#if row.enableSelection}} + + {{input type="checkbox" checked=row.isSelected click=row.toggleSelected}} + + {{/if}} + {{#if row.enableLineNumbers}} + {{row.lineNumber}} + {{/if}} + + {{#if (has-block "data-cells")}} + {{yield dataCells to="data-cells"}} + {{else}} + {{#if dataCells.firstColumn}} + + {{#if (has-block "first-data-cell")}} + {{yield cell to="first-data-cell"}} + {{else if (has-block "data-cell")}} + {{yield cell to="data-cell"}} + {{else}} + {{#if cell.linkedRoute}} + {{#link-to cell.linkedRoute cell.item tagName="td"}}{{cell.value}}{{/link-to}} + {{else}} + {{cell.value}} + {{/if}} + {{/if}} + {{/if}} + {{#each dataCells.otherColumns as |column|}} + + {{#if (has-block "rest-data-cell")}} + {{yield cell to="rest-data-cell"}} + {{else if (has-block "data-cell")}} + {{yield cell to="data-cell"}} + {{else}} + + {{!-- TODO: This should be based on the type of the field --}} + {{cell.value}} + + {{/if}} + + {{/each}} {{/if}} - {{#each dataCells.otherColumns as |field|}} - {{#let (hash - columns=dataCells.columns - firstColumn=dataCells.firstColumn - otherColumns=dataCells.otherColumns - item=dataCells.item - linkedRoute=dataCells.linkedRoute - field=(get dataCells.item field)) - as |data-cell-data|}} - {{#if (has-block "rest-data-cell")}} - {{yield data-cell-data to="rest-data-cell"}} - {{else if (has-block "data-cell")}} - {{yield data-cell-data to="rest-data-cell"}} - {{else}} - - {{!-- TODO: This should be based on the type of the field --}} - {{get dataCells.item field}} - - {{/if}} - {{/let}} - {{/each}} - {{/if}} - - - {{/if}} + + + {{/if}} + {{/each}} {{else}} {{#if (has-block "no-data-message")}} diff --git a/app/components/data-table/data-cell.js b/app/components/data-table/data-cell.js new file mode 100644 index 0000000..11d7dae --- /dev/null +++ b/app/components/data-table/data-cell.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/data-cell'; diff --git a/app/components/data-table/row.js b/app/components/data-table/row.js new file mode 100644 index 0000000..2731d43 --- /dev/null +++ b/app/components/data-table/row.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/components/data-table/row'; diff --git a/tests/integration/components/data-table/data-cell-test.js b/tests/integration/components/data-table/data-cell-test.js new file mode 100644 index 0000000..ff4306f --- /dev/null +++ b/tests/integration/components/data-table/data-cell-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | data-table/data-cell', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom(this.element).hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); diff --git a/tests/integration/components/data-table/row-test.js b/tests/integration/components/data-table/row-test.js new file mode 100644 index 0000000..1139fe7 --- /dev/null +++ b/tests/integration/components/data-table/row-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | data-table/row', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom(this.element).hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); From 4fb34b95bc86f99fea28492831b1b055bec47787 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 25 Jan 2022 12:09:36 +0100 Subject: [PATCH 17/56] Wrap raw data table for css overrides --- addon/components/raw-data-table.hbs | 426 ++++++++++++++-------------- 1 file changed, 213 insertions(+), 213 deletions(-) diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 60853ba..091b4bc 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -16,238 +16,238 @@ @updateSort={{@updateSort}} as |dt|> {{!-- START: search --}} - {{#if dt.enableSearch}} - - {{#if (has-block "search")}} - {{yield search to="search"}} +
+ {{#if dt.enableSearch}} + + {{#if (has-block "search")}} + {{yield search to="search"}} + {{else}} +
+
+ +
+
+ {{/if}} +
+ {{/if}} + {{!-- END: search --}} + + {{!-- START: menu --}} + + {{#if (has-block "menu")}} + {{yield (hash General Selected)}} {{else}} -
+ {{#if dt.dataTable.enableSelection}}
- + + {{#unless selected.data-table.selectionIsEmpty}} + {{#if (has-block "selection-menu")}} + {{yield selected to="selection-menu"}} + {{else}} + {{selected.selectionCount}} item(s) selected + + {{yield (slice 0 selected.selectionCount selected.selection) selected.data-table + to="selection-menu-actions"}} + {{/if}} + {{/unless}} +
-
+ {{/if}} {{/if}} - - {{/if}} - {{!-- END: search --}} +
+ {{!-- END: menu --}} - {{!-- START: menu --}} - - {{#if (has-block "menu")}} - {{yield (hash General Selected)}} - {{else}} - {{#if dt.dataTable.enableSelection}} -
- {{!-- either we have a general block or we have to have a menu --}} - - {{#if general.data-table.selectionIsEmpty}} - {{yield general.data-table to="general-menu"}} - {{/if}} - - - {{#unless selected.data-table.selectionIsEmpty}} - {{#if (has-block "selection-menu")}} - {{yield selected to="selection-menu"}} + {{!-- START: content --}} + + {{#if (has-block "content")}} + {{yield content to="content"}} + {{else}} +
+ + {{!-- START: headers --}} + + {{#if (has-block "full-header")}} + {{yield header to="full-header"}} {{else}} - {{selected.selectionCount}} item(s) selected - - {{yield (slice 0 selected.selectionCount selected.selection) selected.data-table - to="selection-menu-actions"}} + + + {{#if header.enableSelection}} + + {{/if}} + {{#if header.enableLineNumbers}} + + {{/if}} + {{#if (has-block "data-headers")}} + {{yield headers.dataHeadersInfo to="data-headers"}} + {{else}} + {{#each header.parsedFields as |field|}} + + {{#if (has-block "data-header")}} + {{yield dataHeader to="data-header"}} + {{else}} + + {{/if}} + + {{/each}} + {{/if}} + + {{/if}} - {{/unless}} - - - {{/if}} - {{/if}} - - - {{!-- END: menu --}} - - {{!-- START: content --}} - - - {{#if (has-block "content")}} - {{yield content to="content"}} - {{else}} -
-
{{!-- Checkbox --}}{{!-- Linenumbers --}} + + {{#if dataHeader.order}}[{{dataHeader.order}}]{{/if}} + {{dataHeader.label}} + +
- {{!-- START: headers --}} - - {{#if (has-block "full-header")}} - {{yield header to="full-header"}} - {{else}} - - - {{#if header.enableSelection}} - - {{/if}} - {{#if header.enableLineNumbers}} - - {{/if}} - {{#if (has-block "data-headers")}} - {{yield headers.dataHeadersInfo to="data-headers"}} - {{else}} - {{#each header.parsedFields as |field|}} - - {{#if (has-block "data-header")}} - {{yield dataHeader to="data-header"}} - {{else}} - - {{/if}} - - {{/each}} - {{/if}} - - - {{/if}} - - {{!-- END: headers --}} + + {{!-- END: headers --}} - {{!-- START: body --}} - - {{#if (has-block "body")}} - {{yield body to="body"}} - {{else}} - - {{#if body.isLoading}} - {{#if (has-block "body-loading")}} - {{yield to="body-loading"}} + {{!-- START: body --}} + + {{#if (has-block "body")}} + {{yield body to="body"}} + {{else}} + + {{#if body.isLoading}} + {{#if (has-block "body-loading")}} + {{yield to="body-loading"}} + {{else}} + + {{/if}} {{else}} - - {{/if}} - {{else}} - {{#if body.content}} - {{#each body.wrappedItems as |wrapper index|}} - - {{#if (has-block "row")}} - {{yield row to="row"}} - {{else}} - - {{#if row.enableSelection}} - - {{/if}} - {{#if row.enableLineNumbers}} - - {{/if}} - - {{#if (has-block "data-cells")}} - {{yield dataCells to="data-cells"}} - {{else}} - {{#if dataCells.firstColumn}} - - {{#if (has-block "first-data-cell")}} - {{yield cell to="first-data-cell"}} - {{else if (has-block "data-cell")}} - {{yield cell to="data-cell"}} - {{else}} - {{#if cell.linkedRoute}} - {{#link-to cell.linkedRoute cell.item tagName="td"}}{{cell.value}}{{/link-to}} + {{#if body.content}} + {{#each body.wrappedItems as |wrapper index|}} + + {{#if (has-block "row")}} + {{yield row to="row"}} + {{else}} + + {{#if row.enableSelection}} + + {{/if}} + {{#if row.enableLineNumbers}} + + {{/if}} + + {{#if (has-block "data-cells")}} + {{yield dataCells to="data-cells"}} + {{else}} + {{#if dataCells.firstColumn}} + + {{#if (has-block "first-data-cell")}} + {{yield cell to="first-data-cell"}} + {{else if (has-block "data-cell")}} + {{yield cell to="data-cell"}} {{else}} - + {{#if cell.linkedRoute}} + {{#link-to cell.linkedRoute cell.item tagName="td"}}{{cell.value}}{{/link-to}} + {{else}} + + {{/if}} {{/if}} - {{/if}} - + + {{/if}} + {{#each dataCells.otherColumns as |column|}} + + {{#if (has-block "rest-data-cell")}} + {{yield cell to="rest-data-cell"}} + {{else if (has-block "data-cell")}} + {{yield cell to="data-cell"}} + {{else}} + + {{/if}} + + {{/each}} {{/if}} - {{#each dataCells.otherColumns as |column|}} - - {{#if (has-block "rest-data-cell")}} - {{yield cell to="rest-data-cell"}} - {{else if (has-block "data-cell")}} - {{yield cell to="data-cell"}} - {{else}} - - {{/if}} - - {{/each}} - {{/if}} - - - {{/if}} - - {{/each}} - {{else}} - {{#if (has-block "no-data-message")}} - {{yield to="no-data-message"}} + + + {{/if}} + + {{/each}} {{else}} - + {{#if (has-block "no-data-message")}} + {{yield to="no-data-message"}} + {{else}} + + {{/if}} {{/if}} {{/if}} - {{/if}} - - {{/if}} - - {{!-- END: body --}} -
{{!-- Checkbox --}}{{!-- Linenumbers --}} - - {{#if dataHeader.order}}[{{dataHeader.order}}]{{/if}} - {{dataHeader.label}} - -
Loading...
Loading...
- {{input type="checkbox" checked=row.isSelected click=row.toggleSelected}} - {{row.lineNumber}}
+ {{input type="checkbox" checked=row.isSelected click=row.toggleSelected}} + {{row.lineNumber}}{{cell.value}}{{cell.value}} + {{!-- TODO: This should be based on the type of the field --}} + {{cell.value}} + - {{!-- TODO: This should be based on the type of the field --}} - {{cell.value}} -

{{@noDataMessage}}

{{@noDataMessage}}

-
- {{/if}} -
+ + {{/if}} + + {{!-- END: body --}} + +
+ {{/if}} + - {{!-- END: content --}} + {{!-- END: content --}} - {{!-- START: pagination --}} - - {{#if @content}} - {{#if (has-block "pagination")}} - {{yield pagination to="pagination"}} - {{else}} -
-
- Displaying {{pagination.startItem}}-{{pagination.endItem}} - {{#if pagination.total}} of {{pagination.total}}{{/if}} - {{#if pagination.sizeOptions}} - | - + {{!-- START: pagination --}} + + {{#if @content}} + {{#if (has-block "pagination")}} + {{yield pagination to="pagination"}} + {{else}} +
+
+ Displaying {{pagination.startItem}}-{{pagination.endItem}} + {{#if pagination.total}} of {{pagination.total}}{{/if}} + {{#if pagination.sizeOptions}} + | + + {{/if}} +
+ {{#if pagination.hasMultiplePages}} +
+ + + + + +
{{/if}}
- {{#if pagination.hasMultiplePages}} -
- - - - - -
- {{/if}} -
+ {{/if}} {{/if}} - {{/if}} - - {{!-- END: pagination --}} + + {{!-- END: pagination --}} +
\ No newline at end of file From 593c0a6447dceacbb5c141acaeb87ac3030d8fc5 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 25 Jan 2022 16:43:10 +0100 Subject: [PATCH 18/56] Basic controller, route and serializer These basics should help people get started with the components. The route and controller could receive more love to allow better combination with other libraries. Not sure how much that happens at this point in time. --- addon/controller.js | 12 +++++ addon/route.js | 47 +++++++++++++++++++ .../data-table.js => serializer.js} | 0 3 files changed, 59 insertions(+) create mode 100644 addon/controller.js create mode 100644 addon/route.js rename addon/{serializer/data-table.js => serializer.js} (100%) diff --git a/addon/controller.js b/addon/controller.js new file mode 100644 index 0000000..9e43b02 --- /dev/null +++ b/addon/controller.js @@ -0,0 +1,12 @@ +import { tracked } from '@glimmer/tracking'; +import Controller from '@ember/controller'; + +export default class ProductsController extends Controller { + queryParams = ['size', 'page', 'filter', 'sort']; + + @tracked size = 10; + @tracked page = 0; + @tracked filter = ''; + @tracked sort = ''; // TODO: perhaps undefined would be a nicer default for consumers + @tracked isLoadingModel = false; +} diff --git a/addon/route.js b/addon/route.js new file mode 100644 index 0000000..d670568 --- /dev/null +++ b/addon/route.js @@ -0,0 +1,47 @@ +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import Route from '@ember/routing/route'; +import merge from 'lodash/merge'; + +export default class DataTableRoute extends Route { + @service store; + + queryParams = { + filter: { refreshModel: true }, + page: { refreshModel: true }, + size: { refreshModel: true }, + sort: { refreshModel: true }, + }; + + mergeQueryOptions() { + return {}; + } + + model(params) { + const options = { + sort: params.sort, + page: { + number: params.page, + size: params.size, + }, + }; + // TODO: sending an empty filter param to backend returns [] + if (params.filter) { + options['filter'] = params.filter; + } + merge(options, this.mergeQueryOptions(params)); + return this.store.query(this.modelName, options); + } + + @action + loading(transition) { + let controller = this.controllerFor(this.routeName); + controller.isLoadingModel = true; + + transition.promise.finally(function () { + controller.isLoadingModel = false; + }); + + return true; // bubble the loading event + } +} diff --git a/addon/serializer/data-table.js b/addon/serializer.js similarity index 100% rename from addon/serializer/data-table.js rename to addon/serializer.js From 8fd7ce252b561b34131765c2be9d48e55fd4d664 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 25 Jan 2022 21:46:18 +0100 Subject: [PATCH 19/56] Minor updates to the API Whilst implementing an alternative view, some thing options turned out to be missing. Adding those in. --- addon/components/data-table.hbs | 2 +- addon/components/data-table.js | 12 ++++-------- addon/components/data-table/number-pagination.hbs | 1 + addon/components/data-table/number-pagination.js | 7 +++++++ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index d132b3b..78db6dc 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -10,7 +10,7 @@ content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection - enableLineNumbers=this.enableLineNumbers + enableLineNumbers=@lineNumbers onClickRow=@onClickRow updateSort=this.updateSort data-table=this) diff --git a/addon/components/data-table.js b/addon/components/data-table.js index edf2180..d68996c 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -28,20 +28,12 @@ export default class DataTable extends Component { return this.args.isLoading; } - get enableLineNumbers() { - return this.args.lineNumbers; - } - get searchDebounceTime() { return this.args.searchDebounceTime === undefined ? 2000 : this.args.searchDebounceTime; } - get enableLineNumbers() { - return this.args.enableLineNumbers; - } - @tracked _enableSelection = undefined; @@ -67,6 +59,10 @@ export default class DataTable extends Component { return this.args.sort; } + get page() { + return this.args.page; + } + _size = undefined; get size() { if (this._size === undefined && this.args.size) return this.args.size; diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index 8cc12a7..dacd57f 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -7,6 +7,7 @@ sizeOptions=@sizeOptions updatePage=this.updatePage selectSizeOption=this.selectSizeOption + setSizeOption=this.setSizeOption sizeOptions=@sizeOptions hasMultiplePages=this.hasMultiplePages isFirstPage=this.isFirstPage diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index 53785de..3891c7d 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -48,6 +48,8 @@ export default class NumberPaginationComponent extends Component { ); } + + @action updatePage(link) { this.args.updatePage(link?.number || 0); @@ -57,4 +59,9 @@ export default class NumberPaginationComponent extends Component { selectSizeOption(event) { this.args.updateSize(parseInt(event.target.value)); } + + @action + setSizeOption(size) { + this.args.updateSize(parseInt(size)); + } } From 0bd071f2f639978ed012d84833edfe6564ee8572 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 25 Jan 2022 23:33:07 +0100 Subject: [PATCH 20/56] Support passing linked route --- addon/components/data-table.hbs | 1 + addon/components/data-table/data-table-content-body.hbs | 6 ++---- addon/components/data-table/data-table-content.hbs | 2 ++ .../data-table/default-data-table-content-body.hbs | 4 ++-- .../data-table/default-data-table-content-body.js | 4 ---- addon/components/data-table/row.hbs | 2 ++ addon/components/raw-data-table.hbs | 1 + 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 78db6dc..2a98eb2 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -13,6 +13,7 @@ enableLineNumbers=@lineNumbers onClickRow=@onClickRow updateSort=this.updateSort + linkedRoute=@link data-table=this) Pagination=(component "data-table/number-pagination" page=@page diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 7303821..ab74004 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -9,6 +9,7 @@ toggleSelected=this.updateSelection selection=@data-table.selection enableSelection=@enableSelection + linkedRoute=@linkedRoute noDataMessage=@noDataMessage Row=(component "data-table/row" data-table=@data-table @@ -18,8 +19,5 @@ offset=this.offset hasClickRowAction=(and @onClickRow true) onClickRow=this.onClickRow + linkedRoute=@linkedRoute toggleSelected=this.updateSelection))}} - -{{!-- - - --}} diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 0f25b37..723c19c 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -4,6 +4,7 @@ enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers updateSort=@updateSort + linkedRoute=@linkedRoute data-table=@data-table) Body=(component "data-table/data-table-content-body" content=@content @@ -11,5 +12,6 @@ enableLineNumbers=@enableLineNumbers noDataMessage=@noDataMessage onClickRow=@onClickRow + linkedRoute=@linkedRoute data-table=@data-table) dataTable=@data-table)}} diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 338160f..e1cd8a2 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -3,11 +3,11 @@ firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item - linkedRoute=this.linkedRoute + linkedRoute=@linkedRoute DataCell=(component "data-table/data-cell" columns=@data-table.parsedFields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item - linkedRoute=this.linkedRoute))}} + linkedRoute=@linkedRoute))}} diff --git a/addon/components/data-table/default-data-table-content-body.js b/addon/components/data-table/default-data-table-content-body.js index 95913d6..f1817b0 100644 --- a/addon/components/data-table/default-data-table-content-body.js +++ b/addon/components/data-table/default-data-table-content-body.js @@ -20,8 +20,4 @@ export default class DefaultDataTableContentBodyComponent extends Component { return []; } } - - get linkedRoute() { - return this.args['data-table'].link; - } } diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index d065615..d650543 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -9,6 +9,8 @@ onClickRow=(fn @onClickRow @wrapper.item) toggleSelected=(fn @toggleSelected @wrapper) isSelected=(includes @wrapper.item @selection) + linkedRoute=@linkedRoute DataCells=(component "data-table/default-data-table-content-body" + linkedRoute=@linkedRoute data-table=@data-table))}} \ No newline at end of file diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 091b4bc..96b9e97 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -14,6 +14,7 @@ @updatePageSize={{@updatePageSize}} {{!-- change to size or vice-versa? --}} @updateFilter={{@updateFilter}} @updateSort={{@updateSort}} + @linkedRoute={{@linkedRoute}} as |dt|> {{!-- START: search --}}
From aa48cae9e7d0fb36feee40f2f80ecccb62a424e3 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sun, 30 Jan 2022 22:03:30 +0100 Subject: [PATCH 21/56] Add @customFields and @customHeaders These are space-separated lists you may supply to indicate for which columns the :data-header and :data-cell should be rendered. When specified it will only be rendered for those cells. --- addon/components/data-table.hbs | 2 ++ addon/components/data-table/data-cell.hbs | 4 ++++ addon/components/data-table/data-cell.js | 11 +++++++++++ .../data-table/data-table-content-body.hbs | 2 ++ .../data-table/data-table-content-header.hbs | 3 +++ .../data-table/data-table-content.hbs | 2 ++ .../default-data-table-content-body.hbs | 2 ++ addon/components/data-table/row.hbs | 2 ++ addon/components/data-table/th-sortable.hbs | 4 +++- addon/components/data-table/th-sortable.js | 8 ++++++++ addon/components/raw-data-table.hbs | 18 +++++++++++++----- 11 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 addon/components/data-table/data-cell.js diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 2a98eb2..4130bba 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -13,6 +13,8 @@ enableLineNumbers=@lineNumbers onClickRow=@onClickRow updateSort=this.updateSort + customHeaders=@customHeaders + customFields=@customFields linkedRoute=@link data-table=this) Pagination=(component "data-table/number-pagination" diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index 2b4f81a..48c521f 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -3,5 +3,9 @@ firstColumn=@firstColumn otherColumns=@otherColumns item=@item + label=@column + customFields=@customFields linkedRoute=@linkedRoute + isCustom=this.isCustom + hasCustom=this.hasCustom value=(get @item @column))}} \ No newline at end of file diff --git a/addon/components/data-table/data-cell.js b/addon/components/data-table/data-cell.js new file mode 100644 index 0000000..88f6c77 --- /dev/null +++ b/addon/components/data-table/data-cell.js @@ -0,0 +1,11 @@ +import Component from '@glimmer/component'; + +export default class DataTableDataCellComponent extends Component { + get isCustom() { + return this.args.customFields?.split(" ").includes(this.args.column); + } + + get hasCustom() { + return this.args.customFields; + } +} diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index ab74004..0a2d5da 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -11,6 +11,7 @@ enableSelection=@enableSelection linkedRoute=@linkedRoute noDataMessage=@noDataMessage + customFields=@customFields Row=(component "data-table/row" data-table=@data-table enableLineNumbers=@enableLineNumbers @@ -20,4 +21,5 @@ hasClickRowAction=(and @onClickRow true) onClickRow=this.onClickRow linkedRoute=@linkedRoute + customFields=@customFields toggleSelected=this.updateSelection))}} diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index 4d59d47..d206696 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -4,11 +4,14 @@ parsedFields=@data-table.parsedFields currentSorting=@data-table.sort updateSort=@updateSort + customHeaders=@customHeaders dataHeadersInfo=(hash parsedFields=@data-table.parsedFields currentSorting=@data-table.sort + customHeaders=@customHeaders updateSort=@updateSort) ThSortable=(component "data-table/th-sortable" currentSorting=@data-table.sort + customHeaders=@customHeaders updateSort=@updateSort))}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 723c19c..1bc61d7 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -5,6 +5,7 @@ enableLineNumbers=@enableLineNumbers updateSort=@updateSort linkedRoute=@linkedRoute + customHeaders=@customHeaders data-table=@data-table) Body=(component "data-table/data-table-content-body" content=@content @@ -13,5 +14,6 @@ noDataMessage=@noDataMessage onClickRow=@onClickRow linkedRoute=@linkedRoute + customFields=@customFields data-table=@data-table) dataTable=@data-table)}} diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index e1cd8a2..3d686ca 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -4,10 +4,12 @@ otherColumns=this.otherColumns item=@item linkedRoute=@linkedRoute + customFields=@customfields DataCell=(component "data-table/data-cell" columns=@data-table.parsedFields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item + customFields=@customFields linkedRoute=@linkedRoute))}} diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index d650543..c122e64 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -10,7 +10,9 @@ toggleSelected=(fn @toggleSelected @wrapper) isSelected=(includes @wrapper.item @selection) linkedRoute=@linkedRoute + customFields=@customFields DataCells=(component "data-table/default-data-table-content-body" linkedRoute=@linkedRoute + customFields=@customFields data-table=@data-table))}} \ No newline at end of file diff --git a/addon/components/data-table/th-sortable.hbs b/addon/components/data-table/th-sortable.hbs index ffe03e6..682d8b4 100644 --- a/addon/components/data-table/th-sortable.hbs +++ b/addon/components/data-table/th-sortable.hbs @@ -2,4 +2,6 @@ isSorted=this.isSorted inverseSorting=this.inverseSorting label=@label - order=this.order)}} + order=this.order + isCustom=this.isCustom + hasCustom=this.hasCustom)}} \ No newline at end of file diff --git a/addon/components/data-table/th-sortable.js b/addon/components/data-table/th-sortable.js index a8a2498..a6f3a3b 100644 --- a/addon/components/data-table/th-sortable.js +++ b/addon/components/data-table/th-sortable.js @@ -36,6 +36,14 @@ export default class ThSortableComponent extends Component { } } + get isCustom() { + return this.args.customHeaders?.split(" ").includes(this.args.field); + } + + hasCustom() { + return this.args.customHeaders; + } + /** Sets the current sorting parameter. Note: the current sorting parameter may contain another field than the given field. diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 96b9e97..7ec040f 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -104,11 +104,13 @@ {{!-- Linenumbers --}} {{/if}} {{#if (has-block "data-headers")}} - {{yield headers.dataHeadersInfo to="data-headers"}} + {{yield header.dataHeadersInfo to="data-headers"}} {{else}} {{#each header.parsedFields as |field|}} - {{#if (has-block "data-header")}} + {{#if (and (has-block "data-header") dataHeader.isCustom)}} + {{yield dataHeader to="data-header"}} + {{else if (and (has-block "data-header") (not dataHeader.hasCustom))}} {{yield dataHeader to="data-header"}} {{else}} @@ -165,11 +167,15 @@ {{#if (has-block "first-data-cell")}} {{yield cell to="first-data-cell"}} - {{else if (has-block "data-cell")}} + {{else if (and (has-block "data-cell") cell.isCustom)}} + {{yield cell to="data-cell"}} + {{else if (and (has-block "data-cell") (not cell.hasCustom))}} {{yield cell to="data-cell"}} {{else}} {{#if cell.linkedRoute}} - {{#link-to cell.linkedRoute cell.item tagName="td"}}{{cell.value}}{{/link-to}} + + {{cell.value}} + {{else}} {{cell.value}} {{/if}} @@ -180,7 +186,9 @@ {{#if (has-block "rest-data-cell")}} {{yield cell to="rest-data-cell"}} - {{else if (has-block "data-cell")}} + {{else if (and (has-block "data-cell") cell.isCustom)}} + {{yield cell to="data-cell"}} + {{else if (and (has-block "data-cell") (not cell.hasCustom))}} {{yield cell to="data-cell"}} {{else}} From d7db4d4f183aa90295f2ef20a2cf9c97307a0bdc Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 12 Feb 2022 12:10:50 +0100 Subject: [PATCH 22/56] Introduce DSL for supplying links attached to each item at the end This DSL should make it obvious how to add common views to be linked at the end of the view. It is possible to override the statement by using an actions block. --- addon/components/data-table.hbs | 2 +- .../data-table/data-table-content-body.hbs | 4 +- .../data-table/data-table-content-header.hbs | 1 + .../data-table/data-table-content.hbs | 4 +- .../data-table/data-table-content.js | 74 ++++++++++++++++++- addon/components/data-table/row.hbs | 2 +- addon/components/raw-data-table.hbs | 9 ++- .../data-table/data-table-content-test.js | 54 ++++++++++++++ 8 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 tests/unit/components/data-table/data-table-content-test.js diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 4130bba..5d0a51a 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -15,7 +15,7 @@ updateSort=this.updateSort customHeaders=@customHeaders customFields=@customFields - linkedRoute=@link + links=@links data-table=this) Pagination=(component "data-table/number-pagination" page=@page diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 0a2d5da..8f11c79 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -9,7 +9,7 @@ toggleSelected=this.updateSelection selection=@data-table.selection enableSelection=@enableSelection - linkedRoute=@linkedRoute + linkedRoutes=@linkedRoutes noDataMessage=@noDataMessage customFields=@customFields Row=(component "data-table/row" @@ -20,6 +20,6 @@ offset=this.offset hasClickRowAction=(and @onClickRow true) onClickRow=this.onClickRow - linkedRoute=@linkedRoute + linkedRoutes=@linkedRoutes customFields=@customFields toggleSelected=this.updateSelection))}} diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index d206696..517680d 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -5,6 +5,7 @@ currentSorting=@data-table.sort updateSort=@updateSort customHeaders=@customHeaders + hasLinks=@hasLinks dataHeadersInfo=(hash parsedFields=@data-table.parsedFields currentSorting=@data-table.sort diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 1bc61d7..2612a56 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -4,7 +4,7 @@ enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers updateSort=@updateSort - linkedRoute=@linkedRoute + hasLinks=this.hasLinks customHeaders=@customHeaders data-table=@data-table) Body=(component "data-table/data-table-content-body" @@ -13,7 +13,7 @@ enableLineNumbers=@enableLineNumbers noDataMessage=@noDataMessage onClickRow=@onClickRow - linkedRoute=@linkedRoute + linkedRoutes=this.linkedRoutes customFields=@customFields data-table=@data-table) dataTable=@data-table)}} diff --git a/addon/components/data-table/data-table-content.js b/addon/components/data-table/data-table-content.js index b74cc53..28515ed 100644 --- a/addon/components/data-table/data-table-content.js +++ b/addon/components/data-table/data-table-content.js @@ -1,3 +1,75 @@ import Component from '@glimmer/component'; -export default class DataTableContentComponent extends Component {} +function splitDefinitions(string) { + return (string || "") + .split(" ") + .filter((x) => x !== ""); +} + +/** + * Transforms __ to _ and _ to space. + */ +function deUnderscoreString(string) { + const arrString = []; + + // executing this with a regex turned out to be less clear + let idx = 0; + while( idx < string.length ) { + let current = string[idx]; + let next = string[idx+1]; + + if( current === "_" && next === "_") { + arrString.push("_"); + idx = idx + 2; + } else if( current === "_" ) { + arrString.push(" "); + idx = idx + 1; + } else { + arrString.push(current); + idx = idx + 1; + } + } + return arrString.join(""); +} + +function convertDefinition(string) { + const parts = string.split(":"); + return { + route: parts[0], + label: (parts[1] || null) && deUnderscoreString(parts[1]), + icon: (parts[2] || null) && deUnderscoreString(parts[2]), + rawLabel: parts[1] || null, + rawIcon: parts[2] || null + }; +} + + +export default class DataTableContentComponent extends Component { + get hasLinks() { + return this.linkedRoutes.length > 0; + } + + /** + * Accepts and transforms definitions for linked routes. + * + * Implementations may transform this at will. The default + * transformation splits on `:` assuming the first part is the route + * and the second part is the label. If no label is given, it is + * passed as null. If a label is given, all underscores are + * transformed to spaces and double underscores are left as a single + * _. We split again on a third `:`, transforming in the same way for + * the suggested icon. + * + * Behaviour for `___` is undefined. + * + * Yields an array of objects to represent the linked routes. + * [ { route: "products.show", label: "Show product", icon: "show-icon" } ] + */ + get linkedRoutes() { + return splitDefinitions(this.args.links || "") + .map( convertDefinition ); + } +} + +// exposed for testing +export { deUnderscoreString, splitDefinitions, convertDefinition }; diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index c122e64..3381ab7 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -9,7 +9,7 @@ onClickRow=(fn @onClickRow @wrapper.item) toggleSelected=(fn @toggleSelected @wrapper) isSelected=(includes @wrapper.item @selection) - linkedRoute=@linkedRoute + linkedRoutes=@linkedRoutes customFields=@customFields DataCells=(component "data-table/default-data-table-content-body" diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 7ec040f..d53eaa5 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -14,7 +14,7 @@ @updatePageSize={{@updatePageSize}} {{!-- change to size or vice-versa? --}} @updateFilter={{@updateFilter}} @updateSort={{@updateSort}} - @linkedRoute={{@linkedRoute}} + @links={{@links}} as |dt|> {{!-- START: search --}}
@@ -123,6 +123,13 @@ {{/each}} {{/if}} + {{#if (has-block "actions-header")}} + {{yield to="actions-header"}} + {{else}} + {{#if (or (has-block "actions") header.hasLinks)}} + + {{/if}} + {{/if}} {{/if}} diff --git a/tests/unit/components/data-table/data-table-content-test.js b/tests/unit/components/data-table/data-table-content-test.js new file mode 100644 index 0000000..908787a --- /dev/null +++ b/tests/unit/components/data-table/data-table-content-test.js @@ -0,0 +1,54 @@ +import { module, test } from 'qunit'; +import { deUnderscoreString, splitDefinitions, convertDefinition } from 'ember-data-table/components/data-table/data-table-content'; + +module('Unit | Component | data-table-content', function() { + test('it strips underscores', (assert) => { + const checks = [["one", "one"], + ["one_two", "one two"], + ["one_two_three", "one two three"], + ["one__two", "one_two"], + ["one__two_three", "one_two three"], + ["__hello__", "_hello_"]]; + + assert.expect(checks.length); + + checks.forEach(([input, output]) => { + assert.strictEqual(deUnderscoreString(input), output); + }); + }); + + test('it splits definitions', function(assert) { + assert.deepEqual(splitDefinitions("hello world"), ["hello", "world"]); + assert.strictEqual(splitDefinitions(null), null); + assert.strictEqual(splitDefinitions(undefined), undefined); + }); + + test('it creates definition objects', function(assert) { + const checks = [ + ["hello", { + route: "hello", + label: null, + icon: null, + rawLabel: null, + rawIcon: null + }], + ["hello.world:Hello_World", { + route: "hello.world", + label: "Hello World", + icon: null, + rawLabel: "Hello_World", + rawIcon: null + }], + ["hello.world:Hello_World:add-icon-thing", { + route: "hello.world", + label: "Hello World", + icon: "add-icon-thing", + rawLabel: "Hello_World", + rawIcon: "add-icon-thing" + }]]; + + assert.expect(checks.length); + + checks.forEach(([input, output]) => assert.deepEqual(convertDefinition(input), output)); + }); +}); From d8fcf6a844c5827f6db1084033a548506bf56256 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 12 Feb 2022 12:26:33 +0100 Subject: [PATCH 23/56] Disable or update old tests Renaming the files ensures they don't currently run. We may want to alter these tests so they're picked up again later. --- ....js => data-table-content-body-test.no-js} | 18 ++++---- ...s => data-table-content-header-test.no-js} | 0 ...-test.js => data-table-content-test.no-js} | 0 ....js => data-table-menu-general-test.no-js} | 0 ...js => data-table-menu-selected-test.no-js} | 0 ...enu-test.js => data-table-menu-test.no-js} | 0 ...ta-table-test.js => data-table-test.no-js} | 0 .../components/data-table/row-test.js | 7 +++- ...efault-data-table-content-body-test.no-js} | 0 ...n-test.js => number-pagination-test.no-js} | 0 ...able-test.js => raw-data-table-test.no-js} | 0 ...-search-test.js => text-search-test.no-js} | 0 ...ortable-test.js => th-sortable-test.no-js} | 0 .../data-table/data-table-content-test.js | 4 +- .../unit/mixins/default-query-params-test.js | 12 ------ tests/unit/mixins/route-test.js | 41 ------------------- tests/unit/mixins/serializer-test.js | 12 ------ 17 files changed, 16 insertions(+), 78 deletions(-) rename tests/integration/components/{data-table-content-body-test.js => data-table-content-body-test.no-js} (86%) rename tests/integration/components/{data-table-content-header-test.js => data-table-content-header-test.no-js} (100%) rename tests/integration/components/{data-table-content-test.js => data-table-content-test.no-js} (100%) rename tests/integration/components/{data-table-menu-general-test.js => data-table-menu-general-test.no-js} (100%) rename tests/integration/components/{data-table-menu-selected-test.js => data-table-menu-selected-test.no-js} (100%) rename tests/integration/components/{data-table-menu-test.js => data-table-menu-test.no-js} (100%) rename tests/integration/components/{data-table-test.js => data-table-test.no-js} (100%) rename tests/integration/components/{default-data-table-content-body-test.js => default-data-table-content-body-test.no-js} (100%) rename tests/integration/components/{number-pagination-test.js => number-pagination-test.no-js} (100%) rename tests/integration/components/{raw-data-table-test.js => raw-data-table-test.no-js} (100%) rename tests/integration/components/{text-search-test.js => text-search-test.no-js} (100%) rename tests/integration/components/{th-sortable-test.js => th-sortable-test.no-js} (100%) delete mode 100644 tests/unit/mixins/default-query-params-test.js delete mode 100644 tests/unit/mixins/route-test.js delete mode 100644 tests/unit/mixins/serializer-test.js diff --git a/tests/integration/components/data-table-content-body-test.js b/tests/integration/components/data-table-content-body-test.no-js similarity index 86% rename from tests/integration/components/data-table-content-body-test.js rename to tests/integration/components/data-table-content-body-test.no-js index a72beee..8c479ae 100644 --- a/tests/integration/components/data-table-content-body-test.js +++ b/tests/integration/components/data-table-content-body-test.no-js @@ -9,7 +9,7 @@ module('Integration | Component | data table content body', function (hooks) { test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... }); - await render(hbs`{{data-table-content-body}}`); + await render(hbs`{{data-table/data-table-content-body}}`); assert.dom('tbody').exists({ count: 1 }); }); @@ -23,7 +23,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('dataTable.selection', []); await render( - hbs`{{data-table-content-body content=content data-table=dataTable}}` + hbs`{{data-table/data-table-content-body content=content data-table=dataTable}}` ); assert.dom('tr').exists({ count: 2 }, 'displays 2 rows'); @@ -55,7 +55,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('data-table.selection', [jane]); await render( - hbs`{{data-table-content-body content=content data-table=data-table enableSelection=true}}` + hbs`{{data-table/data-table-content-body content=content data-table=data-table enableSelection=true}}` ); assert.equal(this.$('tr:first td').length, 4, 'displays 4 columns'); @@ -82,7 +82,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('data-table.removeItemFromSelection', function () {}); // mock function await render( - hbs`{{data-table-content-body content=content data-table=data-table enableSelection=true}}` + hbs`{{data-table/data-table-content-body content=content data-table=data-table enableSelection=true}}` ); assert @@ -104,7 +104,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('data-table.selection', []); await render( - hbs`{{data-table-content-body content=content data-table=data-table enableLineNumbers=true}}` + hbs`{{data-table/data-table-content-body content=content data-table=data-table enableLineNumbers=true}}` ); assert.equal(this.$('tr:first td').length, 4, 'displays 4 columns'); @@ -127,7 +127,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('data-table.page', 2); this.set('data-table.size', 5); await render( - hbs`{{data-table-content-body content=content data-table=data-table enableLineNumbers=true}}` + hbs`{{data-table/data-table-content-body content=content data-table=data-table enableLineNumbers=true}}` ); assert.equal( @@ -161,7 +161,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('data-table.selection', []); await render( - hbs`{{data-table-content-body noDataMessage=noDataMessage data-table=data-table}}` + hbs`{{data-table/data-table-content-body noDataMessage=noDataMessage data-table=data-table}}` ); assert .dom('td.no-data-message') @@ -172,7 +172,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('content', []); await render( - hbs`{{data-table-content-body content=content noDataMessage=noDataMessage data-table=data-table}}` + hbs`{{data-table/data-table-content-body content=content noDataMessage=noDataMessage data-table=data-table}}` ); assert .dom('td.no-data-message') @@ -183,7 +183,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('content', ['foo', 'bar']); await render( - hbs`{{data-table-content-body content=content noDataMessage=noDataMessage data-table=data-table}}` + hbs`{{data-table/data-table-content-body content=content noDataMessage=noDataMessage data-table=data-table}}` ); assert .dom('td.no-data-message') diff --git a/tests/integration/components/data-table-content-header-test.js b/tests/integration/components/data-table-content-header-test.no-js similarity index 100% rename from tests/integration/components/data-table-content-header-test.js rename to tests/integration/components/data-table-content-header-test.no-js diff --git a/tests/integration/components/data-table-content-test.js b/tests/integration/components/data-table-content-test.no-js similarity index 100% rename from tests/integration/components/data-table-content-test.js rename to tests/integration/components/data-table-content-test.no-js diff --git a/tests/integration/components/data-table-menu-general-test.js b/tests/integration/components/data-table-menu-general-test.no-js similarity index 100% rename from tests/integration/components/data-table-menu-general-test.js rename to tests/integration/components/data-table-menu-general-test.no-js diff --git a/tests/integration/components/data-table-menu-selected-test.js b/tests/integration/components/data-table-menu-selected-test.no-js similarity index 100% rename from tests/integration/components/data-table-menu-selected-test.js rename to tests/integration/components/data-table-menu-selected-test.no-js diff --git a/tests/integration/components/data-table-menu-test.js b/tests/integration/components/data-table-menu-test.no-js similarity index 100% rename from tests/integration/components/data-table-menu-test.js rename to tests/integration/components/data-table-menu-test.no-js diff --git a/tests/integration/components/data-table-test.js b/tests/integration/components/data-table-test.no-js similarity index 100% rename from tests/integration/components/data-table-test.js rename to tests/integration/components/data-table-test.no-js diff --git a/tests/integration/components/data-table/row-test.js b/tests/integration/components/data-table/row-test.js index 1139fe7..4ff68d1 100644 --- a/tests/integration/components/data-table/row-test.js +++ b/tests/integration/components/data-table/row-test.js @@ -10,13 +10,16 @@ module('Integration | Component | data-table/row', function (hooks) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.set('myAction', function(val) { ... }); - await render(hbs``); + this.set('onClickRow', () => undefined ); + this.set('toggleSelected', () => undefined ); + + await render(hbs``); assert.dom(this.element).hasText(''); // Template block usage: await render(hbs` - + template block text `); diff --git a/tests/integration/components/default-data-table-content-body-test.js b/tests/integration/components/default-data-table-content-body-test.no-js similarity index 100% rename from tests/integration/components/default-data-table-content-body-test.js rename to tests/integration/components/default-data-table-content-body-test.no-js diff --git a/tests/integration/components/number-pagination-test.js b/tests/integration/components/number-pagination-test.no-js similarity index 100% rename from tests/integration/components/number-pagination-test.js rename to tests/integration/components/number-pagination-test.no-js diff --git a/tests/integration/components/raw-data-table-test.js b/tests/integration/components/raw-data-table-test.no-js similarity index 100% rename from tests/integration/components/raw-data-table-test.js rename to tests/integration/components/raw-data-table-test.no-js diff --git a/tests/integration/components/text-search-test.js b/tests/integration/components/text-search-test.no-js similarity index 100% rename from tests/integration/components/text-search-test.js rename to tests/integration/components/text-search-test.no-js diff --git a/tests/integration/components/th-sortable-test.js b/tests/integration/components/th-sortable-test.no-js similarity index 100% rename from tests/integration/components/th-sortable-test.js rename to tests/integration/components/th-sortable-test.no-js diff --git a/tests/unit/components/data-table/data-table-content-test.js b/tests/unit/components/data-table/data-table-content-test.js index 908787a..418c288 100644 --- a/tests/unit/components/data-table/data-table-content-test.js +++ b/tests/unit/components/data-table/data-table-content-test.js @@ -19,8 +19,8 @@ module('Unit | Component | data-table-content', function() { test('it splits definitions', function(assert) { assert.deepEqual(splitDefinitions("hello world"), ["hello", "world"]); - assert.strictEqual(splitDefinitions(null), null); - assert.strictEqual(splitDefinitions(undefined), undefined); + assert.deepEqual(splitDefinitions(null), []); + assert.deepEqual(splitDefinitions(undefined), []); }); test('it creates definition objects', function(assert) { diff --git a/tests/unit/mixins/default-query-params-test.js b/tests/unit/mixins/default-query-params-test.js deleted file mode 100644 index 958487e..0000000 --- a/tests/unit/mixins/default-query-params-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import EmberObject from '@ember/object'; -import DefaultQueryParamsMixin from 'ember-data-table/mixins/default-query-params'; -import { module, test } from 'qunit'; - -module('Unit | Mixin | default query params', function () { - // Replace this with your real tests. - test('it works', function (assert) { - let DefaultQueryParamsObject = EmberObject.extend(DefaultQueryParamsMixin); - let subject = DefaultQueryParamsObject.create(); - assert.ok(subject); - }); -}); diff --git a/tests/unit/mixins/route-test.js b/tests/unit/mixins/route-test.js deleted file mode 100644 index 1c7ec25..0000000 --- a/tests/unit/mixins/route-test.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable ember/no-new-mixins,ember/no-mixins */ - -import EmberObject from '@ember/object'; -import RouteMixin from 'ember-data-table/mixins/route'; -import { module, test } from 'qunit'; - -module('Unit | Mixin | route', function () { - test('it (deep) merges the response of mergeQueryOptions method with the query param options', function (assert) { - assert.expect(2); - - let RouteObject = EmberObject.extend(RouteMixin, { - modelName: 'test', - mergeQueryOptions() { - return { - foo: 'bar', - page: { - size: 5, - }, - }; - }, - }); - - let mockStore = { - query: (modelName, queryOptions) => { - assert.strictEqual(modelName, 'test'); - assert.deepEqual(queryOptions, { - sort: 'name', - page: { - size: 5, - number: 0, - }, - foo: 'bar', - }); - }, - }; - - let mockRoute = RouteObject.create(); - mockRoute.store = mockStore; - mockRoute.model({ sort: 'name', page: 0, size: 20 }); - }); -}); diff --git a/tests/unit/mixins/serializer-test.js b/tests/unit/mixins/serializer-test.js deleted file mode 100644 index 55973c2..0000000 --- a/tests/unit/mixins/serializer-test.js +++ /dev/null @@ -1,12 +0,0 @@ -import EmberObject from '@ember/object'; -import SerializerMixin from 'ember-data-table/mixins/serializer'; -import { module, test } from 'qunit'; - -module('Unit | Mixin | serializer', function () { - // Replace this with your real tests. - test('it works', function (assert) { - let SerializerObject = EmberObject.extend(SerializerMixin); - let subject = SerializerObject.create(); - assert.ok(subject); - }); -}); From 5b8bc9e0f290766a767335f728379aeb77b583ab Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 12 Feb 2022 14:16:22 +0100 Subject: [PATCH 24/56] Add support for @rowLink attribute --- addon/components/data-table.hbs | 1 + .../data-table/data-table-content-body.hbs | 7 ++++--- .../data-table/data-table-content-body.js | 8 +++++++- .../data-table/data-table-content.hbs | 1 + addon/components/raw-data-table.hbs | 19 ++++++++++++++++++- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 5d0a51a..c6040a2 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -16,6 +16,7 @@ customHeaders=@customHeaders customFields=@customFields links=@links + rowLink=@rowLink data-table=this) Pagination=(component "data-table/number-pagination" page=@page diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 8f11c79..4ce5add 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -4,12 +4,13 @@ offset=this.offset wrappedItems=this.wrappedItems enableLineNumbers=@enableLineNumbers - hasClickRowAction=(and @onClickRow true) - onClickRow=this.onClickRow + hasClickRowAction=(and (@onClickRow @rowLink) true) + onClickRow=@onClickRow toggleSelected=this.updateSelection selection=@data-table.selection enableSelection=@enableSelection linkedRoutes=@linkedRoutes + rowLink=@rowLink noDataMessage=@noDataMessage customFields=@customFields Row=(component "data-table/row" @@ -18,7 +19,7 @@ enableSelection=@enableSelection selection=@data-table.selection offset=this.offset - hasClickRowAction=(and @onClickRow true) + hasClickRowAction=(and (or @onClickRow @rowLink) true) onClickRow=this.onClickRow linkedRoutes=@linkedRoutes customFields=@customFields diff --git a/addon/components/data-table/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js index 81bfb0f..c10974a 100644 --- a/addon/components/data-table/data-table-content-body.js +++ b/addon/components/data-table/data-table-content-body.js @@ -1,7 +1,10 @@ +import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import Component from '@glimmer/component'; export default class DataTableContentBodyComponent extends Component { + @service router + get offset() { var offset = 1; //to avoid having 0. row var page = this.args['data-table'].page; // TODO: pass on page directly? @@ -35,6 +38,9 @@ export default class DataTableContentBodyComponent extends Component { @action onClickRow() { - if (this.args.onClickRow) this.args.onClickRow(...arguments); + if (this.args.onClickRow) + this.args.onClickRow(...arguments); + else if ( this.args.rowLink ) + this.router.transitionTo( this.args.rowLink, arguments[0] ); } } diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 2612a56..6b998b4 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -14,6 +14,7 @@ noDataMessage=@noDataMessage onClickRow=@onClickRow linkedRoutes=this.linkedRoutes + rowLink=@rowLink customFields=@customFields data-table=@data-table) dataTable=@data-table)}} diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index d53eaa5..0cda181 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -14,7 +14,11 @@ @updatePageSize={{@updatePageSize}} {{!-- change to size or vice-versa? --}} @updateFilter={{@updateFilter}} @updateSort={{@updateSort}} + @onClickRow={{@onClickRow}} @links={{@links}} + @rowLink={{@rowLink}} + @customHeaders={{@customHeaders}} + @customFields={{@customFields}} as |dt|> {{!-- START: search --}}
@@ -155,7 +159,7 @@ {{#if (has-block "row")}} {{yield row to="row"}} {{else}} - {{#if row.enableSelection}} @@ -207,6 +211,19 @@ {{/each}} {{/if}} + {{#if (has-block "actions")}} + {{yield row to="actions"}} + {{else}} + {{#if row.linkedRoutes}} + + {{#each row.linkedRoutes as |linkedRoute|}} + + {{or linkedRoute.label linkedRoute.route}} + + {{/each}} + + {{/if}} + {{/if}} {{/if}} From 95f8e39b53385bedbc323f393eb13f4b0d1f8d3c Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Sat, 12 Feb 2022 16:17:39 +0100 Subject: [PATCH 25/56] Reworking number-pagination with @erikap The number pagination has a concept of the server pagination on the outside and human pagination on the inside. Some features could be added, but this is an easier to understand workflow for the features that exist now. --- addon/components/data-table.hbs | 2 +- .../data-table/number-pagination.hbs | 9 +- .../data-table/number-pagination.js | 112 +++++++++++++++--- addon/components/raw-data-table.hbs | 54 ++++----- 4 files changed, 131 insertions(+), 46 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index c6040a2..2315b07 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -21,7 +21,7 @@ Pagination=(component "data-table/number-pagination" page=@page size=@size - nbOfItems=@content.length + itemsOnCurrentPage=@content.length sizeOptions=this.sizeOptions total=@content.meta.count links=@content.meta.pagination diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index dacd57f..0279ac3 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -2,13 +2,16 @@ startItem=this.startItem endItem=this.endItem total=@total - size=@size - page=@page + hasTotal=this.hasTotal + pageSize=@size + pageNumber=this.humanPage + pageOptions=this.pageOptions sizeOptions=@sizeOptions + firstPage=this.firstPage + lastPage=this.lastPage updatePage=this.updatePage selectSizeOption=this.selectSizeOption setSizeOption=this.setSizeOption - sizeOptions=@sizeOptions hasMultiplePages=this.hasMultiplePages isFirstPage=this.isFirstPage isLastPage=this.isLastPage diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index 3891c7d..67014a4 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -1,58 +1,142 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; +const defaultBackendPageOffset = 0; +const humanPageOffset = 1; // humans assume the first page has number 1 + +/** + * Converts from the human based page number (eg: first = 1) to the + * backend-based offset. + */ +function humanToBackend(number, backendPageOffset) { + return number - humanPageOffset + backendPageOffset; +} + +/** + * Converts from a backend page number to (eg: often first = 0) to the + * human-based offset (eg: first = 1) + */ +function backendToHuman(number, backendPageOffset) { + return number + humanPageOffset - backendPageOffset; +} + +/** + * Converts a human-based number to a zero-based number. + */ +function humanToZeroBased(number) { + return number - humanPageOffset; +} + +/** + * Converts a zero-based number to a human-based number. + */ +function zeroToHumanBased(number) { + return number + humanPageOffset; +} + +/** + * Helpers for pagination buttons. + * + * This component does not assume a backend offset. If the backend's + * first page has a number, we will assume that is the offset for the + * first page. This component then uses the numbers as a user would see + * them, as that's likely the easiest to construct the template. + * + * The inputs to this component from its wrapping component are what the + * backend understands in terms of page numbers, the outputs to the + * yielded block are what humans would understand. + */ export default class NumberPaginationComponent extends Component { - get currentPage() { + get currentBackendPage() { return this.args.page - ? parseInt(this.args.page) + 1 // TODO: verify this needs to parse the page, if the default is a number input should be parsed as a number too - : 1; + ? parseInt(this.args.page) + : defaultBackendPageOffset; } + /** + * The current page is as the user would view the page, what we supply + * as functions and parameters outside of this component is based on + * what the API supplies. + */ set currentPage(newPage) { - this.args.updatePage(newPage - 1); + this.args.updatePage(humanToBackend(newPage, this.backendPageOffset)); + } + + /** + * Yields 0 for 0-based offset from the backend, and 1 for 1-based + * offset. Also works for n-based offset but no one does that, we + * hope. + */ + get backendPageOffset() { + return this.args.links?.first?.number || defaultBackendPageOffset; + } + + get humanPage() { + return backendToHuman(this.args.page || defaultBackendPageOffset, this.backendPageOffset); } get firstPage() { - return this.args.links?.first?.number || 1; + return humanPageOffset; } get lastPage() { - return this.args.links?.last?.number || 1; + const backendLastPageNumber = this.args.links?.last?.number || defaultBackendPageOffset; + return backendToHuman(backendLastPageNumber, this.backendPageOffset); } get isFirstPage() { - return this.firstPage == this.currentPage; + return this.humanPage == this.firstPage; } get isLastPage() { - return this.lastPage == this.currentPage; + return this.humanPage == this.lastPage; } get hasMultiplePages() { - return this.lastPage > 0; // TODO: is this right? All numbers seem to default to 1. + return this.lastPage > this.firstPage; } get startItem() { - return this.args.size * (this.currentPage - 1) + 1; + // 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. + if( this.args.itemsOnCurrentPage == 0 && this.isFirstPage ) + // human probably expects to see 0-0 when no items exist. + return 0; + else + return zeroToHumanBased(this.args.size * humanToZeroBased( this.humanPage )); } get endItem() { - return this.startItem + this.args.nbOfItems - 1; + // 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. + if( this.args.itemsOnCurrentPage == 0 && this.isFirstPage ) + // human probably expects to see 0-0 when no items exist. + return 0; + else + return this.startItem - 1 + this.args.itemsOnCurrentPage; } + /** + * Supplies an array with all available pages. + */ get pageOptions() { - const nbOfPages = this.lastPage - this.firstPage + 1; + const nbOfPages = this.lastPage - this.firstPage; + return Array.from( new Array(nbOfPages), (_val, index) => this.firstPage + index ); } - + get hasTotal() { + return this.total || this.total === 0; + } @action updatePage(link) { - this.args.updatePage(link?.number || 0); + this.args.updatePage(link?.number || this.backendPageOffset); } @action diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 0cda181..eeac560 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -249,36 +249,34 @@ {{!-- START: pagination --}} - {{#if @content}} - {{#if (has-block "pagination")}} - {{yield pagination to="pagination"}} - {{else}} -
-
- Displaying {{pagination.startItem}}-{{pagination.endItem}} - {{#if pagination.total}} of {{pagination.total}}{{/if}} - {{#if pagination.sizeOptions}} - | - - {{/if}} -
- {{#if pagination.hasMultiplePages}} -
- - - - - -
+ {{#if (has-block "pagination")}} + {{yield pagination to="pagination"}} + {{else}} +
+
+ Displaying {{pagination.startItem}}-{{pagination.endItem}} + {{#if pagination.hasTotal}} of {{pagination.total}}{{/if}} + {{#if pagination.sizeOptions}} + | + {{/if}}
- {{/if}} + {{#if pagination.hasMultiplePages}} +
+ + + + + +
+ {{/if}} +
{{/if}} {{!-- END: pagination --}} From 67a8893b888b36a11da35738e6986f67004df42e Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Mon, 24 Jun 2024 13:45:35 +0200 Subject: [PATCH 26/56] Improvements on naming and anchoring Co-author: @erikap --- addon/components/raw-data-table.hbs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index eeac560..4241ed6 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -1,6 +1,8 @@ +{{!-- template-lint-disable no-inline-styles --}} - {{cell.value}} - + + + {{cell.value}} + + {{else}} {{cell.value}} {{/if}} @@ -202,10 +207,16 @@ {{else if (and (has-block "data-cell") (not cell.hasCustom))}} {{yield cell to="data-cell"}} {{else}} - - {{!-- TODO: This should be based on the type of the field --}} - {{cell.value}} - + {{!-- TODO: This should be based on the type of the field --}} + {{#if cell.linkedRoute}} + + + {{cell.value}} + + + {{else}} + {{cell.value}} + {{/if}} {{/if}} {{/each}} @@ -244,7 +255,6 @@
{{/if}} - {{!-- END: content --}} {{!-- START: pagination --}} From 3a77e01beab00636ecd7097381acab40ac9df1a6 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Wed, 26 Jun 2024 12:56:29 +0200 Subject: [PATCH 27/56] Add documentation backlinks in components The components hbs files mostly follow a logical name but it can still be a bit confusing. We're adding the breadcrumbs now so we can find our way back more easily. --- addon/components/data-table/data-cell.hbs | 1 + addon/components/data-table/data-table-content-body.hbs | 1 + addon/components/data-table/data-table-content-header.hbs | 1 + addon/components/data-table/data-table-content.hbs | 1 + addon/components/data-table/data-table-menu-general.hbs | 1 + addon/components/data-table/data-table-menu-selected.hbs | 1 + addon/components/data-table/data-table-menu.hbs | 1 + addon/components/data-table/default-data-table-content-body.hbs | 1 + addon/components/data-table/number-pagination.hbs | 1 + addon/components/data-table/row.hbs | 1 + addon/components/data-table/text-search.hbs | 1 + addon/components/data-table/th-sortable.hbs | 1 + 12 files changed, 12 insertions(+) diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index 48c521f..ea1d82b 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/default-data-table-content-body --}} {{yield (hash columns=@columns firstColumn=@firstColumn diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 4ce5add..880115e 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-content --}} {{yield (hash isLoading=@data-table.isLoading content=@content diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index 517680d..7644b5b 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-content --}} {{yield (hash enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 6b998b4..9657ea0 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table.hbs --}} {{yield (hash Header=(component "data-table/data-table-content-header" diff --git a/addon/components/data-table/data-table-menu-general.hbs b/addon/components/data-table/data-table-menu-general.hbs index bbe3ab5..63a90ba 100644 --- a/addon/components/data-table/data-table-menu-general.hbs +++ b/addon/components/data-table/data-table-menu-general.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-menu --}} {{#if @data-table.selectionIsEmpty}} {{yield}} {{/if}} diff --git a/addon/components/data-table/data-table-menu-selected.hbs b/addon/components/data-table/data-table-menu-selected.hbs index f6606d1..0a167e0 100644 --- a/addon/components/data-table/data-table-menu-selected.hbs +++ b/addon/components/data-table/data-table-menu-selected.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-menu --}} {{yield (hash selectionIsEmpty=@data-table.selectionIsEmpty selectionCount=this.selectionCount diff --git a/addon/components/data-table/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs index 9ad128d..f8144a3 100644 --- a/addon/components/data-table/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table.hbs --}} {{#let (component "data-table/data-table-menu-general" data-table=@data-table) (component "data-table/data-table-menu-selected" data-table=@data-table) diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 3d686ca..53c133c 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-content-body --}} {{yield (hash columns=@data-table.parsedFields firstColumn=this.firstColumn diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index 0279ac3..ed3b6c9 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table.hbs --}} {{yield (hash startItem=this.startItem endItem=this.endItem diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index 3381ab7..cb897b2 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-content-body --}} {{yield (hash wrapper=@wrapper item=@wrapper.item diff --git a/addon/components/data-table/text-search.hbs b/addon/components/data-table/text-search.hbs index 120eeae..97e1439 100644 --- a/addon/components/data-table/text-search.hbs +++ b/addon/components/data-table/text-search.hbs @@ -1,3 +1,4 @@ +{{!-- Used in data-table.hbs --}} {{yield (hash submitForm=this.submitForm placeholder=this.placeholder diff --git a/addon/components/data-table/th-sortable.hbs b/addon/components/data-table/th-sortable.hbs index 682d8b4..1f3ecbc 100644 --- a/addon/components/data-table/th-sortable.hbs +++ b/addon/components/data-table/th-sortable.hbs @@ -1,3 +1,4 @@ +{{!-- Used in: data-table/data-table-content-header --}} {{yield (hash isSorted=this.isSorted inverseSorting=this.inverseSorting From 5daded0e0b9536a23da4a559159b5677751bd87e Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Wed, 26 Jun 2024 14:07:53 +0200 Subject: [PATCH 28/56] Introduce rich field objects These field objects contain information fetched from various configuration parameters and will make it easier to provide certain overrides by providing known information through a single interface. Co-author: @erikap --- addon/components/data-table.hbs | 5 +- addon/components/data-table.js | 45 +++++++++- addon/components/data-table/data-cell.hbs | 6 +- addon/components/data-table/data-cell.js | 10 ++- .../data-table/data-table-content-header.hbs | 3 + .../data-table/data-table-content.hbs | 6 +- .../data-table/data-table-content.js | 51 +---------- .../default-data-table-content-body.hbs | 2 + addon/components/data-table/th-sortable.hbs | 5 +- addon/components/data-table/th-sortable.js | 19 +++- addon/components/raw-data-table.hbs | 38 ++++---- addon/utils/string-specification-helpers.js | 88 +++++++++++++++++++ .../data-table/data-table-content-test.js | 6 +- .../string-specification-helpers-test.js | 10 +++ 14 files changed, 208 insertions(+), 86 deletions(-) create mode 100644 addon/utils/string-specification-helpers.js create mode 100644 tests/unit/utils/string-specification-helpers-test.js diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 2315b07..27e1fe8 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -13,8 +13,9 @@ enableLineNumbers=@lineNumbers onClickRow=@onClickRow updateSort=this.updateSort - customHeaders=@customHeaders - customFields=@customFields + customHeaders=this.customHeaders + customFields=this.customFields + fields=this.fields links=@links rowLink=@rowLink data-table=this) diff --git a/addon/components/data-table.js b/addon/components/data-table.js index d68996c..0122efd 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -1,8 +1,8 @@ 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"; export default class DataTable extends Component { @tracked _selection = undefined; @@ -96,15 +96,54 @@ export default class DataTable extends Component { return this.args.autoSearch === undefined ? true : this.args.autoSearch; } - get parsedFields() { + get fieldsWithMeta() { const fields = this.args.fields; + if (typeOf(fields) === 'string') { - return fields.split(' '); + return toComponentSpecifications(fields, [{raw: "attribute"},{name: "label", default: "attribute"}]); } else { return fields || []; } } + get fields() { + return this + .fieldsWithMeta + .map( ({ attribute, label, isSortable, hasCustomHeader, isCustom }) => ({ + attribute, + label, + isSortable: isSortable + || !this.sortableFields + || this.sortableFields.includes(attribute), + hasCustomHeader: hasCustomHeader + || this.customHeaders.includes(attribute), + isCustom: isCustom + || this.customFields.includes(attribute) + }) ); + } + + get customHeaders() { + return splitDefinitions(this.args.customHeaders); + } + + get customFields() { + return splitDefinitions(this.args.customFields); + } + + get sortableFields() { + const sortableFields = this.args.sortableFields; + if (sortableFields || sortableFields === "") + // default: all fields are sortable + return splitDefinitions(sortableFields); + else + return null; + } + + get parsedFields() { + console.warn("Use of parsedFields in dataTable is deprecated"); + return this.fieldsWithMeta.map(({attribute}) => attribute); + } + get searchPlaceholder() { return this.args.searchPlaceholder === undefined ? 'Search input' diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index ea1d82b..c205c82 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -4,9 +4,11 @@ firstColumn=@firstColumn otherColumns=@otherColumns item=@item - label=@column + label=@column.label customFields=@customFields linkedRoute=@linkedRoute isCustom=this.isCustom hasCustom=this.hasCustom - value=(get @item @column))}} \ No newline at end of file + attribute=@column.attribute + renderCustomBlock=this.renderCustomBlock + value=(get @item @column.attribute))}} \ No newline at end of file diff --git a/addon/components/data-table/data-cell.js b/addon/components/data-table/data-cell.js index 88f6c77..136c5da 100644 --- a/addon/components/data-table/data-cell.js +++ b/addon/components/data-table/data-cell.js @@ -2,10 +2,14 @@ import Component from '@glimmer/component'; export default class DataTableDataCellComponent extends Component { get isCustom() { - return this.args.customFields?.split(" ").includes(this.args.column); + return this.args.column.isCustom; } - get hasCustom() { - return this.args.customFields; + get hasCustomFields() { + return this.args.fields.find( ({isCustom}) => isCustom) || false; + } + + get renderCustomBlock() { + return this.args.hasCustomBlock && ( this.isCustom || !this.hasCustomFields ); } } diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index 7644b5b..b3e99f5 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -2,18 +2,21 @@ {{yield (hash enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers + fields=@fields parsedFields=@data-table.parsedFields currentSorting=@data-table.sort updateSort=@updateSort customHeaders=@customHeaders hasLinks=@hasLinks dataHeadersInfo=(hash + fields=@fields parsedFields=@data-table.parsedFields currentSorting=@data-table.sort customHeaders=@customHeaders updateSort=@updateSort) ThSortable=(component "data-table/th-sortable" + fields=@fields currentSorting=@data-table.sort customHeaders=@customHeaders updateSort=@updateSort))}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 9657ea0..59d0fea 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -7,7 +7,8 @@ updateSort=@updateSort hasLinks=this.hasLinks customHeaders=@customHeaders - data-table=@data-table) + data-table=@data-table + fields=@fields) Body=(component "data-table/data-table-content-body" content=@content enableSelection=@enableSelection @@ -17,5 +18,6 @@ linkedRoutes=this.linkedRoutes rowLink=@rowLink customFields=@customFields - data-table=@data-table) + data-table=@data-table + fields=@fields) dataTable=@data-table)}} diff --git a/addon/components/data-table/data-table-content.js b/addon/components/data-table/data-table-content.js index 28515ed..c0cfaa3 100644 --- a/addon/components/data-table/data-table-content.js +++ b/addon/components/data-table/data-table-content.js @@ -1,48 +1,5 @@ import Component from '@glimmer/component'; - -function splitDefinitions(string) { - return (string || "") - .split(" ") - .filter((x) => x !== ""); -} - -/** - * Transforms __ to _ and _ to space. - */ -function deUnderscoreString(string) { - const arrString = []; - - // executing this with a regex turned out to be less clear - let idx = 0; - while( idx < string.length ) { - let current = string[idx]; - let next = string[idx+1]; - - if( current === "_" && next === "_") { - arrString.push("_"); - idx = idx + 2; - } else if( current === "_" ) { - arrString.push(" "); - idx = idx + 1; - } else { - arrString.push(current); - idx = idx + 1; - } - } - return arrString.join(""); -} - -function convertDefinition(string) { - const parts = string.split(":"); - return { - route: parts[0], - label: (parts[1] || null) && deUnderscoreString(parts[1]), - icon: (parts[2] || null) && deUnderscoreString(parts[2]), - rawLabel: parts[1] || null, - rawIcon: parts[2] || null - }; -} - +import { toComponentSpecifications } from '../../utils/string-specification-helpers'; export default class DataTableContentComponent extends Component { get hasLinks() { @@ -66,10 +23,6 @@ export default class DataTableContentComponent extends Component { * [ { route: "products.show", label: "Show product", icon: "show-icon" } ] */ get linkedRoutes() { - return splitDefinitions(this.args.links || "") - .map( convertDefinition ); + return toComponentSpecifications(this.args.links || "", [{ raw: "route" }, "label", "icon"]); } } - -// exposed for testing -export { deUnderscoreString, splitDefinitions, convertDefinition }; diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 53c133c..059e0ac 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -1,6 +1,7 @@ {{!-- Used in: data-table/data-table-content-body --}} {{yield (hash columns=@data-table.parsedFields + fields=@data-table.fields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item @@ -9,6 +10,7 @@ DataCell=(component "data-table/data-cell" columns=@data-table.parsedFields + fields=@data-table.fields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item diff --git a/addon/components/data-table/th-sortable.hbs b/addon/components/data-table/th-sortable.hbs index 1f3ecbc..26c9de9 100644 --- a/addon/components/data-table/th-sortable.hbs +++ b/addon/components/data-table/th-sortable.hbs @@ -2,7 +2,10 @@ {{yield (hash isSorted=this.isSorted inverseSorting=this.inverseSorting - label=@label + isSortable=@field.isSortable + label=@field.label + attribute=@field.attribute order=this.order + renderCustomBlock=this.renderCustomBlock isCustom=this.isCustom hasCustom=this.hasCustom)}} \ No newline at end of file diff --git a/addon/components/data-table/th-sortable.js b/addon/components/data-table/th-sortable.js index a6f3a3b..f5c4ecb 100644 --- a/addon/components/data-table/th-sortable.js +++ b/addon/components/data-table/th-sortable.js @@ -2,8 +2,9 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; export default class ThSortableComponent extends Component { + // TODO: this is a json-api translation and it should be configurable on the data-table get dasherizedField() { - return this.args.field.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + return this.args.field.attribute.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } /** @@ -36,12 +37,22 @@ export default class ThSortableComponent extends Component { } } + get renderCustomBlock() { + // render the custom block when this header is custom or when a + // custom block was given and no specific headers were supplied to + // be custom. + // + // Note: data table can't make this decision because it doesn't know + // whether a custom block was supplied. + return this.args.hasCustomBlock && (this.isCustom || !this.hasCustomHeaders); + } + get isCustom() { - return this.args.customHeaders?.split(" ").includes(this.args.field); + return this.args.field.hasCustomHeader; } - hasCustom() { - return this.args.customHeaders; + get hasCustomHeaders() { + return this.args.fields.find(({hasCustomHeader}) => hasCustomHeader) || false; } /** diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 4241ed6..38251f0 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -112,19 +112,21 @@ {{#if (has-block "data-headers")}} {{yield header.dataHeadersInfo to="data-headers"}} {{else}} - {{#each header.parsedFields as |field|}} - - {{#if (and (has-block "data-header") dataHeader.isCustom)}} - {{yield dataHeader to="data-header"}} - {{else if (and (has-block "data-header") (not dataHeader.hasCustom))}} + {{#each header.fields as |field|}} + + {{#if dataHeader.renderCustomBlock}} {{yield dataHeader to="data-header"}} {{else}} - - - {{#if dataHeader.order}}[{{dataHeader.order}}]{{/if}} - {{dataHeader.label}} - - + {{#if dataHeader.isSortable}} + + + {{#if dataHeader.order}}[{{dataHeader.order}}]{{/if}} + {{dataHeader.label}} + + + {{else}} + {{dataHeader.label}} + {{/if}} {{/if}} {{/each}} @@ -176,13 +178,12 @@ {{#if (has-block "data-cells")}} {{yield dataCells to="data-cells"}} {{else}} + {{!-- NOTE: you may drop this {{#if dataCells.firstColumn}}...{{/if}} when no custom first column styling is needed --}} {{#if dataCells.firstColumn}} - + {{#if (has-block "first-data-cell")}} {{yield cell to="first-data-cell"}} - {{else if (and (has-block "data-cell") cell.isCustom)}} - {{yield cell to="data-cell"}} - {{else if (and (has-block "data-cell") (not cell.hasCustom))}} + {{else if cell.renderCustomBlock}} {{yield cell to="data-cell"}} {{else}} {{!-- TODO: This should be based on the type of the field --}} @@ -198,13 +199,12 @@ {{/if}} {{/if}} + {{!-- NOTE: if you dropped custom styling for dataCells.firstColumn then use {{#each dataCells.fields as |column|}}...{{/each}} --}} {{#each dataCells.otherColumns as |column|}} - + {{#if (has-block "rest-data-cell")}} {{yield cell to="rest-data-cell"}} - {{else if (and (has-block "data-cell") cell.isCustom)}} - {{yield cell to="data-cell"}} - {{else if (and (has-block "data-cell") (not cell.hasCustom))}} + {{else if cell.renderCustomBlock}} {{yield cell to="data-cell"}} {{else}} {{!-- TODO: This should be based on the type of the field --}} diff --git a/addon/utils/string-specification-helpers.js b/addon/utils/string-specification-helpers.js new file mode 100644 index 0000000..0818b45 --- /dev/null +++ b/addon/utils/string-specification-helpers.js @@ -0,0 +1,88 @@ +import { upperFirst } from "lodash"; + +/** + * Splits a string of defitinions by space. + */ +export function splitDefinitions(string) { + return (string || "") + .split(" ") + .filter((x) => x !== ""); +} + +/** + * Transforms __ to _ and _ to space. + */ +export function deUnderscoreString(string) { + const arrString = []; + + // executing this with a regex turned out to be less clear + let idx = 0; + while( idx < string.length ) { + let current = string[idx]; + let next = string[idx+1]; + + if( current === "_" && next === "_") { + arrString.push("_"); + idx = idx + 2; + } else if( current === "_" ) { + arrString.push(" "); + idx = idx + 1; + } else { + arrString.push(current); + idx = idx + 1; + } + } + return arrString.join(""); +} + +/** + * Unpacks the components of a series of name/label specifications split + * by spaces (top-level) and : lower-level, including the unpacking of + * _. + * + * configuration is an array of components to be recognized. In case of + * a simple string, the item is placed under that key in the returned + * object and rawLabel is used to provide the unparsed value (without + * clearing _). An object may be supplied for further unpacking which + * may contain the following key/values: + * + * - raw: label : Store the raw value as label, do not process further. + * - default: label : Use the previously parsed value for label as the * + * default value if no value was supplied or if an empty value was + * supplied, must also supply name as key. + * + * toComponentSpecifications( "number:Nr. location:Gemeente_en_straat land", [{raw: "attribute"},{name: "label", default: "attribute"}]) + * -> [{attribute:"number", label: "Nr."},{attribute:"location",label:"Gemeente en straat",rawLabel:"Gemeente_en_straat"},{attribute:"land",label:"land"}] + */ +export function toComponentSpecifications(string, configuration) { + return splitDefinitions(string) + .map( (specification) => { + let obj = {}; + let components = specification.split(":"); + for ( let i = 0; i < configuration.length; i++ ) { + const spec = configuration[i]; + const component = components[i]; + if ( typeof spec === "string" ) { + obj[`raw${upperFirst(spec)}`] = component; + obj[spec] = deUnderscoreString(component || ""); + } else { + // object specification + if (spec.raw) { + obj[spec.raw] = component; + } + if (spec.name) { + if (spec.default && !component) { + obj[spec.name] = obj[spec.default]; + } else { + obj[`raw${upperFirst(spec.name)}`] = component; + obj[spec.name] = deUnderscoreString(component || ""); + } + } + if (!spec.raw && !spec.default) { + throw `Specification ${JSON.stringify(spec)} not understood`; + } + } + } + return obj; + } ); +} diff --git a/tests/unit/components/data-table/data-table-content-test.js b/tests/unit/components/data-table/data-table-content-test.js index 418c288..b84dbc7 100644 --- a/tests/unit/components/data-table/data-table-content-test.js +++ b/tests/unit/components/data-table/data-table-content-test.js @@ -1,5 +1,9 @@ import { module, test } from 'qunit'; -import { deUnderscoreString, splitDefinitions, convertDefinition } from 'ember-data-table/components/data-table/data-table-content'; +import { deUnderscoreString, splitDefinitions, toComponentSpecifications } from 'ember-data-table/utils/string-specification-helpers'; + +function convertDefinition(string) { + return toComponentSpecifications(string || "", [{ raw: "route" }, "label", "icon"]); +} module('Unit | Component | data-table-content', function() { test('it strips underscores', (assert) => { diff --git a/tests/unit/utils/string-specification-helpers-test.js b/tests/unit/utils/string-specification-helpers-test.js new file mode 100644 index 0000000..e158f30 --- /dev/null +++ b/tests/unit/utils/string-specification-helpers-test.js @@ -0,0 +1,10 @@ +import stringSpecificationHelpers from 'dummy/utils/string-specification-helpers'; +import { module, test } from 'qunit'; + +module('Unit | Utility | string-specification-helpers', function () { + // TODO: Replace this with your real tests. + test('it works', function (assert) { + let result = stringSpecificationHelpers(); + assert.ok(result); + }); +}); From 49544a26d3ce4960d499dfe3cc35f428fc4485a8 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Wed, 26 Jun 2024 16:44:18 +0200 Subject: [PATCH 29/56] Rename data-table to dataTable in arguments Cleaner wording, replaced semi-automatically. Co-author: @erikap --- addon/components/data-table.hbs | 4 +- .../data-table/data-table-content-body.hbs | 8 +-- .../data-table/data-table-content-body.js | 10 ++-- .../data-table/data-table-content-header.hbs | 10 ++-- .../data-table/data-table-content.hbs | 6 +-- .../data-table/data-table-menu-general.hbs | 2 +- .../data-table/data-table-menu-selected.hbs | 6 +-- .../data-table/data-table-menu-selected.js | 6 +-- .../components/data-table/data-table-menu.hbs | 4 +- .../default-data-table-content-body.hbs | 8 +-- .../default-data-table-content-body.js | 6 +-- addon/components/data-table/row.hbs | 2 +- addon/components/raw-data-table.hbs | 8 +-- .../data-table-content-body-test.no-js | 50 +++++++++---------- .../data-table-content-header-test.no-js | 18 +++---- .../data-table-menu-general-test.no-js | 6 +-- .../data-table-menu-selected-test.no-js | 20 ++++---- ...default-data-table-content-body-test.no-js | 6 +-- 18 files changed, 90 insertions(+), 90 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 27e1fe8..a9cc270 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -18,7 +18,7 @@ fields=this.fields links=@links rowLink=@rowLink - data-table=this) + dataTable=this) Pagination=(component "data-table/number-pagination" page=@page size=@size @@ -29,7 +29,7 @@ updatePage=@updatePage updateSize=this.updatePageSize) Menu=(component "data-table/data-table-menu" - data-table=this) + dataTable=this) content=@content enableSearch=this.enableSearch dataTable=this)}} diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 880115e..efa4383 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -1,6 +1,6 @@ {{!-- Used in: data-table/data-table-content --}} {{yield (hash - isLoading=@data-table.isLoading + isLoading=@dataTable.isLoading content=@content offset=this.offset wrappedItems=this.wrappedItems @@ -8,17 +8,17 @@ hasClickRowAction=(and (@onClickRow @rowLink) true) onClickRow=@onClickRow toggleSelected=this.updateSelection - selection=@data-table.selection + selection=@dataTable.selection enableSelection=@enableSelection linkedRoutes=@linkedRoutes rowLink=@rowLink noDataMessage=@noDataMessage customFields=@customFields Row=(component "data-table/row" - data-table=@data-table + dataTable=@dataTable enableLineNumbers=@enableLineNumbers enableSelection=@enableSelection - selection=@data-table.selection + selection=@dataTable.selection offset=this.offset hasClickRowAction=(and (or @onClickRow @rowLink) true) onClickRow=this.onClickRow diff --git a/addon/components/data-table/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js index c10974a..a2b747f 100644 --- a/addon/components/data-table/data-table-content-body.js +++ b/addon/components/data-table/data-table-content-body.js @@ -7,8 +7,8 @@ export default class DataTableContentBodyComponent extends Component { get offset() { var offset = 1; //to avoid having 0. row - var page = this.args['data-table'].page; // TODO: pass on page directly? - var size = this.args['data-table'].size; // TODO: pass on size directly? + var page = this.args.dataTable.page; // TODO: pass on page directly? + var size = this.args.dataTable.size; // TODO: pass on size directly? if (page && size) { offset += page * size; } @@ -16,7 +16,7 @@ export default class DataTableContentBodyComponent extends Component { } get wrappedItems() { - const selection = this.args['data-table'].selection || []; // TODO: should the data-table ensure this is an array? + const selection = this.args.dataTable.selection || []; // TODO: should the dataTable ensure this is an array? const content = this.args.content; return content.map(function (item) { return { item: item, isSelected: selection.includes(item) }; @@ -29,9 +29,9 @@ export default class DataTableContentBodyComponent extends Component { this.wrappedItems.forEach((wrapper) => { if (wrapper.isSelected) { - this.args['data-table'].addItemToSelection(wrapper.item); // TODO: pass on addItemToSelection directly? + this.args.dataTable.addItemToSelection(wrapper.item); // TODO: pass on addItemToSelection directly? } else { - this.arg['data-table'].removeItemFromSelection(wrapper.item); // TODO: pass on removeItemFromSelection directly? + this.args.dataTable.removeItemFromSelection(wrapper.item); // TODO: pass on removeItemFromSelection directly? } }); } diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index b3e99f5..5922a26 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -3,20 +3,20 @@ enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers fields=@fields - parsedFields=@data-table.parsedFields - currentSorting=@data-table.sort + parsedFields=@dataTable.parsedFields + currentSorting=@dataTable.sort updateSort=@updateSort customHeaders=@customHeaders hasLinks=@hasLinks dataHeadersInfo=(hash fields=@fields - parsedFields=@data-table.parsedFields - currentSorting=@data-table.sort + parsedFields=@dataTable.parsedFields + currentSorting=@dataTable.sort customHeaders=@customHeaders updateSort=@updateSort) ThSortable=(component "data-table/th-sortable" fields=@fields - currentSorting=@data-table.sort + currentSorting=@dataTable.sort customHeaders=@customHeaders updateSort=@updateSort))}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 59d0fea..678ebda 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -7,7 +7,7 @@ updateSort=@updateSort hasLinks=this.hasLinks customHeaders=@customHeaders - data-table=@data-table + dataTable=@dataTable fields=@fields) Body=(component "data-table/data-table-content-body" content=@content @@ -18,6 +18,6 @@ linkedRoutes=this.linkedRoutes rowLink=@rowLink customFields=@customFields - data-table=@data-table + dataTable=@dataTable fields=@fields) - dataTable=@data-table)}} + dataTable=@dataTable)}} diff --git a/addon/components/data-table/data-table-menu-general.hbs b/addon/components/data-table/data-table-menu-general.hbs index 63a90ba..fad1606 100644 --- a/addon/components/data-table/data-table-menu-general.hbs +++ b/addon/components/data-table/data-table-menu-general.hbs @@ -1,4 +1,4 @@ {{!-- Used in: data-table/data-table-menu --}} -{{#if @data-table.selectionIsEmpty}} +{{#if @dataTable.selectionIsEmpty}} {{yield}} {{/if}} diff --git a/addon/components/data-table/data-table-menu-selected.hbs b/addon/components/data-table/data-table-menu-selected.hbs index 0a167e0..d2b38a8 100644 --- a/addon/components/data-table/data-table-menu-selected.hbs +++ b/addon/components/data-table/data-table-menu-selected.hbs @@ -1,9 +1,9 @@ {{!-- Used in: data-table/data-table-menu --}} {{yield (hash - selectionIsEmpty=@data-table.selectionIsEmpty + selectionIsEmpty=@dataTable.selectionIsEmpty selectionCount=this.selectionCount clearSelection=this.clearSelection - selection=@data-table.selection - data-table=@data-table)}} + selection=@dataTable.selection + dataTable=@dataTable)}} {{!-- TODO: must we pass the data table itself? It is shared with the consumers. --}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-menu-selected.js b/addon/components/data-table/data-table-menu-selected.js index a627f38..83e2f2c 100644 --- a/addon/components/data-table/data-table-menu-selected.js +++ b/addon/components/data-table/data-table-menu-selected.js @@ -4,15 +4,15 @@ import Component from '@glimmer/component'; export default class DataTableMenuSelectedComponent extends Component { constructor() { super(...arguments); - this.args['data-table'].enableSelection = true; // TODO: is this the best way to handle such case? + this.args.dataTable.enableSelection = true; // TODO: is this the best way to handle such case? } get selectionCount() { - return this.args['data-table'].selection.length; + return this.args.dataTable.selection.length; } @action clearSelection() { - this.args['data-table'].clearSelection(); + this.args.dataTable.clearSelection(); } } diff --git a/addon/components/data-table/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs index f8144a3..f206b62 100644 --- a/addon/components/data-table/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table.hbs --}} {{#let - (component "data-table/data-table-menu-general" data-table=@data-table) - (component "data-table/data-table-menu-selected" data-table=@data-table) + (component "data-table/data-table-menu-general" data-table=@dataTable) + (component "data-table/data-table-menu-selected" data-table=@dataTable) as |general selected|}} {{yield general selected}} {{/let}} \ No newline at end of file diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 059e0ac..2bedf87 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table/data-table-content-body --}} {{yield (hash - columns=@data-table.parsedFields - fields=@data-table.fields + columns=@dataTable.parsedFields + fields=@dataTable.fields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item @@ -9,8 +9,8 @@ customFields=@customfields DataCell=(component "data-table/data-cell" - columns=@data-table.parsedFields - fields=@data-table.fields + columns=@dataTable.parsedFields + fields=@dataTable.fields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item diff --git a/addon/components/data-table/default-data-table-content-body.js b/addon/components/data-table/default-data-table-content-body.js index f1817b0..6192d4c 100644 --- a/addon/components/data-table/default-data-table-content-body.js +++ b/addon/components/data-table/default-data-table-content-body.js @@ -2,17 +2,17 @@ import Component from '@glimmer/component'; export default class DefaultDataTableContentBodyComponent extends Component { get allFields() { - return this.args['data-table'].parsedFields; // TODO: pass directly? + return this.args.dataTable.parsedFields; // TODO: pass directly? } get firstColumn() { - const parsedFields = this.args['data-table'].parsedFields; + const parsedFields = this.args.dataTable.parsedFields; if (parsedFields.length > 0) return parsedFields[0]; else return null; } get otherColumns() { - const parsedFields = this.args['data-table'].parsedFields; + const parsedFields = this.args.dataTable.parsedFields; if (parsedFields.length > 0) { let [, ...fields] = parsedFields; return fields; diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index cb897b2..2ab9b30 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -16,4 +16,4 @@ "data-table/default-data-table-content-body" linkedRoute=@linkedRoute customFields=@customFields - data-table=@data-table))}} \ No newline at end of file + dataTable=@dataTable))}} \ No newline at end of file diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 38251f0..737c0c0 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -67,18 +67,18 @@
{{!-- either we have a general block or we have to have a menu --}} - {{#if general.data-table.selectionIsEmpty}} - {{yield general.data-table to="general-menu"}} + {{#if general.dataTable.selectionIsEmpty}} + {{yield general.dataTable to="general-menu"}} {{/if}} - {{#unless selected.data-table.selectionIsEmpty}} + {{#unless selected.dataTable.selectionIsEmpty}} {{#if (has-block "selection-menu")}} {{yield selected to="selection-menu"}} {{else}} {{selected.selectionCount}} item(s) selected - {{yield (slice 0 selected.selectionCount selected.selection) selected.data-table + {{yield (slice 0 selected.selectionCount selected.selection) selected.dataTable to="selection-menu-actions"}} {{/if}} {{/unless}} diff --git a/tests/integration/components/data-table-content-body-test.no-js b/tests/integration/components/data-table-content-body-test.no-js index 8c479ae..e02ae58 100644 --- a/tests/integration/components/data-table-content-body-test.no-js +++ b/tests/integration/components/data-table-content-body-test.no-js @@ -23,7 +23,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('dataTable.selection', []); await render( - hbs`{{data-table/data-table-content-body content=content data-table=dataTable}}` + hbs`{{data-table/data-table-content-body content=content dataTable=dataTable}}` ); assert.dom('tr').exists({ count: 2 }, 'displays 2 rows'); @@ -50,12 +50,12 @@ module('Integration | Component | data table content body', function (hooks) { const jane = { firstName: 'Jane', lastName: 'Doe', age: 21 }; const jeff = { firstName: 'Jeff', lastName: 'Doe', age: 22 }; this.set('content', [john, jane, jeff]); - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); - this.set('data-table.selection', [jane]); + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable.selection', [jane]); await render( - hbs`{{data-table/data-table-content-body content=content data-table=data-table enableSelection=true}}` + hbs`{{data-table/data-table-content-body content=content dataTable=dataTable enableSelection=true}}` ); assert.equal(this.$('tr:first td').length, 4, 'displays 4 columns'); @@ -73,16 +73,16 @@ module('Integration | Component | data table content body', function (hooks) { const jane = { firstName: 'Jane', lastName: 'Doe', age: 21 }; const jeff = { firstName: 'Jeff', lastName: 'Doe', age: 22 }; this.set('content', [john, jane, jeff]); - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); - this.set('data-table.selection', [jane]); - this.set('data-table.addItemToSelection', () => - this.set('data-table.selection', [john, jane]) + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable.selection', [jane]); + this.set('dataTable.addItemToSelection', () => + this.set('dataTable.selection', [john, jane]) ); // mock function - this.set('data-table.removeItemFromSelection', function () {}); // mock function + this.set('dataTable.removeItemFromSelection', function () {}); // mock function await render( - hbs`{{data-table/data-table-content-body content=content data-table=data-table enableSelection=true}}` + hbs`{{data-table/data-table-content-body content=content dataTable=dataTable enableSelection=true}}` ); assert @@ -99,12 +99,12 @@ module('Integration | Component | data table content body', function (hooks) { const jane = { firstName: 'Jane', lastName: 'Doe', age: 21 }; const jeff = { firstName: 'Jeff', lastName: 'Doe', age: 22 }; this.set('content', [john, jane, jeff]); - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); - this.set('data-table.selection', []); + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable.selection', []); await render( - hbs`{{data-table/data-table-content-body content=content data-table=data-table enableLineNumbers=true}}` + hbs`{{data-table/data-table-content-body content=content dataTable=dataTable enableLineNumbers=true}}` ); assert.equal(this.$('tr:first td').length, 4, 'displays 4 columns'); @@ -124,10 +124,10 @@ module('Integration | Component | data table content body', function (hooks) { 'displays offset 3 on the third row' ); - this.set('data-table.page', 2); - this.set('data-table.size', 5); + this.set('dataTable.page', 2); + this.set('dataTable.size', 5); await render( - hbs`{{data-table/data-table-content-body content=content data-table=data-table enableLineNumbers=true}}` + hbs`{{data-table/data-table-content-body content=content dataTable=dataTable enableLineNumbers=true}}` ); assert.equal( @@ -156,12 +156,12 @@ module('Integration | Component | data table content body', function (hooks) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... }); this.set('noDataMessage', 'No data'); - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); - this.set('data-table.selection', []); + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable.selection', []); await render( - hbs`{{data-table/data-table-content-body noDataMessage=noDataMessage data-table=data-table}}` + hbs`{{data-table/data-table-content-body noDataMessage=noDataMessage dataTable=dataTable}}` ); assert .dom('td.no-data-message') @@ -172,7 +172,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('content', []); await render( - hbs`{{data-table/data-table-content-body content=content noDataMessage=noDataMessage data-table=data-table}}` + hbs`{{data-table/data-table-content-body content=content noDataMessage=noDataMessage dataTable=dataTable}}` ); assert .dom('td.no-data-message') @@ -183,7 +183,7 @@ module('Integration | Component | data table content body', function (hooks) { this.set('content', ['foo', 'bar']); await render( - hbs`{{data-table/data-table-content-body content=content noDataMessage=noDataMessage data-table=data-table}}` + hbs`{{data-table/data-table-content-body content=content noDataMessage=noDataMessage dataTable=dataTable}}` ); assert .dom('td.no-data-message') diff --git a/tests/integration/components/data-table-content-header-test.no-js b/tests/integration/components/data-table-content-header-test.no-js index be3368c..6ff2ee3 100644 --- a/tests/integration/components/data-table-content-header-test.no-js +++ b/tests/integration/components/data-table-content-header-test.no-js @@ -26,10 +26,10 @@ module('Integration | Component | data table content header', function (hooks) { }); test('display column headers', async function (assert) { - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); - await render(hbs`{{data-table-content-header data-table=data-table}}`); + await render(hbs`{{data-table-content-header dataTable=dataTable}}`); assert.dom('tr').exists({ count: 1 }, 'displays 1 header row'); assert.equal(this.$('tr:first th').length, 3, 'displays 3 column headers'); @@ -51,11 +51,11 @@ module('Integration | Component | data table content header', function (hooks) { }); test('add selection column header if enabled', async function (assert) { - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); await render( - hbs`{{data-table-content-header data-table=data-table enableSelection=true}}` + hbs`{{data-table-content-header dataTable=dataTable enableSelection=true}}` ); assert.dom('tr').exists({ count: 1 }, 'displays 1 header row'); @@ -68,11 +68,11 @@ module('Integration | Component | data table content header', function (hooks) { }); test('add line number column header if enabled', async function (assert) { - this.set('data-table', {}); - this.set('data-table.parsedFields', ['firstName', 'lastName', 'age']); + this.set('dataTable', {}); + this.set('dataTable.parsedFields', ['firstName', 'lastName', 'age']); await render( - hbs`{{data-table-content-header data-table=data-table enableLineNumbers=true}}` + hbs`{{data-table-content-header dataTable=dataTable enableLineNumbers=true}}` ); assert.dom('tr').exists({ count: 1 }, 'displays 1 header row'); diff --git a/tests/integration/components/data-table-menu-general-test.no-js b/tests/integration/components/data-table-menu-general-test.no-js index 57e1ad7..60c9fad 100644 --- a/tests/integration/components/data-table-menu-general-test.no-js +++ b/tests/integration/components/data-table-menu-general-test.no-js @@ -22,16 +22,16 @@ module('Integration | Component | data table menu general', function (hooks) { this.set('data-table', { selectionIsEmpty: true }); // Template block usage: await render(hbs` - {{#data-table-menu-general data-table=data-table}} + {{#data-table-menu-general dataTable=dataTable}} template block text {{/data-table-menu-general}} `); assert.dom('*').hasText('template block text'); - this.set('data-table', { selectionIsEmpty: false }); + this.set('dataTable', { selectionIsEmpty: false }); // Template block usage: await render(hbs` - {{#data-table-menu-general data-table=data-table}} + {{#data-table-menu-general dataTable=dataTable}} template block text {{/data-table-menu-general}} `); diff --git a/tests/integration/components/data-table-menu-selected-test.no-js b/tests/integration/components/data-table-menu-selected-test.no-js index ec3157c..5ab4e5d 100644 --- a/tests/integration/components/data-table-menu-selected-test.no-js +++ b/tests/integration/components/data-table-menu-selected-test.no-js @@ -7,10 +7,10 @@ module('Integration | Component | data table menu selected', function (hooks) { setupRenderingTest(hooks); test('it renders block only if data table selection is not empty', async function (assert) { - this.set('data-table', { selectionIsEmpty: true }); + this.set('dataTable', { selectionIsEmpty: true }); // Template block usage: await render(hbs` - {{#data-table-menu-selected data-table=data-table}} + {{#data-table-menu-selected dataTable=dataTable}} template block text {{/data-table-menu-selected}} `); @@ -18,23 +18,23 @@ module('Integration | Component | data table menu selected', function (hooks) { }); test('it renders selection count', async function (assert) { - this.set('data-table', { selectionIsEmpty: false, selection: ['foo'] }); + this.set('dataTable', { selectionIsEmpty: false, selection: ['foo'] }); // Template block usage: await render(hbs` - {{#data-table-menu-selected data-table=data-table}} + {{#data-table-menu-selected dataTable=dataTable}} template block text {{/data-table-menu-selected}} `); assert.dom('span.item-count').hasText('1 item(s) selected', 'item count 1'); - this.set('data-table', { + this.set('dataTable', { selectionIsEmpty: false, selection: ['foo', 'bar'], }); // Template block usage: await render(hbs` - {{#data-table-menu-selected data-table=data-table}} + {{#data-table-menu-selected dataTable=dataTable}} template block text {{/data-table-menu-selected}} `); @@ -45,13 +45,13 @@ module('Integration | Component | data table menu selected', function (hooks) { test('calls clearSelection on cancel button click', async function (assert) { assert.expect(2); // 2 asserts in this test - this.set('data-table', { selectionIsEmpty: false, selection: ['foo'] }); - this.set('data-table.clearSelection', function () { - assert.ok(true, 'data-table.clearSelection gets called'); + this.set('dataTable', { selectionIsEmpty: false, selection: ['foo'] }); + this.set('dataTable.clearSelection', function () { + assert.ok(true, 'dataTable.clearSelection gets called'); }); // Template block usage: await render(hbs` - {{#data-table-menu-selected data-table=data-table}} + {{#data-table-menu-selected dataTable=dataTable}} template block text {{/data-table-menu-selected}} `); diff --git a/tests/integration/components/default-data-table-content-body-test.no-js b/tests/integration/components/default-data-table-content-body-test.no-js index b4c2644..5966f01 100644 --- a/tests/integration/components/default-data-table-content-body-test.no-js +++ b/tests/integration/components/default-data-table-content-body-test.no-js @@ -12,19 +12,19 @@ module( // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... }); - this.set('data-table', { + this.set('dataTable', { parsedFields: ['firstName', 'lastName', 'age'], }); await render( - hbs`{{default-data-table-content-body data-table=data-table}}` + hbs`{{default-data-table-content-body dataTable=dataTable}}` ); assert.dom().hasText(''); // Template block usage: await render(hbs` - + template block text `); From 7c5baefcb417f1c01a44a03517e7c81d56a81d88 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Wed, 26 Jun 2024 16:53:20 +0200 Subject: [PATCH 30/56] Rename lineNumbers to enableLineNumbers More obvious naming. Co-author: @erikap --- addon/components/data-table.hbs | 2 +- addon/components/raw-data-table.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index a9cc270..9240550 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -10,7 +10,7 @@ content=@content noDataMessage=this.noDataMessage enableSelection=this.enableSelection - enableLineNumbers=@lineNumbers + enableLineNumbers=@enableLineNumbers onClickRow=@onClickRow updateSort=this.updateSort customHeaders=this.customHeaders diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 737c0c0..12051ab 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -11,7 +11,7 @@ @selection={{@selection}} @noDataMessage={{@noDataMessage}} @isLoading={{@isloading}} - @lineNumbers={{@lineNumbers}} + @enableLineNumbers={{@enableLineNumbers}} @updatePage={{@updatePage}} @updatePageSize={{@updatePageSize}} {{!-- change to size or vice-versa? --}} @updateFilter={{@updateFilter}} From a810f036a549fd680e119a3e2a91132cd69a2199 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Wed, 26 Jun 2024 17:00:25 +0200 Subject: [PATCH 31/56] Provide warnings when update functions are not supplied A warning is shown on missing update functions. This will not error before they're used but it's a bit more helpful. Co-author: @erikap --- addon/components/data-table.hbs | 2 +- addon/components/data-table.js | 33 +++++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 9240550..fb4bcbe 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -26,7 +26,7 @@ sizeOptions=this.sizeOptions total=@content.meta.count links=@content.meta.pagination - updatePage=@updatePage + updatePage=this.updatePage updateSize=this.updatePageSize) Menu=(component "data-table/data-table-menu" dataTable=this) diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 0122efd..37de5b6 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -152,20 +152,41 @@ export default class DataTable extends Component { @action updatePageSize(size) { - this.args.updatePage(0); - this.args.updatePageSize(size); + if( !this.args.updatePageSize ) { + console.error(`Could not update page size to ${size} because @updatePageSize was not supplied to data table`); + } else { + this.updatePage(0); + this.args.updatePageSize(size); + } } @action updateFilter(filter) { - this.args.updatePage(0); - this.args.updateFilter(filter); + if( !this.args.updateFilter ) { + console.error(`Could not update filter to '${filter}' because @updateFilter was not supplied to data table`); + } else { + this.updatePage(0); + this.args.updateFilter(filter); + } } @action updateSort(sort) { - this.args.updatePage(0); - this.args.updateSort(sort); + if( !this.args.updateSort ) { + console.error(`Could not update sorting to '${sort}' because @updateSort was not supplied to data table`); + } else { + this.updatePage(0); + this.args.updateSort(sort); + } + } + + @action + updatePage(page) { + if( !this.args.updatePage ) { + console.error(`Could not update page to ${page} because @updatePage was not supplied to data table`); + } else { + this.args.updatePage(page); + } } addItemToSelection(item) { From 1c157932793a4875938bf2488ddb5831200de2f5 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Wed, 26 Jun 2024 17:13:35 +0200 Subject: [PATCH 32/56] Rename isloading to isLoading This only appears in the RawDataTable component at this point. Best to update it as it will likely become the preferred starting point. --- addon/components/raw-data-table.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 12051ab..de419b4 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -10,7 +10,7 @@ @hasMenu={{@hasMenu}} @selection={{@selection}} @noDataMessage={{@noDataMessage}} - @isLoading={{@isloading}} + @isLoading={{@isLoading}} @enableLineNumbers={{@enableLineNumbers}} @updatePage={{@updatePage}} @updatePageSize={{@updatePageSize}} {{!-- change to size or vice-versa? --}} From 4ef37ed732cfb73fa682e1a56e8b2576bd3fee87 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Thu, 18 Jul 2024 13:53:11 +0200 Subject: [PATCH 33/56] Remove usage of customFields and parsedFields These properties have been replaced with the fields object. Co-author: @erikap --- addon/components/data-table.hbs | 1 - addon/components/data-table/data-cell.hbs | 3 +-- .../data-table/data-table-content-body.hbs | 4 ++-- .../data-table/data-table-content-header.hbs | 4 +--- addon/components/data-table/data-table-content.hbs | 1 - .../data-table/default-data-table-content-body.hbs | 6 ++---- .../data-table/default-data-table-content-body.js | 13 +++---------- addon/components/data-table/row.hbs | 4 ++-- 8 files changed, 11 insertions(+), 25 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index fb4bcbe..a42a765 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -14,7 +14,6 @@ onClickRow=@onClickRow updateSort=this.updateSort customHeaders=this.customHeaders - customFields=this.customFields fields=this.fields links=@links rowLink=@rowLink diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index c205c82..dd3dc56 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -1,11 +1,10 @@ {{!-- Used in: data-table/default-data-table-content-body --}} {{yield (hash - columns=@columns firstColumn=@firstColumn otherColumns=@otherColumns item=@item label=@column.label - customFields=@customFields + fields=@fields linkedRoute=@linkedRoute isCustom=this.isCustom hasCustom=this.hasCustom diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index efa4383..04d638d 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -13,7 +13,7 @@ linkedRoutes=@linkedRoutes rowLink=@rowLink noDataMessage=@noDataMessage - customFields=@customFields + fields=@fields Row=(component "data-table/row" dataTable=@dataTable enableLineNumbers=@enableLineNumbers @@ -23,5 +23,5 @@ hasClickRowAction=(and (or @onClickRow @rowLink) true) onClickRow=this.onClickRow linkedRoutes=@linkedRoutes - customFields=@customFields + fields=@fields toggleSelected=this.updateSelection))}} diff --git a/addon/components/data-table/data-table-content-header.hbs b/addon/components/data-table/data-table-content-header.hbs index 5922a26..5b90b4a 100644 --- a/addon/components/data-table/data-table-content-header.hbs +++ b/addon/components/data-table/data-table-content-header.hbs @@ -3,14 +3,12 @@ enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers fields=@fields - parsedFields=@dataTable.parsedFields currentSorting=@dataTable.sort updateSort=@updateSort customHeaders=@customHeaders hasLinks=@hasLinks dataHeadersInfo=(hash fields=@fields - parsedFields=@dataTable.parsedFields currentSorting=@dataTable.sort customHeaders=@customHeaders updateSort=@updateSort) @@ -19,4 +17,4 @@ fields=@fields currentSorting=@dataTable.sort customHeaders=@customHeaders - updateSort=@updateSort))}} \ No newline at end of file + updateSort=@updateSort))}} diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 678ebda..11f87b8 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -17,7 +17,6 @@ onClickRow=@onClickRow linkedRoutes=this.linkedRoutes rowLink=@rowLink - customFields=@customFields dataTable=@dataTable fields=@fields) dataTable=@dataTable)}} diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/default-data-table-content-body.hbs index 2bedf87..6f777b2 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/default-data-table-content-body.hbs @@ -1,18 +1,16 @@ {{!-- Used in: data-table/data-table-content-body --}} {{yield (hash - columns=@dataTable.parsedFields fields=@dataTable.fields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item linkedRoute=@linkedRoute - customFields=@customfields + fields=@fields DataCell=(component "data-table/data-cell" - columns=@dataTable.parsedFields fields=@dataTable.fields firstColumn=this.firstColumn otherColumns=this.otherColumns item=@item - customFields=@customFields + fields=@fields linkedRoute=@linkedRoute))}} diff --git a/addon/components/data-table/default-data-table-content-body.js b/addon/components/data-table/default-data-table-content-body.js index 6192d4c..0275bd3 100644 --- a/addon/components/data-table/default-data-table-content-body.js +++ b/addon/components/data-table/default-data-table-content-body.js @@ -1,20 +1,13 @@ import Component from '@glimmer/component'; export default class DefaultDataTableContentBodyComponent extends Component { - get allFields() { - return this.args.dataTable.parsedFields; // TODO: pass directly? - } - get firstColumn() { - const parsedFields = this.args.dataTable.parsedFields; - if (parsedFields.length > 0) return parsedFields[0]; - else return null; + return this.args.fields?.[0] || null; } get otherColumns() { - const parsedFields = this.args.dataTable.parsedFields; - if (parsedFields.length > 0) { - let [, ...fields] = parsedFields; + if (this.args.fields?.length) { + let [, ...fields] = this.args.fields; return fields; } else { return []; diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index 2ab9b30..6c75564 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -11,9 +11,9 @@ toggleSelected=(fn @toggleSelected @wrapper) isSelected=(includes @wrapper.item @selection) linkedRoutes=@linkedRoutes - customFields=@customFields + fields=@fields DataCells=(component "data-table/default-data-table-content-body" linkedRoute=@linkedRoute - customFields=@customFields + fields=@fields dataTable=@dataTable))}} \ No newline at end of file From fa5dc83b920c3391153f367ea57e654aae4b0a86 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Thu, 18 Jul 2024 13:59:19 +0200 Subject: [PATCH 34/56] All known implementations have been upgraded Co-author: @erikap --- addon/components/data-table.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 37de5b6..edbe17c 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -139,11 +139,6 @@ export default class DataTable extends Component { return null; } - get parsedFields() { - console.warn("Use of parsedFields in dataTable is deprecated"); - return this.fieldsWithMeta.map(({attribute}) => attribute); - } - get searchPlaceholder() { return this.args.searchPlaceholder === undefined ? 'Search input' From 9f553d70ea8dd57008ed40f1c0c72508fa347ba8 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Thu, 18 Jul 2024 14:17:54 +0200 Subject: [PATCH 35/56] Cleanup search variables Co-author: @erikap --- addon/components/data-table.hbs | 6 +++--- addon/components/data-table.js | 11 ++++++++++- addon/components/data-table/text-search.hbs | 8 ++++---- addon/components/data-table/text-search.js | 2 +- addon/components/raw-data-table.hbs | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index a42a765..361118d 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -2,10 +2,10 @@ (hash Search=(component "data-table/text-search" filter=@filter - auto=this.autoSearch + placeholder=this.searchPlaceholder + autoSearch=this.autoSearch updateFilter=this.updateFilter - wait=this.searchDebounceTime - placeholder=this.searchPlaceholder) + searchDebounceTime=this.searchDebounceTime) Content=(component "data-table/data-table-content" content=@content noDataMessage=this.noDataMessage diff --git a/addon/components/data-table.js b/addon/components/data-table.js index edbe17c..3d00655 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -28,9 +28,18 @@ export default class DataTable extends Component { return this.args.isLoading; } + /** + * 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 + * 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 - ? 2000 + ? isNaN(this.args.autoSearch) ? 2000 : this.args.autoSearch : this.args.searchDebounceTime; } diff --git a/addon/components/data-table/text-search.hbs b/addon/components/data-table/text-search.hbs index 97e1439..a204a12 100644 --- a/addon/components/data-table/text-search.hbs +++ b/addon/components/data-table/text-search.hbs @@ -1,8 +1,8 @@ {{!-- Used in data-table.hbs --}} {{yield (hash + filter=@filter + placeholder=@placeholder + autoSearch=@autoSearch submitForm=this.submitForm - placeholder=this.placeholder handleAutoInput=this.handleAutoInput - handleDirectInput=this.handleDirectInput - filter=@filter - auto=@auto)}} + handleDirectInput=this.handleDirectInput)}} diff --git a/addon/components/data-table/text-search.js b/addon/components/data-table/text-search.js index 55d3c2f..8000fa0 100644 --- a/addon/components/data-table/text-search.js +++ b/addon/components/data-table/text-search.js @@ -10,7 +10,7 @@ export default class TextSearchComponent extends Component { @action handleAutoInput(event) { this.enteredValue = event.target.value; - this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.wait); + this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); } submitCurrent() { diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index de419b4..d54cddb 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -34,7 +34,7 @@
From 2fae217080e37834321c7df437ca1fa249ff1490 Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Fri, 19 Jul 2024 15:27:25 +0200 Subject: [PATCH 40/56] Add summarized page options to pagination component Co-author: @madnificent From @rollvolet <3 --- .../data-table/number-pagination.hbs | 4 ++ .../data-table/number-pagination.js | 64 ++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index 0f70d4e..769b7e3 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -6,7 +6,9 @@ hasTotal=this.hasTotal pageSize=@size pageNumber=this.humanPage + numberOfPages=this.numberOfPages pageOptions=this.pageOptions + summarizedPageOptions=this.summarizedPageOptions sizeOptions=@sizeOptions firstPage=this.firstPage lastPage=this.lastPage @@ -20,5 +22,7 @@ hasMultiplePages=this.hasMultiplePages isFirstPage=this.isFirstPage isLastPage=this.isLastPage + hasPreviousPage=this.hasPreviousPage + hasNextPage=this.hasNextPage meta=@meta backendPageOffset=this.backendPageOffset)}} \ No newline at end of file diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index 061a87d..6bae6a5 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -115,6 +115,14 @@ export default class NumberPaginationComponent extends Component { return this.humanPage == this.lastPage; } + get hasPreviousPage() { + return this.humanPage > this.firstPage; + } + + get hasNextPage() { + return this.humanPage < this.lastPage; + } + get hasMultiplePages() { return this.lastPage > this.firstPage; } @@ -141,18 +149,68 @@ export default class NumberPaginationComponent extends Component { return this.startItem - 1 + this.args.itemsOnCurrentPage; } + get numberOfPages() { + return this.lastPage - this.firstPage + 1; + } + /** * Supplies an array with all available pages. */ get pageOptions() { - const nbOfPages = 1 + this.lastPage - this.firstPage; - return Array.from( - new Array(nbOfPages), + new Array(this.numberOfPages), (_val, index) => this.firstPage + index ); } + /** + * Page selectors to show + * Examples: (~x~ indicates current page, more indicates ellipsis) + * [~1~, 2, 3, more, 8, 9, 10] + * [1, 2, 3, more, 8, 9, ~10~] + * [1, more, 5, 6, ~7~, 8, 9, 10] + * [1, more, 3, 4, ~5~, 6, 7, more, 10] + * [1, more, 7, 8, ~9~, 10] + */ + get summarizedPageOptions() { + const more = 'more'; + + if (this.numberOfPages > 0) { + if (this.isFirstPage || this.isLastPage) { + const x = this.firstPage; + const leftWindow = [x, x + 1, x + 2].filter((i) => i <= this.lastPage); + const y = this.lastPage; + const rightWindow = [y - 2, y - 1, y].filter((i) => i >= this.firstPage); + const pages = [...new Set([...leftWindow, ...rightWindow])].sort((a, b) => a - b); + if (pages.length == 6 && pages[2] < pages[3] - 1) { + return [...leftWindow, more, ...rightWindow]; + } else { + return pages; + } + } else { + const x = this.humanPage; + const currentPageWindow = [x - 2, x - 1, x, x + 1, x + 2].filter( + (i) => i >= this.firstPage && i <= this.lastPage + ); + let prepend = []; + let append = []; + if (currentPageWindow.length) { + const first = currentPageWindow[0]; + if (first > this.firstPage) { + prepend = first == this.firstPage + 1 ? [this.firstPage] : [this.firstPage, more]; + } + const last = currentPageWindow[currentPageWindow.length - 1]; + if (last < this.lastPage) { + append = last == this.lastPage - 1 ? [this.lastPage] : [more, this.lastPage]; + } + } + return [...prepend, ...currentPageWindow, ...append]; + } + } else { + return [this.firstPage]; + } + } + get total() { if( this.args.total !== undefined ) return this.args.total; From 675130f8212a16cf5d7c8b64a264146c4a773cf3 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Fri, 19 Jul 2024 17:36:24 +0200 Subject: [PATCH 41/56] Improvements to menu Co-author: @erikap --- addon/components/data-table.hbs | 3 +- addon/components/data-table.js | 27 ++++++-------- .../data-table/data-table-content-body.js | 15 ++++---- .../data-table/data-table-menu-general.hbs | 7 ++-- .../data-table/data-table-menu-selected.hbs | 6 ++-- .../data-table/data-table-menu-selected.js | 10 ++---- .../components/data-table/data-table-menu.hbs | 6 ++-- addon/components/raw-data-table.hbs | 35 ++++++++++--------- 8 files changed, 49 insertions(+), 60 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 1dc3bf5..16f6d2d 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -9,7 +9,7 @@ Content=(component "data-table/data-table-content" content=@content noDataMessage=this.noDataMessage - enableSelection=this.enableSelection + enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers onClickRow=@onClickRow sort=@sort @@ -32,6 +32,7 @@ updateSize=this.updatePageSize backendPageOffset=@backendPageOffset) Menu=(component "data-table/data-table-menu" + enableSelection=@enableSelection dataTable=this) content=@content enableSearch=this.enableSearch diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 96e4fd3..429cd27 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -43,17 +43,8 @@ export default class DataTable extends Component { : this.args.searchDebounceTime; } - @tracked - _enableSelection = undefined; - get enableSelection() { - return this._enableSelection === undefined - ? this.args.hasMenu - : this._enableSelection; - } - - set enableSelection(value) { - this._enableSelection = value; + return this.args.enableSelection; } get selectionIsEmpty() { @@ -74,9 +65,12 @@ export default class DataTable extends Component { _size = undefined; get size() { - if (this._size === undefined && this.args.size) return this.args.size; - else if (this._size) return this._size; - else return 5; + if (this._size === undefined && this.args.size) + return this.args.size; + else if (this._size) + return this._size; + else + return 5; } set size(newSize) { this._size = newSize; @@ -95,8 +89,6 @@ export default class DataTable extends Component { } } - @tracked hasMenu = false; // old comment: set from inner component, migth fail with nested if - get enableSearch() { return this.args.filter !== undefined; } @@ -218,12 +210,15 @@ export default class DataTable extends Component { } } + @action addItemToSelection(item) { - this.selection = [item, ...this.selection]; + this.selection = [...new Set([item, ...this.selection])]; } + @action removeItemFromSelection(item) { this.selection = this.selection.filter((x) => x !== item); } + @action clearSelection() { this.selection = []; } diff --git a/addon/components/data-table/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js index dd304b4..3b27459 100644 --- a/addon/components/data-table/data-table-content-body.js +++ b/addon/components/data-table/data-table-content-body.js @@ -1,3 +1,4 @@ +import { cached } from '@glimmer/tracking'; import { get } from '@ember/object'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; @@ -16,6 +17,7 @@ export default class DataTableContentBodyComponent extends Component { return offset; } + @cached get wrappedItems() { const selection = this.args.dataTable.selection || []; // TODO: should the dataTable ensure this is an array? const content = this.args.content; @@ -37,15 +39,10 @@ export default class DataTableContentBodyComponent extends Component { @action updateSelection(selectedWrapper, event) { - selectedWrapper.isSelected = event.target.checked; - - this.wrappedItems.forEach((wrapper) => { - if (wrapper.isSelected) { - this.args.dataTable.addItemToSelection(wrapper.item); // TODO: pass on addItemToSelection directly? - } else { - this.args.dataTable.removeItemFromSelection(wrapper.item); // TODO: pass on removeItemFromSelection directly? - } - }); + if( event.target.checked ) + this.args.dataTable.addItemToSelection(selectedWrapper.item); + else + this.args.dataTable.removeItemFromSelection(selectedWrapper.item); } @action diff --git a/addon/components/data-table/data-table-menu-general.hbs b/addon/components/data-table/data-table-menu-general.hbs index fad1606..aa4bd58 100644 --- a/addon/components/data-table/data-table-menu-general.hbs +++ b/addon/components/data-table/data-table-menu-general.hbs @@ -1,4 +1,5 @@ {{!-- Used in: data-table/data-table-menu --}} -{{#if @dataTable.selectionIsEmpty}} - {{yield}} -{{/if}} +{{yield (hash + dataTable=@dataTable + selectionIsEmpty=@dataTable.selectionIsEmpty) +}} diff --git a/addon/components/data-table/data-table-menu-selected.hbs b/addon/components/data-table/data-table-menu-selected.hbs index d2b38a8..f232183 100644 --- a/addon/components/data-table/data-table-menu-selected.hbs +++ b/addon/components/data-table/data-table-menu-selected.hbs @@ -1,9 +1,9 @@ {{!-- Used in: data-table/data-table-menu --}} {{yield (hash selectionIsEmpty=@dataTable.selectionIsEmpty - selectionCount=this.selectionCount - clearSelection=this.clearSelection - selection=@dataTable.selection + selectionCount=@dataTable.selection.length + clearSelection=@dataTable.clearSelection + selection=this.copiedSelection dataTable=@dataTable)}} {{!-- TODO: must we pass the data table itself? It is shared with the consumers. --}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-menu-selected.js b/addon/components/data-table/data-table-menu-selected.js index 83e2f2c..29c343a 100644 --- a/addon/components/data-table/data-table-menu-selected.js +++ b/addon/components/data-table/data-table-menu-selected.js @@ -2,17 +2,11 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; export default class DataTableMenuSelectedComponent extends Component { - constructor() { - super(...arguments); - this.args.dataTable.enableSelection = true; // TODO: is this the best way to handle such case? - } - get selectionCount() { return this.args.dataTable.selection.length; } - @action - clearSelection() { - this.args.dataTable.clearSelection(); + get copiedSelection() { + return [...this.args.dataTable.selection]; } } diff --git a/addon/components/data-table/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs index f206b62..635a3ff 100644 --- a/addon/components/data-table/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table.hbs --}} {{#let - (component "data-table/data-table-menu-general" data-table=@dataTable) - (component "data-table/data-table-menu-selected" data-table=@dataTable) + (component "data-table/data-table-menu-general" dataTable=@dataTable) + (component "data-table/data-table-menu-selected" dataTable=@dataTable) as |general selected|}} - {{yield general selected}} + {{yield general selected @dataTable.enableSelection}} {{/let}} \ No newline at end of file diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 184d865..6ac1d72 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -65,32 +65,33 @@ {{!-- END: search --}} {{!-- START: menu --}} - + {{#if (has-block "menu")}} - {{yield (hash General Selected)}} + {{yield (hash General Selected) to="menu"}} {{else}} - {{#if dt.dataTable.enableSelection}} -
- {{!-- either we have a general block or we have to have a menu --}} - - {{#if general.dataTable.selectionIsEmpty}} - {{yield general.dataTable to="general-menu"}} - {{/if}} - +
+ {{!-- either we have a general block or we have to have a menu --}} + + {{#if general.selectionIsEmpty}} + {{yield general to="general-menu"}} + {{/if}} + + {{#if enableSelection}} - {{#unless selected.dataTable.selectionIsEmpty}} + {{#unless selected.selectionIsEmpty}} {{#if (has-block "selection-menu")}} {{yield selected to="selection-menu"}} {{else}} - {{selected.selectionCount}} item(s) selected - - {{yield (slice 0 selected.selectionCount selected.selection) selected.dataTable - to="selection-menu-actions"}} + {{#if (has-block "selection-menu-actions")}} + {{selected.selectionCount}} item(s) selected + + {{yield selected to="selection-menu-actions"}} + {{/if}} {{/if}} {{/unless}} -
- {{/if}} + {{/if}} +
{{/if}}
{{!-- END: menu --}} From c1dac2f2c05080fa6b66a23e7ce95c5bcd223952 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Fri, 19 Jul 2024 17:48:32 +0200 Subject: [PATCH 42/56] Cleaner handling of pages Co-author: @erikap --- addon/components/data-table.hbs | 4 ++-- addon/components/data-table.js | 19 +++++-------------- .../data-table/number-pagination.js | 3 +-- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 16f6d2d..3b5f963 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -22,8 +22,8 @@ rowLinkModelProperty=this.rowLinkModelProperty dataTable=this) Pagination=(component "data-table/number-pagination" - page=@page - size=@size + page=this.page + size=this.size itemsOnCurrentPage=@content.length sizeOptions=this.sizeOptions total=@total diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 429cd27..fdb8561 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -55,25 +55,16 @@ export default class DataTable extends Component { return this.args.enableSizes === undefined ? true : this.args.enableSizes; } - get sort() { - return this.args.sort; - } - get page() { - return this.args.page; + return this.args.page || 0; } - _size = undefined; get size() { - if (this._size === undefined && this.args.size) + if ( this.args.size ) { return this.args.size; - else if (this._size) - return this._size; - else + } else { return 5; - } - set size(newSize) { - this._size = newSize; + } } get sizeOptions() { @@ -81,7 +72,7 @@ export default class DataTable extends Component { return null; } else { const sizeOptions = this.args.sizes || [5, 10, 25, 50, 100]; - if (!sizeOptions.includes(this.size)) { + if (!sizeOptions.includes(this.size) && this.size) { sizeOptions.push(this.size); } sizeOptions.sort((a, b) => a - b); diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index 6bae6a5..61516b9 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -91,8 +91,7 @@ export default class NumberPaginationComponent extends Component { } get lastPage() { - const backendLastPageNumber = Math.floor(this.total / this.args.size); - return backendToHuman(backendLastPageNumber, this.backendPageOffset); + return Math.ceil( (0.0 + this.total) / this.args.size); } get previousPage() { From 91d3cecd3e607808457cf02463eddbddb06cd388 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Fri, 19 Jul 2024 18:00:02 +0200 Subject: [PATCH 43/56] Rename default-data-table-content-body to data-cells Co-author: @erikap --- addon/components/data-table/data-cell.hbs | 2 +- ...-table-content-body.hbs => data-cells.hbs} | 4 +-- ...ta-table-content-body.js => data-cells.js} | 2 +- addon/components/data-table/row.hbs | 2 +- ...ta-table-content-body.js => data-cells.js} | 2 +- .../components/data-table/data-cells-test.js | 26 +++++++++++++++++++ .../data-table/data-table-content-test.js | 2 +- 7 files changed, 33 insertions(+), 7 deletions(-) rename addon/components/data-table/{default-data-table-content-body.hbs => data-cells.hbs} (82%) rename addon/components/data-table/{default-data-table-content-body.js => data-cells.js} (78%) rename app/components/data-table/{default-data-table-content-body.js => data-cells.js} (65%) create mode 100644 tests/integration/components/data-table/data-cells-test.js diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index f230d9e..c4fab44 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -1,4 +1,4 @@ -{{!-- Used in: data-table/default-data-table-content-body --}} +{{!-- Used in: data-table/data-cells --}} {{yield (hash firstColumn=@firstColumn otherColumns=@otherColumns diff --git a/addon/components/data-table/default-data-table-content-body.hbs b/addon/components/data-table/data-cells.hbs similarity index 82% rename from addon/components/data-table/default-data-table-content-body.hbs rename to addon/components/data-table/data-cells.hbs index aaa139d..a0bfebb 100644 --- a/addon/components/data-table/default-data-table-content-body.hbs +++ b/addon/components/data-table/data-cells.hbs @@ -1,4 +1,4 @@ -{{!-- Used in: data-table/data-table-content-body --}} +{{!-- Used in: data-table/row --}} {{yield (hash fields=@dataTable.fields firstColumn=this.firstColumn @@ -9,7 +9,7 @@ rowLinkModel=@wrapper.rowLinkModel fields=@fields DataCell=(component - "data-table/data-cell" + "data-table/data-cell" firstColumn=this.firstColumn otherColumns=this.otherColumns wrapper=@wrapper diff --git a/addon/components/data-table/default-data-table-content-body.js b/addon/components/data-table/data-cells.js similarity index 78% rename from addon/components/data-table/default-data-table-content-body.js rename to addon/components/data-table/data-cells.js index 0275bd3..3b607d1 100644 --- a/addon/components/data-table/default-data-table-content-body.js +++ b/addon/components/data-table/data-cells.js @@ -1,6 +1,6 @@ import Component from '@glimmer/component'; -export default class DefaultDataTableContentBodyComponent extends Component { +export default class DataTableDataCellsComponent extends Component { get firstColumn() { return this.args.fields?.[0] || null; } diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index 2adc331..a82a499 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -13,7 +13,7 @@ linkedRoutes=this.linkedRoutes fields=@fields DataCells=(component - "data-table/default-data-table-content-body" + "data-table/data-cells" fields=@fields linkedRoutes=this.linkedRoutes dataTable=@dataTable))}} \ No newline at end of file diff --git a/app/components/data-table/default-data-table-content-body.js b/app/components/data-table/data-cells.js similarity index 65% rename from app/components/data-table/default-data-table-content-body.js rename to app/components/data-table/data-cells.js index bd3f185..e68d66a 100644 --- a/app/components/data-table/default-data-table-content-body.js +++ b/app/components/data-table/data-cells.js @@ -1 +1 @@ -export { default } from 'ember-data-table/components/data-table/default-data-table-content-body'; +export { default } from 'ember-data-table/components/data-table/data-cells'; \ No newline at end of file diff --git a/tests/integration/components/data-table/data-cells-test.js b/tests/integration/components/data-table/data-cells-test.js new file mode 100644 index 0000000..bced827 --- /dev/null +++ b/tests/integration/components/data-table/data-cells-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | data-table/data-cells', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom(this.element).hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); diff --git a/tests/unit/components/data-table/data-table-content-test.js b/tests/unit/components/data-table/data-table-content-test.js index b84dbc7..99cf300 100644 --- a/tests/unit/components/data-table/data-table-content-test.js +++ b/tests/unit/components/data-table/data-table-content-test.js @@ -6,7 +6,7 @@ function convertDefinition(string) { } module('Unit | Component | data-table-content', function() { - test('it strips underscores', (assert) => { + test('it strips underscores', function(assert) { const checks = [["one", "one"], ["one_two", "one two"], ["one_two_three", "one two three"], From a46b5119508d3adab27bf54adea93b5647267c55 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 23 Jul 2024 12:33:14 +0200 Subject: [PATCH 44/56] Work towards new readme for ember-data-table The readme would need to be completely revamped for the new data table. This should go a long way towards that. --- README.md | 441 +++++++++++++++------------- addon/components/data-table.hbs | 2 +- addon/components/raw-data-table.hbs | 2 +- 3 files changed, 245 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index d8b9cf9..ee4b61a 100644 --- a/README.md +++ b/README.md @@ -2,243 +2,288 @@ [![Build Status](https://travis-ci.org/mu-semtech/ember-data-table.svg?branch=master)](https://travis-ci.org/mu-semtech/ember-data-table) [![npm version](https://badge.fury.io/js/ember-data-table.svg)](https://badge.fury.io/js/ember-data-table) -Data table for Ember based on a JSONAPI compliant backend. +Data table for EmberJS -Have a look at [ember-paper-data-table](https://github.com/mu-semtech/emper-paper-data-table) to get a data table styled with [ember-paper](https://github.com/miguelcobain/ember-paper). +## Tutorials + +### Add basic Ember Data Table + +It is advised to adapt Ember Data Table to your specific design setup or use a variant implemented for your setup. To check if things are working, you can add the `RawDataTable` to your application. + +Let's generate a route for products first: -## Installation -If you're using Ember > v3.8 ```bash -ember install ember-data-table +ember g route products/index ``` -For Ember < v3.8, use version 1.x of the addon +We will assume a model exists with `label` and `price` which we could generate using: + ```bash -ember install ember-data-table@1.2.2 +ember g model product label:string price:number ``` -## Getting started -Include the `DataTableRouteMixin` in the route which model you want to show in the data table. Configure the model name. +Next we ensure content is fetched from the backend using standard model hooks and query parameters are set up. Extending from the provided Route and Controller is the shortest form. + +For the route stored in `/app/routes/products/index.js` write: ```javascript -import Ember from 'ember'; -import DataTableRouteMixin from 'ember-data-table/mixins/route'; +import DataTableRoute from 'ember-data-table/route'; -export default Ember.Route.extend(DataTableRouteMixin, { - modelName: 'blogpost' -}); +export default class ProductsIndexRoute extends DataTableRoute { + modelName = 'product'; +} ``` -Next, include the data table in your template: - -```htmlbars -{{data-table - content=model - fields="firstName lastName age created modified" - isLoading=isLoadingModel - filter=filter - sort=sort - page=page - size=size -}} -``` +For the controller stored in `/app/controllers/product/index.js` write: -Note: the filtering, sorting and pagination isn't done at the frontend. By including the `DataTableRouteMixin` in the route each change to the `filter`, `sort`, `page` and `size` params will result in a new request to the backend. The `DataTableRouteMixin` also sets an isLoadingModel flag while the route's model is being loaded. - -Have a look at [Customizing the data table](https://github.com/erikap/ember-data-table#customizing-the-data-table) to learn how you can customize the data table's header and body. - -## Data table component - -### Specification - -The following parameters can be passed to the data-table component: - -| Parameter | Required | Default | Description | -|-----------|----------|---------|-------------| -| content | x | | a list of resources to be displayed in the table | -| fields | | | names of the model fields to show as columns (seperated by whitespace) | -| isLoading | | false | shows a spinner instead of the table content if true | -| filter | | | current value of the text search | -| sort | | | field by which the data is currently sorted | -| page | | | number of the page that is currently displayed | -| size | | | number of items shown on one page | -| enableSizes | | true | flag to enable page size options dropdown | -| sizes | | [5, 10, 25, 50, 100] | array of page size options (numbers) | -| link | | | name of the route the first column will link to. The selected row will be passed as a parameter. | -| onClickRow | | | action sent when a row is clicked. Takes the clicked item as a parameter. | -| autoSearch | | true | whether filter value is updated automatically while typing (with a debounce) or user must click a search button explicitly to set the filter value. -| noDataMessage | | No data | message to be shown when there is no content | -| lineNumbers | | false | display a line number per table row (default: false). Must be true or false. | -| searchDebounceTime | | 2000 | debounce time of the search action of the data table. Must be integer. | - -By default the data table will make each column sortable. The search text box is only shown if the `filter` parameter is bound. Pagination is only shown if the pagination metadata is set on the model (see the [Ember Data Table Serializer mixin](https://github.com/mu-semtech/ember-data-table#serializer)). - -### Customizing the data table -The way the data is shown in the table can be customized by defining a `content` block instead of a `fields` parameter. - -```htmlbars -{{#data-table content=model filter=filter sort=sort page=page size=size onClickRow=(action "clickRow") as |t|}} - {{#t.content as |c|}} - {{#c.header}} - {{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 - {{/c.header}} - {{#c.body as |row|}} - {{row.firstName}} - {{row.lastName}} - {{row.age}} - {{moment-format row.created}} - {{moment-format row.modified}} - {{/c.body}} - {{/t.content}} -{{/data-table}} -``` -Have a look at the [helper components](https://github.com/mu-semtech/ember-data-table#helper-components). - -### Adding actions to the data table -The user can add actions on top of the data table by providing a `menu` block. -```htmlbars -{{#data-table content=model filter=filter sort=sort page=page size=size isLoading=isLoadingModel as |t|}} - {{#t.menu as |menu|}} - {{#menu.general}} - {{#paper-button onClick=(action "export") accent=true noInk=true}}Export{{/paper-button}} - {{#paper-button onClick=(action "print") accent=true noInk=true}}Print{{/paper-button}} - {{/menu.general}} - {{#menu.selected as |selection datatable|}} - {{#paper-button onClick=(action "delete" selection table) accent=true noInk=true}}Delete{{/paper-button}} - {{/menu.selected}} - {{/t.menu}} - {{#t.content as |c|}} - ... - {{/t.content}} -{{/data-table}} -``` -The menu block consists of a `general` and a `selected` block. The `menu.general` is shown by default. The `menu.selected` is shown when one or more rows in the data table are selected. - -When applying an action on a selection, the currently selected rows can be provided to the action by the `selection` parameter. The user must reset the selection by calling `clearSelection()` on the data table. -E.g. ```javascript -actions: - myAction(selection, datatable) { - console.log("Hi, you reached my action for selection: " + JSON.stringify(selection)); - datatable.clearSelection(); - } -``` - -## Helper components -The following components may be helpful when customizing the data table: -* [Sortable header](https://github.com/mu-semtech/ember-data-table#sortable-header) +import DataTableController from 'ember-data-table/countroller'; -### Sortable header -The `th-sortable` component makes a column in the data table sortable. It displays a table header `` element including an ascending/descending sorting icon in case the table is currently sorted by the given column. +export default class ProductsIndexController extends DataTableController {} +``` -```htmlbars -{{th-sortable field='firstName' currentSorting=sort label='First name'}} +These steps would be the same for any Ember Data Table flavour, the following visulizes `RawDataTable`: + +```hbs + ``` -The following parameters are passed to the `th-sortable` component: +Visiting `http://localhost:4200/products` will now show the Raw Data Table. -| Parameter | Required | Description | -|-----------|----------|-------------| -| field | x | name of the model field in the column | -| label | x | label to be shown in the column's table header | -| currentSorting | x | current sorting (field and order) of the data according to [the JSONAPI specification](http://jsonapi.org/format/#fetching-sorting) | +## How-to guides -Note: the data table will update the `currentSorting` variable, but the user needs to handle the reloading of the data. The [Ember Data Table Route mixin](https://github.com/mu-semtech/ember-data-table#route) may be of use. +### Implementing a new style -## Mixins -The following mixins may be helpful to use with the data table: -* [Serializer mixin](https://github.com/mu-semtech/ember-data-table#serializer) -* [Route mixin](https://github.com/mu-semtech/ember-data-table#route) -* [Default Query Params mixin](https://github.com/mu-semtech/ember-data-table#default-query-params) +It is advised to adapt Ember Data Table to your application or design framework. Multiple examples exist. The best approach to build a new style is to copy the file from `ember-data-table/addon/components/raw-data-table.hbs` and adapt it to your needs from top to bottom. -### Serializer -Upon installation, the `DataTableSerializerMixin` is automatically included in your application serializer to add parsing of the filter, sortig and pagination meta data from the links in the [JSONAPI](http://jsonapi.org) responses. The data is stored in [Ember's model metadata](https://guides.emberjs.com/v2.9.0/models/handling-metadata/). +The file is quite daunting, yet much can be left as is. Only the HTML parts of the file need to be overwritten to suit your needs. Liberally add wrapping tags and classes and use custom input components for your design framework (eg: a custom input component for searching). Feel free to move things around within the same nesting (eg: moving pagination to the top). -To include the `DataTableSerializerMixin` in your application, add the mixin to your application serializer: -```javascript -import DS from 'ember-data'; -import DataTableSerializerMixin from 'ember-data-table/mixins/serializer'; +### Overwriting the rendering of fields -export default DS.JSONAPISerializer.extend(DataTableSerializerMixin, { +Columns of Ember Data Table can receive custom rendering. Say we are rendering products and we want to render the Unit Price through a custom component and whether the product is available too. -}); -``` +Assume the initial Ember Data Table looks like: -E.g. -```javascript -meta: { - count: 42 -}, -links: { -  previous: '/posts?page[number]=1&page[size]=10' -  next: '/posts?page[number]=3&page[size]=10' -} +```hbs + + ``` -will be parsed to -```javascript -meta: { - count: 42, - pagination: { - previous: { number: 1, size: 10 }, - next: { number: 3, size: 10 } - } -} + +Add the `@customFields` property to indicate which fields should receive custom rendering, and use the `:data-cell` slot to implement the rendering aspect: + +```hbs + + <:data-cell as |cell|> + {{#if (eq cell.attribute "price")}} + + + + {{else if (eq cell.attribute "available")}} + + {{#if cell.value}}Available{{else}}Out of stock{{/if}} + + {{/if}} + + ``` -### Route -The route providing data for the `data-table` component often looks similar. The model hook needs to query a list of resources of a specific model from the server. This list needs to be reloaded when the sorting, page or page size changes. The `DataTableRouteMixin` provides a default implementation of this behaviour. Just include the mixin in your route and specify the model to be queried as `modelName`. +In this case `label` is rendered as usual, but the `price` and `available` are rendered through a custom form. Note that the order of the columns is still the order of `@fields`. -```javascript -import Ember from 'ember'; -import DataTableRouteMixin from 'ember-data-table/mixins/route'; +### Overwrite the header labels -export default Ember.Route.extend(DataTableRouteMixin, { - modelName: 'post' -}); -``` - -The `DataTableRouteMixin` specifies the `filter`, `page`, `sort` and `size` variables as `queryParams` of the route with the `refreshModel` flag set to `true`. As such the data is reloaded when one of the variables changes. A user can add custom options to be passed in the query to the server by defining a `mergeQueryOptions(parms)` function in the route. The function must return an object with the options to be merged. +Column headers can be supplied by adding extra properties to the fields attribute split by a colon. A single `_` will be replaced by a space and two underscores get replaced by a single underscore -```javascript -import Ember from 'ember'; -import DataTableRouteMixin from 'ember-data-table/mixins/route'; - -export default Ember.Route.extend(DataTableRouteMixin, { - modelName: 'post', - mergeQueryOptions(params) { - return { included: 'author' }; - } -}); +``` + ``` -Note: if the `mergeQueryOptions` returns a filter option on a specific field (e.g. `title`), the nested key needs to be provided as a string. Otherwise the `filter` param across all fields will be overwritten breaking the general search. +## Discussions -E.g. -```javascript -mergeQueryOptions(params) { - return { - included: 'author', - 'filter[title]': params.title - }; -} -``` +### Why one big template file -The `DataTableRouteMixin` also sets the `isLoadingModel` flag on the controller while the route's model is being loaded. Passing this flag to the data table's `isLoading` property will show a spinner while data is loaded. +With the advent of named slots users can overwrite things deeply nested inside Ember Data Table when using a single template. -### Default Query Params -The `DefaultQueryParams` mixin provides sensible defaults for the `page` (default: 0), `size` (default: 25) and `filter` (default: '') query parameters. The mixin can be mixed in a controller that uses the `page` and `filter` query params. +Multiple components are used to offer certain logical processing relevant in intermediate steps (eg: `DataTable::Row`) to split up logic. This keeps some of the logic contained. -```javascript -import Ember from 'ember'; -import DefaultQueryParamsMixin from 'ember-data-table/mixins/default-query-params'; +The template file itself contains a repeating pattern to check if a block was given and use that, or render the default implementation for your design framework. Eg. the `:menu` named slot is defined as follows in raw-data-table.hbs: -export default Ember.Controller.extend(DefaultQueryParamsMixin, { - ... -}); +```hbs + + {{#if (has-block "menu")}} + {{yield (hash General Selected) to="menu"}} + {{else}} + ... + {{/if}} + ``` -Note: if you want the search text field to be enabled on a data table, the filter parameter may not be `undefined`. Therefore you must initialize it on an empty query string (as done by the `DefaultQueryParams` mixin). +The logic is contained in the `dt.Menu` component offered from above. We first check if the `:menu` named block is given and dispatch processing to that block if it is. Otherwise we provide our implementation. + +The downside of this approach is that we have a large handlebars file in our codebase, but with good reason. We create a dirty dustbin here so the applications using the component can stay clean. We hope Ember Data Table design implementations get used in many applications so the heavy template outweigs the clean usage. + +The default implementation will almost always be the rendered item, but we provide the end-user with escape hatches on every level to overwrite exactly the piece they need. This makes it much more obvious what diverges from the default where we use Ember Data Table. It makes maintenance and upgrades easier and allows apps to best express the intended diversion. + +## Reference + +### Arguments to Ember Data Table + +These arguments are expected to be supported by specific design implementations too. + +#### Common information from route and controller + +The passing of data from route and controller, and moving data back up. + +- `@content` :: Data to be rendered. In case this has a `meta` + property, this is used as default to derive amount of results and + backend pagination offset. +- `@page` and `@updatePage` :: Indicates the current page number and the + function called to update the current page. +- `@size` and `@updatePageSize` :: Indicates 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. +- `@total` :: The total amount of results across all pages. If not set, + `@meta.count` or `@content.meta.count` is tried. +- `@isLoading` :: Truethy if the Data Table is currently loading data. + +- `@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 backend pagination offset from + `@meta.links.first.number` (generally `0` but sometimes `1`) and + amount of results as alternative to `@total` from `@meta.count`. + + +#### Ember Data Table visualization configuration + +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. Eg: `@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. +- `@noDataMessage` :: Custom message to show when no data is available. + The `:no-data-message` block can be used as an elternative to provide + styling. +- `@enableLineNumbers` :: Set to truethy 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 + 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 + if supported (screen readers should see the label still). Eg: + `@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` + attribute (see below). +- `@customHeaders` :: List of attributes for which a custom header will + be rendered through the `:data-header` named block. Each of the + attributes mentioned here will not 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. + + ```hbs + + <:data-header as |header|> + {{#if (eq header.attribute "label")}} + Here is my label + {{else if (eq header.attribute "priceInEuros")}} + Here is my price! + {{/if}} + + + ``` + +- `@customFields` :: List 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 + for. + + ```hbs + + <:data-cell as |cell|> + {{#if (eq cell.attribute "label")}} + {{cell.value}} + {{else if (eq cell.attribute "priceInEuros")}} + €{{cell.value}},- + {{/if}} + + + ``` + +#### 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 before sending the request. If no number is + supplied a default is used. +- `@hasMenu` :: If not truthy, the component will show the supplide + menu. This allows controlling whether the menu should be shown + dynamically. The menu may contain actions which act on the current + selection. +- `@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. +- `@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 + supplied if desired (such as `uuid` when using mu-search). An empty + string will provide the full object. +- `@attributeToSortParams` :: Which attributes may be sorted on. This + defaults to all attributes. You may choose to provide only some + attributes. Setting this to the empty string disables sorting on all + fields. +- `@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 + call a function instead but this is less accessible. +- `@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 overriden by this property. Set to `uuid` when using + mu-search for instance, or set to empty string to supply the full + model. + +#### 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. + diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index 3b5f963..eed1c36 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -27,7 +27,7 @@ itemsOnCurrentPage=@content.length sizeOptions=this.sizeOptions total=@total - meta=@content.meta + meta=@content.meta ;; TODO: supply both meta and @content.meta or supply @content.meta only when @meta is not supplied to be in line with readme updatePage=this.updatePage updateSize=this.updatePageSize backendPageOffset=@backendPageOffset) diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 6ac1d72..192e258 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -10,7 +10,7 @@ @filter={{@filter}} @meta={{@meta}} @hasMenu={{@hasMenu}} - @selection={{@selection}} + @enableSelection={{@enableSelection}} @noDataMessage={{@noDataMessage}} @isLoading={{@isLoading}} @enableLineNumbers={{@enableLineNumbers}} From 35b89ec05ba00ab8d327717b62e88cc7d3fef7f8 Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 23 Jul 2024 17:51:29 +0200 Subject: [PATCH 45/56] More work towards README of new Ember Data Table This documents the various parts which can be overridden. It's probably the easiest to check the specific design implementation but this might help determine the intent. --- README.md | 294 +++++++++++++++++- addon/components/data-table/data-cell.hbs | 2 +- .../data-table/data-table-content-body.hbs | 2 +- addon/components/data-table/row.hbs | 5 +- addon/components/raw-data-table.hbs | 5 +- 5 files changed, 298 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ee4b61a..0b1dbc9 100644 --- a/README.md +++ b/README.md @@ -266,10 +266,13 @@ How to show different things in Ember Data Table default the `id` property is used but another attribute may be supplied if desired (such as `uuid` when using mu-search). An empty string will provide the full object. -- `@attributeToSortParams` :: Which attributes may be sorted on. This - defaults to all attributes. You may choose to provide only some - attributes. Setting this to the empty string disables sorting on all - fields. +- `@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 backend). More + options than `'asc'` and `'desc'` can be provided if the backend + 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. @@ -287,3 +290,286 @@ How to show different things in Ember Data Table 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: + - `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`. + - `Selected` :: Component with information on handling selected items. + The block given to Selected receives `selected` which should be + passed to `:selection-menu`. + +- `general-menu` :: Implements the menu with actions which is shown when + no items are selected. Receives a hash with two items: + - `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 + selected. It is 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: + - `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. + - `selection` :: Copy of the selected items which can be passed to other functions. + - `dataTable` :: The DataTable object. + +- `selection-menu-actions` :: Contains the actions which can be applied + to a selection. This is likely custom for each use of the Ember Data + 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. + - `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. + - `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. + - `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. + - `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. + - `fields` :: A complex fields object containing the information about + each 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 + `@attributeToSortParams`. + - `hasCustomHeader` :: whether this column has a custom header or + not (meaning we should render it 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`). + - `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 + 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 + `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 + 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). + - `customHeaders` :: Headers which should be rendered in a custom way + as an array or 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: + - `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 + (eg: from `'asc'` to `'desc'` or from `'desc'` to nothing). + - `nextSort` :: Next way of sorting. This is obvious for + `["asc","desc",""]` but users may have provided multiple sorting + methods through `@attributeToSortParams`. + - `isAscending` :: Are we sorting ascending now? + - `isDescending` :: Are we sorting descending now? + - `sortDirection` :: What is the key on which we are sorting now (eg: `"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 `` + 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. + - `wrappedItems` :: Rows of the data table in a way through which they + can be selected. + - `enableLineNumbers` :: Whether line numbers are enabled or not. + - `hasClickRowAction` :: Wheter 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. + - `toggleSelected` :: Action which allows to toggle the selection + state of the current row. Should receive the an element from + `wrappedItems` as first element and the event that caused it (will + check `event.target.fetched`) 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. + - `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 (eg: `id` for the id or `""` if the whole + object should be supplied). + - `rowLink` :: The route which should be used when users click on the + row itself. + - `rowLinkModelProperty` :: The property of the model which sholud be + supplied to the `rowLink` route (eg: `id` for the id or `""` if + the whole object should be supplied). + - `noDataMessage` :: String message which the user asked to render + when no data was supplied. + - `fields` :: Array of objects describing each of the fields to be + rendered. See `fields` higher up. + - `Row` :: Contextual component handling the logic of an individual + row. This has te be called for each row in the visible table and it + should receive `@wrapper` for the element of `wrappedItems` we are + rendering here, as well as the `@index` for the index we are looping + over here. The `@index` is a local index for this rendering + 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: + - `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). + - `toggleSelected` :: See above. + - `hasClickRowAction` :: See above. + - `onClickRow` :: See above. + - `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 (eg: 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 forrendering 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 + for designs where the first column should receive different styling. + - `otherColumns` :: 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. + - `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. + - `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 + 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 + 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 + 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. + - `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). + - `pageNumber` :: The page number as seen by a human (first page is 1 + regardless of the backend using 0 for the first page or not). + - `numberOfPages` :: Total number of pages avaialeble. + - `pageOptions` :: Array containing a number for each page available + in the data table in human form (can be used for rendering buttons). + - `summarizedPageOptions` :: A smart way of showing pages. Yields a list of page numbers with: + - the leftmost being the first page number, + - followed by the string 'more' if empty spots follow, + - followed by up to three pages less than the current page, + - followed by the current page number, + - 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. + - `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 + is the last page. + - `previousPage` :: The previous page number in this view, `undefined` + if this is the first page. + - `updatePage` :: Function which takes a backend 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 + `updatePage` after mapping the human page number through the backend + page number offset). + - `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 + string or as number and calls the `@updateSize` function supplied to + Data Table. + - `hasMultiplePages` :: Whether this Data Table has multiple pages or + not. + - `isFirstPage` :: Whether we are now rendering the first page or + not. + - `isLastPage` :: Whether we are rendering the last page or not. + - `hasPreviousPage` :: Whether there is a previous page or not. + - `hasNextPage` :: Whether there is a next page or not. + - `meta` :: If meta is available, it will be stored here. This may + contain page links. + - `backendPageOffset` :: The current backend page offset (either + calculated or guessed). diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index c4fab44..877b297 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -8,7 +8,7 @@ label=@column.label fields=@fields isCustom=this.isCustom - hasCustom=this.hasCustom + hasCustomFields=this.hasCustomFields attribute=@column.attribute renderCustomBlock=this.renderCustomBlock value=(get @wrapper.item @column.attribute))}} \ No newline at end of file diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 8dffc76..5891161 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -5,7 +5,7 @@ offset=this.offset wrappedItems=this.wrappedItems enableLineNumbers=@enableLineNumbers - hasClickRowAction=(and (@onClickRow @rowLink) true) + hasClickRowAction=(and (or @onClickRow @rowLink) true) onClickRow=@onClickRow toggleSelected=this.updateSelection selection=@dataTable.selection diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index a82a499..fb1da17 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -5,15 +5,16 @@ enableLineNumbers=@enableLineNumbers lineNumber=(add @index @offset) enableSelection=@enableSelection - selected=(includes @wrapper.item @selection) hasClickRowAction=@hasClickRowAction onClickRow=(fn @onClickRow @wrapper.item) - toggleSelected=(fn @toggleSelected @wrapper) isSelected=(includes @wrapper.item @selection) + selected=(includes @wrapper.item @selection) {{!-- TODO: do we want both selected and isSelected? --}} + toggleSelected=(fn @toggleSelected @wrapper) linkedRoutes=this.linkedRoutes fields=@fields DataCells=(component "data-table/data-cells" fields=@fields + wrapper=@wrapper linkedRoutes=this.linkedRoutes dataTable=@dataTable))}} \ No newline at end of file diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 192e258..6d8795a 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -67,11 +67,12 @@ {{!-- START: menu --}} {{#if (has-block "menu")}} - {{yield (hash General Selected) to="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}} @@ -181,7 +182,7 @@ {{#if row.enableLineNumbers}} {{row.lineNumber}} {{/if}} - + {{#if (has-block "data-cells")}} {{yield dataCells to="data-cells"}} {{else}} From f789812dcb1df3c26ea132f14d1e2c997a28bdaf Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 23 Jul 2024 21:30:38 +0200 Subject: [PATCH 46/56] Nicer wording in README --- README.md | 106 +++++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 0b1dbc9..a89e7f2 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,21 @@ Data table for EmberJS ### Add basic Ember Data Table -It is advised to adapt Ember Data Table to your specific design setup or use a variant implemented for your setup. To check if things are working, you can add the `RawDataTable` to your application. +Find an adaptation of Ember Data Table for the design framework of your choice or implement a custom variant for your application. This tutorial uses `RawDataTable`. -Let's generate a route for products first: +Generate a route for products first: ```bash ember g route products/index ``` -We will assume a model exists with `label` and `price` which we could generate using: +The tutorial assumes a model exists with `label` and `price` which you can generate using: ```bash ember g model product label:string price:number ``` -Next we ensure content is fetched from the backend using standard model hooks and query parameters are set up. Extending from the provided Route and Controller is the shortest form. +Next you'll fetch content from the back-end using standard model hooks and query parameters. Extending from the provided Route and Controller is the shortest form. For the route stored in `/app/routes/products/index.js` write: @@ -37,12 +37,12 @@ export default class ProductsIndexRoute extends DataTableRoute { For the controller stored in `/app/controllers/product/index.js` write: ```javascript -import DataTableController from 'ember-data-table/countroller'; +import DataTableController from 'ember-data-table/controller'; export default class ProductsIndexController extends DataTableController {} ``` -These steps would be the same for any Ember Data Table flavour, the following visulizes `RawDataTable`: +These steps are the same for any Ember Data Table flavour, the following visualizes `RawDataTable`: ```hbs ``` -Add the `@customFields` property to indicate which fields should receive custom rendering, and use the `:data-cell` slot to implement the rendering aspect: +The `@customFields` property lists which fields which receive custom rendering. Use the `:data-cell` slot to implement the rendering aspect: ```hbs ``` -In this case `label` is rendered as usual, but the `price` and `available` are rendered through a custom form. Note that the order of the columns is still the order of `@fields`. +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`. ### Overwrite the header labels -Column headers can be supplied by adding extra properties to the fields attribute split by a colon. A single `_` will be replaced by a space and two underscores get replaced by a single underscore +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 ``` @@ -142,17 +142,17 @@ The template file itself contains a repeating pattern to check if a block was gi ``` -The logic is contained in the `dt.Menu` component offered from above. We first check if the `:menu` named block is given and dispatch processing to that block if it is. Otherwise we provide our implementation. +The `dt.Menu` component contains the logic and is supplied by the enclosing scope. First check if the `:menu` named block is given and dispatch processing to that block if it's available. Otherwise use an implementation suiting for your design framework in '...'. -The downside of this approach is that we have a large handlebars file in our codebase, but with good reason. We create a dirty dustbin here so the applications using the component can stay clean. We hope Ember Data Table design implementations get used in many applications so the heavy template outweigs the clean usage. +The downside of this approach is a large handlebars file, but with good reason. The dustbin here lets consuming applications of stay clean. We hope Ember Data Table design implementations get used in many applications so the heavy template outweighs the clean usage. -The default implementation will almost always be the rendered item, but we provide the end-user with escape hatches on every level to overwrite exactly the piece they need. This makes it much more obvious what diverges from the default where we use Ember Data Table. It makes maintenance and upgrades easier and allows apps to best express the intended diversion. +The default implementation will be used most often, but the end-user receives an escape hatch on every level to overwrite exactly the piece they need. The focus is placed on what diverges from the default where we use Ember Data Table. This makes maintenance and upgrades easier and lets apps better express the intended diversion. ## Reference ### Arguments to Ember Data Table -These arguments are expected to be supported by specific design implementations too. +These arguments should be supported by specific design implementations too. #### Common information from route and controller @@ -160,7 +160,7 @@ The passing of data from route and controller, and moving data back up. - `@content` :: Data to be rendered. In case this has a `meta` property, this is used as default to derive amount of results and - backend pagination offset. + back-end pagination offset. - `@page` and `@updatePage` :: Indicates the current page number and the function called to update the current page. - `@size` and `@updatePageSize` :: Indicates the current page size and @@ -171,12 +171,12 @@ The passing of data from route and controller, and moving data back up. the function to call for updating that string. - `@total` :: The total amount of results across all pages. If not set, `@meta.count` or `@content.meta.count` is tried. -- `@isLoading` :: Truethy if the Data Table is currently loading data. +- `@isLoading` :: Truthy if the Data Table is currently loading data. - `@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 backend pagination offset from - `@meta.links.first.number` (generally `0` but sometimes `1`) and + determine the back-end pagination offset from + `@meta.links.first.number` (often `0` but sometimes `1`) and amount of results as alternative to `@total` from `@meta.count`. @@ -184,21 +184,21 @@ 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. Eg: `@fields="label:Name priceInEuros:Euro_price"`. +- `@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. - `@noDataMessage` :: Custom message to show when no data is available. - The `:no-data-message` block can be used as an elternative to provide + The `:no-data-message` block can be used as an alternative to provide styling. -- `@enableLineNumbers` :: Set to truethy to show line numbers in the +- `@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 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 - if supported (screen readers should see the label still). Eg: + 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` @@ -206,7 +206,7 @@ How to show different things in Ember Data Table attribute (see below). - `@customHeaders` :: List of attributes for which a custom header will be rendered through the `:data-header` named block. Each of the - attributes mentioned here will not render the default header but will + 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 @@ -254,7 +254,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 supplide +- `@hasMenu` :: If not truthy, the component will show the supplied menu. This allows controlling whether the menu should be shown dynamically. The menu may contain actions which act on the current selection. @@ -270,8 +270,8 @@ How to show different things in Ember Data Table 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 backend). More - options than `'asc'` and `'desc'` can be provided if the backend + 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 @@ -282,7 +282,7 @@ How to show different things in Ember Data Table call a function instead but this is less accessible. - `@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 overriden by this property. Set to `uuid` when using + property may be overridden by this property. Set to `uuid` when using mu-search for instance, or set to empty string to supply the full model. @@ -317,7 +317,7 @@ Various named blocks are offered, check your Ember Data Table design implementat - `selectionIsEmpty` :: Whether items are currently selected or not. - `selection-menu` :: This menu is rendered only when items have been - selected. It is the main wrapper which contains + 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: @@ -393,13 +393,13 @@ Various named blocks are offered, check your Ember Data Table design implementat - `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 - (eg: from `'asc'` to `'desc'` or from `'desc'` to nothing). - - `nextSort` :: Next way of sorting. This is obvious for - `["asc","desc",""]` but users may have provided multiple sorting + (e.g.: from `'asc'` to `'desc'` or from `'desc'` to nothing). + - `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? - - `sortDirection` :: What is the key on which we are sorting now (eg: `"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? @@ -415,7 +415,7 @@ Various named blocks are offered, check your Ember Data Table design implementat - `wrappedItems` :: Rows of the data table in a way through which they can be selected. - `enableLineNumbers` :: Whether line numbers are enabled or not. - - `hasClickRowAction` :: Wheter something needs to happen when the row + - `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 @@ -433,21 +433,21 @@ Various named blocks are offered, check your Ember Data Table design implementat - `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 (eg: `id` for the id or `""` if the whole + supplied to the route (e.g.: `id` for the id or `""` if the whole object should be supplied). - `rowLink` :: The route which should be used when users click on the row itself. - - `rowLinkModelProperty` :: The property of the model which sholud be - supplied to the `rowLink` route (eg: `id` for the id or `""` if + - `rowLinkModelProperty` :: The property of the model which should be + supplied to the `rowLink` route (e.g.: `id` for the id or `""` if the whole object should be supplied). - `noDataMessage` :: String message which the user asked to render when no data was supplied. - `fields` :: Array of objects describing each of the fields to be rendered. See `fields` higher up. - `Row` :: Contextual component handling the logic of an individual - row. This has te be called for each row in the visible table and it - should receive `@wrapper` for the element of `wrappedItems` we are - rendering here, as well as the `@index` for the index we are looping + row. This has to be called for each row in the visible table and it + should receive `@wrapper` for the element of `wrappedItems` we're + rendering here, as well as the `@index` for the index we're looping over here. The `@index` is a local index for this rendering regardless of the page, so you can use `{{#each body.wrappedItems as |wrapper index|}} Date: Tue, 23 Jul 2024 22:43:17 +0200 Subject: [PATCH 47/56] Fix location of some handlebars TODO comments These were within a component and they're not allowed there. --- addon/components/data-table.hbs | 4 +++- addon/components/data-table/row.hbs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index eed1c36..be1e53f 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -1,3 +1,5 @@ +{{!-- TODO: supply both meta and @content.meta or supply @content.meta only when @meta is not supplied to be in line with readme --}} + {{yield (hash Search=(component "data-table/text-search" @@ -27,7 +29,7 @@ itemsOnCurrentPage=@content.length sizeOptions=this.sizeOptions total=@total - meta=@content.meta ;; TODO: supply both meta and @content.meta or supply @content.meta only when @meta is not supplied to be in line with readme + meta=@content.meta updatePage=this.updatePage updateSize=this.updatePageSize backendPageOffset=@backendPageOffset) diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index fb1da17..60c2bc8 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -1,4 +1,5 @@ {{!-- Used in: data-table/data-table-content-body --}} +{{!-- TODO: do we want both selected and isSelected? --}} {{yield (hash wrapper=@wrapper item=@wrapper.item @@ -8,7 +9,7 @@ hasClickRowAction=@hasClickRowAction onClickRow=(fn @onClickRow @wrapper.item) isSelected=(includes @wrapper.item @selection) - selected=(includes @wrapper.item @selection) {{!-- TODO: do we want both selected and isSelected? --}} + selected=(includes @wrapper.item @selection) toggleSelected=(fn @toggleSelected @wrapper) linkedRoutes=this.linkedRoutes fields=@fields From 623311b21014e84a3f2121c104a8aba70059f04f Mon Sep 17 00:00:00 2001 From: Aad Versteden Date: Tue, 23 Jul 2024 22:43:43 +0200 Subject: [PATCH 48/56] Add support for view argument to limit arguments to pass This is a wrapper object containing: - size - page - filter - sort - isLoading - updatePage - updatePageSize - updateFilter - updateSort It is an opaque object from the user's point of view. All current arguments take precedence making it easy to override. The default case becomes shorter which is great to show what's special about the specific Data Table at hand and thus lowers mental overhead for the default case. --- addon/components/data-table.hbs | 2 +- addon/components/data-table.js | 66 +++++++++++++++++++++++++-------- addon/controller.js | 14 +++++++ 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index be1e53f..c3a5001 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -3,7 +3,7 @@ {{yield (hash Search=(component "data-table/text-search" - filter=@filter + filter=this.filter placeholder=this.searchPlaceholder autoSearch=this.autoSearch updateFilter=this.updateFilter diff --git a/addon/components/data-table.js b/addon/components/data-table.js index fdb8561..5a7a524 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -7,11 +7,25 @@ import { toComponentSpecifications, splitDefinitions } from "../utils/string-spe export default class DataTable extends Component { @tracked _selection = undefined; + get filter() { + return this.args.filter !== undefined + ? this.args.filter + : this.args.view?.filter; + } + + get sort() { + return this.args.sort !== undefined + ? this.args.sort + : this.args.view?.sort; + } + get selection() { if (this._selection === undefined && this.args.selection === undefined) return []; - else if (this._selection !== undefined) return this._selection; - else return this.args.selection; + else if (this._selection !== undefined) + return this._selection; + else + return this.args.selection; } set selection(newSelection) { @@ -25,7 +39,9 @@ export default class DataTable extends Component { } get isLoading() { - return this.args.isLoading; + return this.args.isLoading !== undefined + ? this.args.isLoading + : this.args.view?.isLoading; } /** @@ -56,15 +72,19 @@ export default class DataTable extends Component { } get page() { - return this.args.page || 0; + const page = this.args.page !== undefined + ? this.args.page + : this.args.view?.page; + return page || 0; } get size() { - if ( this.args.size ) { + if ( this.args.size ) return this.args.size; - } else { + else if ( this.args.view?.size ) + return this.args.view.size; + else return 5; - } } get sizeOptions() { @@ -81,7 +101,7 @@ export default class DataTable extends Component { } get enableSearch() { - return this.args.filter !== undefined; + return this.filter !== undefined; } get autoSearch() { @@ -164,40 +184,54 @@ export default class DataTable extends Component { @action updatePageSize(size) { - if( !this.args.updatePageSize ) { + const updater = this.args.updatePageSize !== undefined + ? this.args.updatePageSize + : this.args.view?.updatePageSize; + + if( !updater ) { console.error(`Could not update page size to ${size} because @updatePageSize was not supplied to data table`); } else { this.updatePage(0); - this.args.updatePageSize(size); + updater(size); } } @action updateFilter(filter) { - if( !this.args.updateFilter ) { + const updater = this.args.updateFilter || this.args.view?.updateFilter; + + if( !updater ) { console.error(`Could not update filter to '${filter}' because @updateFilter was not supplied to data table`); } else { this.updatePage(0); - this.args.updateFilter(filter); + updater(filter); } } @action updateSort(sort) { - if( !this.args.updateSort ) { + const updater = this.args.updateSort !== undefined + ? this.args.updateSort + : this.args.view?.updateSort; + + if( !updater ) { console.error(`Could not update sorting to '${sort}' because @updateSort was not supplied to data table`); } else { this.updatePage(0); - this.args.updateSort(sort); + updater(sort); } } @action updatePage(page) { - if( !this.args.updatePage ) { + const updater = this.args.updatePage !== undefined + ? this.args.updatePage + : this.args.view?.updatePage; + + if( !updater ) { console.error(`Could not update page to ${page} because @updatePage was not supplied to data table`); } else { - this.args.updatePage(page); + updater(page); } } diff --git a/addon/controller.js b/addon/controller.js index 9e43b02..62c9b22 100644 --- a/addon/controller.js +++ b/addon/controller.js @@ -9,4 +9,18 @@ export default class ProductsController extends Controller { @tracked filter = ''; @tracked sort = ''; // TODO: perhaps undefined would be a nicer default for consumers @tracked isLoadingModel = false; + + get view() { + return { + size: this.size, + page: this.page, + 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 505ed2c5a1f1d4ca391a2e6f7b17f90563341efb Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Wed, 24 Jul 2024 08:16:16 +0200 Subject: [PATCH 49/56] Fix class name of the data-table controller --- addon/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/controller.js b/addon/controller.js index 62c9b22..a8063f4 100644 --- a/addon/controller.js +++ b/addon/controller.js @@ -1,7 +1,7 @@ import { tracked } from '@glimmer/tracking'; import Controller from '@ember/controller'; -export default class ProductsController extends Controller { +export default class DataTableController extends Controller { queryParams = ['size', 'page', 'filter', 'sort']; @tracked size = 10; From 527556e2151abfd81ce78311e846d4677cb8d018 Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Thu, 25 Jul 2024 11:09:27 +0200 Subject: [PATCH 50/56] Fix passing of sort argument --- addon/components/data-table.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index c3a5001..c401c1e 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -14,7 +14,7 @@ enableSelection=@enableSelection enableLineNumbers=@enableLineNumbers onClickRow=@onClickRow - sort=@sort + sort=this.sort updateSort=this.updateSort customHeaders=this.customHeaders fields=this.fields From 979a6b42ed5c3ea30cc65a337d540641be0a015c Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Thu, 25 Jul 2024 11:09:39 +0200 Subject: [PATCH 51/56] Pass view argument in raw-data-table template --- addon/components/raw-data-table.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 6d8795a..e4c1249 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -3,6 +3,7 @@ @content={{@content}} @fields={{@fields}} @autoSearch={{@autoSearch}} + @view={{@view}} @page={{@page}} @size={{@size}} @total={{@total}} From 547fbdf47f26dba055978f5a928085fb2e582df5 Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Thu, 25 Jul 2024 13:27:01 +0200 Subject: [PATCH 52/56] Export attribute-to-sort-params as util function Makes it easier for users to fallback to the default behaviour when implementing custom sort parameters for an attribute. --- addon/components/data-table.js | 4 ++-- addon/utils/attribute-to-sort-params.js | 4 ++++ app/utils/attribute-to-sort-params.js | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 addon/utils/attribute-to-sort-params.js create mode 100644 app/utils/attribute-to-sort-params.js diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 5a7a524..6f4fb1e 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -3,6 +3,7 @@ import { tracked } from '@glimmer/tracking'; import Component from '@glimmer/component'; import { typeOf } from '@ember/utils'; import { toComponentSpecifications, splitDefinitions } from "../utils/string-specification-helpers"; +import attributeToSortParams from "../utils/attribute-to-sort-params"; export default class DataTable extends Component { @tracked _selection = undefined; @@ -134,8 +135,7 @@ export default class DataTable extends Component { if( this.args.attributeToSortParams ) { return this.args.attributeToSortParams(attribute); } else { - const attr = attribute.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); - return { asc: attr, desc: `-${attr}` }; + return attributeToSortParams(attribute); } } diff --git a/addon/utils/attribute-to-sort-params.js b/addon/utils/attribute-to-sort-params.js new file mode 100644 index 0000000..a77df92 --- /dev/null +++ b/addon/utils/attribute-to-sort-params.js @@ -0,0 +1,4 @@ +export default function attributeToSortParams(attribute) { + const attr = attribute.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + return { asc: attr, desc: `-${attr}` }; +} diff --git a/app/utils/attribute-to-sort-params.js b/app/utils/attribute-to-sort-params.js new file mode 100644 index 0000000..d9701d0 --- /dev/null +++ b/app/utils/attribute-to-sort-params.js @@ -0,0 +1 @@ +export { default } from 'ember-data-table/utils/attribute-to-sort-params'; From d2b0bd5dc1cbcc46c807bc14862b9c0602e7dae0 Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Wed, 16 Oct 2024 14:40:50 +0200 Subject: [PATCH 53/56] Add peer dependency on ember-data The addon imports the JSONAPI serializer from ember-data --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index e4c7514..903be48 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "test:ember": "ember test", "test:ember-compatibility": "ember try:each" }, + "peerDependencies": { + "ember-data": ">=3.28.0" + }, "dependencies": { "ember-auto-import": "^1.12.0", "ember-cli-babel": "^7.26.10", From c56e56223a9bdbea65918f55170bc362d6d5dd6a Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Wed, 16 Oct 2024 14:52:18 +0200 Subject: [PATCH 54/56] Move ember-data from peerDependencies to dependencies --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 903be48..fe0a170 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,12 @@ "test:ember": "ember test", "test:ember-compatibility": "ember try:each" }, - "peerDependencies": { - "ember-data": ">=3.28.0" - }, "dependencies": { "ember-auto-import": "^1.12.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", "lodash": "^4.17.21" From 1f18789dd987c3bfdb96d37a8953d86d6d23c8b7 Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Wed, 16 Oct 2024 14:59:30 +0200 Subject: [PATCH 55/56] Bump ember-auto-import to v2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe0a170..e5f4664 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "test:ember-compatibility": "ember try:each" }, "dependencies": { - "ember-auto-import": "^1.12.0", + "ember-auto-import": "^2.0.0", "ember-cli-babel": "^7.26.10", "ember-cli-htmlbars": "^5.7.2", "ember-composable-helpers": "^5.0.0", From 703d827a22fdbc38b70c3df1a0bcff9526f07ed2 Mon Sep 17 00:00:00 2001 From: Erika Pauwels Date: Wed, 16 Oct 2024 15:58:30 +0200 Subject: [PATCH 56/56] Fix default sorting of all fields --- addon/components/data-table.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 6f4fb1e..056c7fa 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -145,13 +145,12 @@ export default class DataTable extends Component { .map( ({ attribute, label, isSortable, hasCustomHeader, isCustom, sortParameters }) => ({ attribute, label, - // custom format says it's sortable: sort params known - // say it should be sortable: use attribute to sort param - // field included in sortable fields: use attribute to sort param - sortParameters: sortParameters - || ( ( isSortable || this.sortableFields?.includes(attribute) ) - && this.attributeToSortParams(attribute) ), - get isSortable() { return Object.keys( this.sortParameters || {} ).length > 1; }, + sortParameters: sortParameters // custom format says it's sortable + || ( ( isSortable // custom format says it's sortable + || this.sortableFields === null // default: all fields are sortable + || this.sortableFields?.includes(attribute) ) // @sortableFields + && this.attributeToSortParams(attribute) ), + get isSortable() { return Object.keys( this.sortParameters || {} ).length >= 1; }, hasCustomHeader: hasCustomHeader || this.customHeaders.includes(attribute), isCustom: isCustom @@ -170,9 +169,9 @@ export default class DataTable extends Component { get sortableFields() { const sortableFields = this.args.sortableFields; if (sortableFields || sortableFields === "") - // default: all fields are sortable return splitDefinitions(sortableFields); else + // default: all fields are sortable return null; }