diff --git a/addon/-private/controllers/pagination.js b/addon/-private/controllers/pagination.js index 3b0dc89b..14b77138 100644 --- a/addon/-private/controllers/pagination.js +++ b/addon/-private/controllers/pagination.js @@ -4,7 +4,7 @@ import { inject as service } from "@ember/service"; import { tracked } from "@glimmer/tracking"; export default class PaginationController extends Controller { - queryParams = ["page", "search"]; + queryParams = ["page", "search", "sort", "include"]; @service router; @@ -13,7 +13,7 @@ export default class PaginationController extends Controller { @action updateQueryParam(field, value) { - // We dont want to set an empty stirng as this is still serialized + // We dont want to set an empty string as this is still serialized if (typeof value === "string" && !value.length) { value = null; } diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index c21c3a68..ceffebe2 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -50,7 +50,7 @@ {{yield (hash body=(component "data-table/body" models=this.data.value) - head=(component "data-table/head") + head=(component "data-table/head" sortedBy=this.sort update=this.updateSort) refresh=(perform this.fetchData) ) }} diff --git a/addon/components/data-table.js b/addon/components/data-table.js index 150c6829..761d68b3 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -14,15 +14,21 @@ export default class DataTableComponent extends Component { @tracked numPages; @tracked internalSearch; @tracked internalPage = 1; + @tracked internalSort; // While using 'useTask' we ended up in an infinite loop. // data = useTask(this, this.fetchData, () => [this.args.filter]); data = useFunction(this, this.fetchData.perform, () => [ this.args.filter, - this.page, this.search, + this.sort, + this.page, ]); + get sort() { + return this.internalSort || this.args.sort; + } + get page() { return this.args.page || this.internalPage; } @@ -31,6 +37,14 @@ export default class DataTableComponent extends Component { return this.args.search || this.internalSearch; } + set sort(sort) { + if (this.args.updateSort) { + this.args.updateSort(sort); + } else { + this.internalSort = sort; + } + } + set search(search) { if (this.args.updateSearch) { this.args.updateSearch(search); @@ -39,6 +53,14 @@ export default class DataTableComponent extends Component { } } + set page(page) { + if (this.args.updatePage) { + this.args.updatePage(page); + } else { + this.internalPage = page; + } + } + get isLoading() { return this.fetchData.isRunning; } @@ -66,7 +88,7 @@ export default class DataTableComponent extends Component { let options = { filter: { search: this.search, ...(this.args.filter || {}) }, - sort: this.args.sort, + sort: this.sort, include: this.args.include || "", }; @@ -87,28 +109,43 @@ export default class DataTableComponent extends Component { return data; } + @action + updateSort(sortLabel) { + if (this.args.updateSort) { + this.args.updateSort(sortLabel); + } + + const invers = this.sort[0] === "-"; + + if ( + this.sort === sortLabel || + (invers && this.sort.slice(1) === sortLabel) + ) { + if (invers) { + this.internalSort = undefined; + } else { + this.internalSort = `-${sortLabel}`; + } + } else { + this.internalSort = sortLabel; + } + } + @action updateSearch(submitEvent) { // Prevent reload because of form submit submitEvent.preventDefault(); this.search = submitEvent.target.elements.search.value; - this.fetchData.perform(); } @action resetSearch(event) { event.preventDefault(); this.search = ""; - this.fetchData.perform(); } @action updatePage(page) { - if (this.args.updatePage) { - this.args.updatePage(page); - } else { - this.internalPage = page; - } - this.fetchData.perform(); + this.page = page; } } diff --git a/addon/components/data-table/head.hbs b/addon/components/data-table/head.hbs index b91e4f23..bbcdbcab 100644 --- a/addon/components/data-table/head.hbs +++ b/addon/components/data-table/head.hbs @@ -1,5 +1,5 @@ - {{yield}} + {{yield (hash sorthead=(component "data-table/head/sortable-th" update=@update sortedBy=@sortedBy))}} - + \ No newline at end of file diff --git a/addon/components/data-table/head/sortable-th.hbs b/addon/components/data-table/head/sortable-th.hbs new file mode 100644 index 00000000..5d2bf5a1 --- /dev/null +++ b/addon/components/data-table/head/sortable-th.hbs @@ -0,0 +1,15 @@ + + + {{yield}} + {{#if (eq @sortedBy @sort)}} + + {{else if (eq @sortedBy (concat "-" @sort))}} + + {{/if}} + + \ No newline at end of file diff --git a/addon/templates/users/index.hbs b/addon/templates/users/index.hbs index 25cc3cf8..5f946693 100644 --- a/addon/templates/users/index.hbs +++ b/addon/templates/users/index.hbs @@ -4,23 +4,26 @@ @search={{this.search}} @sort="last_name" @updatePage={{fn this.updateQueryParam "page"}} - @updateSearch={{fn this.updateQueryParam "search"}} as |table| + @updateSearch={{fn this.updateQueryParam "search"}} + @updateSort={{fn this.updateQueryParam "sort"}} + @include={{"acls.role"}} + as |table| > - + {{#unless this.emailAsUsername}} - + {{t "emeis.users.headings.username"}} - - {{/unless}} - + + {{/unless}} + {{t "emeis.users.headings.lastName"}} - - + + {{t "emeis.users.headings.firstName"}} - - + + {{t "emeis.users.headings.email"}} - + diff --git a/app/components/data-table/head/sortable-th.js b/app/components/data-table/head/sortable-th.js new file mode 100644 index 00000000..624c6fc4 --- /dev/null +++ b/app/components/data-table/head/sortable-th.js @@ -0,0 +1 @@ +export { default } from "ember-emeis/components/data-table/head/sortable-th"; diff --git a/app/styles/ember-emeis.scss b/app/styles/ember-emeis.scss index 5f754f62..9fcc9fca 100644 --- a/app/styles/ember-emeis.scss +++ b/app/styles/ember-emeis.scss @@ -69,6 +69,14 @@ $ember-power-select-border-color: $global-border; border-left-color: transparent; } } + +.sortable-th { + cursor: pointer; + + :hover { + text-decoration: underline; + } +} /* END GENERAL INPUTS*/ /* TREE VIEW */ @@ -76,7 +84,6 @@ $ember-power-select-border-color: $global-border; .tree-border-right { border-right: solid 1px $global-border; } -} $tree_node_height: 1.9em; $tree_indicator_width: 0.6em; diff --git a/tests/integration/components/data-table/head/sortable-th-test.js b/tests/integration/components/data-table/head/sortable-th-test.js new file mode 100644 index 00000000..d9112ecb --- /dev/null +++ b/tests/integration/components/data-table/head/sortable-th-test.js @@ -0,0 +1,87 @@ +import { render, click } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import { setupRenderingTest } from "ember-qunit"; +import { module, test } from "qunit"; + +module( + "Integration | Component | data-table/head/sortable-th", + function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function (assert) { + this.set("sort", "last_name"); + this.set("update", (sort) => { + this.set("sort", sort); + assert.step("update"); + }); + }); + + test("it renders", async function (assert) { + await render(hbs` + + + test + + + `); + + assert.dom(this.element).hasText("test"); + assert.dom("span[icon=chevron-down]").exists(); + }); + + test("it renders as block", async function (assert) { + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText("template block text"); + }); + + test("it toggles sort state", async function (assert) { + await render(hbs` + + + one + + + two + + + `); + + assert.dom("[data-test-sortable-th=last_name]").hasText("one"); + assert.dom("[data-test-sortable-th=first_name]").hasText("two"); + assert + .dom("[data-test-sortable-th=last_name] span[icon=chevron-down]") + .exists(); + + this.set("sort", "first_name"); + + assert + .dom("[data-test-sortable-th=last_name] span[icon=chevron-down]") + .doesNotExist(); + assert + .dom("[data-test-sortable-th=first_name] span[icon=chevron-down]") + .exists(); + + await click("[data-test-sortable-th=last_name]"); + + assert.verifySteps(["update"]); + + assert + .dom("[data-test-sortable-th=first_name] span[icon=chevron-down]") + .doesNotExist(); + assert + .dom("[data-test-sortable-th=last_name] span[icon=chevron-down]") + .exists(); + + this.set("sort", "-last_name"); + + assert + .dom("[data-test-sortable-th=last_name] span[icon=chevron-up]") + .exists(); + }); + } +);