diff --git a/ts/WoltLabSuite/Core/Component/GridView.ts b/ts/WoltLabSuite/Core/Component/GridView.ts index 2a68881e1c..4cae3bbdd1 100644 --- a/ts/WoltLabSuite/Core/Component/GridView.ts +++ b/ts/WoltLabSuite/Core/Component/GridView.ts @@ -5,19 +5,18 @@ import DomUtil from "../Dom/Util"; import { wheneverFirstSeen } from "../Helper/Selector"; import UiDropdownSimple from "../Ui/Dropdown/Simple"; import Filter from "./GridView/Filter"; +import Sorting from "./GridView/Sorting"; export class GridView { readonly #filter: Filter; readonly #gridClassName: string; readonly #table: HTMLTableElement; readonly #pagination: WoltlabCorePaginationElement; + readonly #sorting: Sorting; readonly #baseUrl: string; readonly #noItemsNotice: HTMLElement; #pageNo: number; - #sortField: string; - #sortOrder: string; - #defaultSortField: string; - #defaultSortOrder: string; + #gridViewParameters?: Map; constructor( @@ -35,16 +34,14 @@ export class GridView { this.#noItemsNotice = document.getElementById(`${gridId}_noItemsNotice`) as HTMLElement; this.#pageNo = pageNo; this.#baseUrl = baseUrl; - this.#sortField = sortField; - this.#defaultSortField = sortField; - this.#sortOrder = sortOrder; - this.#defaultSortOrder = sortOrder; + this.#gridViewParameters = gridViewParameters; this.#initPagination(); this.#initSorting(); this.#initInteractions(); this.#filter = this.#setupFilter(gridId); + this.#sorting = this.#setupSorting(sortField, sortOrder); this.#initEventListeners(); window.addEventListener("popstate", () => { @@ -58,40 +55,7 @@ export class GridView { }); } - #initSorting(): void { - this.#table - .querySelectorAll('.gridView__headerColumn[data-sortable="1"]') - .forEach((element) => { - const button = element.querySelector(".gridView__headerColumn__button"); - button?.addEventListener("click", () => { - this.#sort(element.dataset.id!); - }); - }); - - this.#renderActiveSorting(); - } - - #sort(sortField: string): void { - if (this.#sortField == sortField && this.#sortOrder == "ASC") { - this.#sortOrder = "DESC"; - } else { - this.#sortField = sortField; - this.#sortOrder = "ASC"; - } - - this.#switchPage(1); - this.#renderActiveSorting(); - } - - #renderActiveSorting(): void { - this.#table.querySelectorAll('th[data-sortable="1"]').forEach((element) => { - element.classList.remove("active", "ASC", "DESC"); - - if (element.dataset.id == this.#sortField) { - element.classList.add("active", this.#sortOrder); - } - }); - } + #initSorting(): void {} #switchPage(pageNo: number, updateQueryString: boolean = true): void { this.#pagination.page = pageNo; @@ -105,8 +69,8 @@ export class GridView { await getRows( this.#gridClassName, this.#pageNo, - this.#sortField, - this.#sortOrder, + this.#sorting.getSortField(), + this.#sorting.getSortOrder(), this.#filter.getActiveFilters(), this.#gridViewParameters, ) @@ -143,9 +107,9 @@ export class GridView { if (this.#pageNo > 1) { parameters.push(["pageNo", this.#pageNo.toString()]); } - if (this.#sortField) { - parameters.push(["sortField", this.#sortField]); - parameters.push(["sortOrder", this.#sortOrder]); + if (this.#sorting.getSortField()) { + parameters.push(["sortField", this.#sorting.getSortField()]); + parameters.push(["sortOrder", this.#sorting.getSortOrder()]); } this.#filter.getActiveFilters().forEach((value, key) => { @@ -184,8 +148,7 @@ export class GridView { #handlePopState(): void { let pageNo = 1; - this.#sortField = this.#defaultSortField; - this.#sortOrder = this.#defaultSortOrder; + this.#sorting.resetSorting(); this.#filter.resetFilters(); const url = new URL(window.location.href); @@ -196,11 +159,11 @@ export class GridView { } if (key === "sortField") { - this.#sortField = value; + this.#sorting.setSortField(value); } if (key === "sortOrder") { - this.#sortOrder = value; + this.#sorting.setSortOrder(value); } const matches = key.match(/^filters\[([a-z0-9_]+)\]$/i); @@ -230,4 +193,13 @@ export class GridView { return filter; } + + #setupSorting(sortField: string, sortOrder: string): Sorting { + const sorting = new Sorting(this.#table, sortField, sortOrder); + sorting.addEventListener("switchPage", (event) => { + this.#switchPage(event.detail.pageNo); + }); + + return sorting; + } } diff --git a/ts/WoltLabSuite/Core/Component/GridView/Sorting.ts b/ts/WoltLabSuite/Core/Component/GridView/Sorting.ts new file mode 100644 index 0000000000..a8c34204b4 --- /dev/null +++ b/ts/WoltLabSuite/Core/Component/GridView/Sorting.ts @@ -0,0 +1,89 @@ +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export class Sorting extends EventTarget { + #defaultSortField: string; + #defaultSortOrder: string; + #sortField: string; + #sortOrder: string; + #table: HTMLTableElement; + + constructor(table: HTMLTableElement, sortField: string, sortOrder: string) { + super(); + + this.#sortField = sortField; + this.#defaultSortField = sortField; + this.#sortOrder = sortOrder; + this.#defaultSortOrder = sortOrder; + this.#table = table; + + this.#table + .querySelectorAll('.gridView__headerColumn[data-sortable="1"]') + .forEach((element) => { + const button = element.querySelector(".gridView__headerColumn__button"); + button?.addEventListener("click", () => { + this.#sort(element.dataset.id!); + }); + }); + + this.#renderActiveSorting(); + } + + getSortField(): string { + return this.#sortField; + } + + getSortOrder(): string { + return this.#sortOrder; + } + + resetSorting(): void { + this.#sortField = this.#defaultSortField; + this.#sortOrder = this.#defaultSortOrder; + } + + setSortField(sortField: string): void { + this.#sortField = sortField; + } + + setSortOrder(sortOrder: string): void { + this.#sortOrder = sortOrder; + } + + #sort(sortField: string): void { + if (this.#sortField == sortField && this.#sortOrder == "ASC") { + this.#sortOrder = "DESC"; + } else { + this.#sortField = sortField; + this.#sortOrder = "ASC"; + } + + this.dispatchEvent(new CustomEvent("switchPage", { detail: { pageNo: 1 } })); + this.#renderActiveSorting(); + } + + #renderActiveSorting(): void { + this.#table.querySelectorAll('th[data-sortable="1"]').forEach((element) => { + element.classList.remove("active", "ASC", "DESC"); + + if (element.dataset.id == this.#sortField) { + element.classList.add("active", this.#sortOrder); + } + }); + } +} + +interface SortingEventMap { + switchPage: CustomEvent<{ pageNo: number }>; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface Sorting extends EventTarget { + addEventListener: { + ( + type: T, + listener: (this: Sorting, ev: SortingEventMap[T]) => any, + options?: boolean | AddEventListenerOptions, + ): void; + } & HTMLElement["addEventListener"]; +} + +export default Sorting; diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js index 206831ce94..26b04084e3 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js @@ -1,4 +1,4 @@ -define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridviews/GetRows", "../Dom/Change/Listener", "../Dom/Util", "../Helper/Selector", "../Ui/Dropdown/Simple", "./GridView/Filter"], function (require, exports, tslib_1, GetRow_1, GetRows_1, Listener_1, Util_1, Selector_1, Simple_1, Filter_1) { +define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridviews/GetRows", "../Dom/Change/Listener", "../Dom/Util", "../Helper/Selector", "../Ui/Dropdown/Simple", "./GridView/Filter", "./GridView/Sorting"], function (require, exports, tslib_1, GetRow_1, GetRows_1, Listener_1, Util_1, Selector_1, Simple_1, Filter_1, Sorting_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GridView = void 0; @@ -6,18 +6,16 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi Util_1 = tslib_1.__importDefault(Util_1); Simple_1 = tslib_1.__importDefault(Simple_1); Filter_1 = tslib_1.__importDefault(Filter_1); + Sorting_1 = tslib_1.__importDefault(Sorting_1); class GridView { #filter; #gridClassName; #table; #pagination; + #sorting; #baseUrl; #noItemsNotice; #pageNo; - #sortField; - #sortOrder; - #defaultSortField; - #defaultSortOrder; #gridViewParameters; constructor(gridId, gridClassName, pageNo, baseUrl = "", sortField = "", sortOrder = "ASC", gridViewParameters) { this.#gridClassName = gridClassName; @@ -26,15 +24,12 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi this.#noItemsNotice = document.getElementById(`${gridId}_noItemsNotice`); this.#pageNo = pageNo; this.#baseUrl = baseUrl; - this.#sortField = sortField; - this.#defaultSortField = sortField; - this.#sortOrder = sortOrder; - this.#defaultSortOrder = sortOrder; this.#gridViewParameters = gridViewParameters; this.#initPagination(); this.#initSorting(); this.#initInteractions(); this.#filter = this.#setupFilter(gridId); + this.#sorting = this.#setupSorting(sortField, sortOrder); this.#initEventListeners(); window.addEventListener("popstate", () => { this.#handlePopState(); @@ -45,43 +40,14 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi void this.#switchPage(event.detail); }); } - #initSorting() { - this.#table - .querySelectorAll('.gridView__headerColumn[data-sortable="1"]') - .forEach((element) => { - const button = element.querySelector(".gridView__headerColumn__button"); - button?.addEventListener("click", () => { - this.#sort(element.dataset.id); - }); - }); - this.#renderActiveSorting(); - } - #sort(sortField) { - if (this.#sortField == sortField && this.#sortOrder == "ASC") { - this.#sortOrder = "DESC"; - } - else { - this.#sortField = sortField; - this.#sortOrder = "ASC"; - } - this.#switchPage(1); - this.#renderActiveSorting(); - } - #renderActiveSorting() { - this.#table.querySelectorAll('th[data-sortable="1"]').forEach((element) => { - element.classList.remove("active", "ASC", "DESC"); - if (element.dataset.id == this.#sortField) { - element.classList.add("active", this.#sortOrder); - } - }); - } + #initSorting() { } #switchPage(pageNo, updateQueryString = true) { this.#pagination.page = pageNo; this.#pageNo = pageNo; void this.#loadRows(updateQueryString); } async #loadRows(updateQueryString = true) { - const response = (await (0, GetRows_1.getRows)(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder, this.#filter.getActiveFilters(), this.#gridViewParameters)).unwrap(); + const response = (await (0, GetRows_1.getRows)(this.#gridClassName, this.#pageNo, this.#sorting.getSortField(), this.#sorting.getSortOrder(), this.#filter.getActiveFilters(), this.#gridViewParameters)).unwrap(); Util_1.default.setInnerHtml(this.#table.querySelector("tbody"), response.template); this.#table.hidden = response.totalRows == 0; this.#noItemsNotice.hidden = response.totalRows != 0; @@ -106,9 +72,9 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi if (this.#pageNo > 1) { parameters.push(["pageNo", this.#pageNo.toString()]); } - if (this.#sortField) { - parameters.push(["sortField", this.#sortField]); - parameters.push(["sortOrder", this.#sortOrder]); + if (this.#sorting.getSortField()) { + parameters.push(["sortField", this.#sorting.getSortField()]); + parameters.push(["sortOrder", this.#sorting.getSortOrder()]); } this.#filter.getActiveFilters().forEach((value, key) => { parameters.push([`filters[${key}]`, value]); @@ -139,8 +105,7 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi } #handlePopState() { let pageNo = 1; - this.#sortField = this.#defaultSortField; - this.#sortOrder = this.#defaultSortOrder; + this.#sorting.resetSorting(); this.#filter.resetFilters(); const url = new URL(window.location.href); url.searchParams.forEach((value, key) => { @@ -149,10 +114,10 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi return; } if (key === "sortField") { - this.#sortField = value; + this.#sorting.setSortField(value); } if (key === "sortOrder") { - this.#sortOrder = value; + this.#sorting.setSortOrder(value); } const matches = key.match(/^filters\[([a-z0-9_]+)\]$/i); if (matches) { @@ -176,6 +141,13 @@ define(["require", "exports", "tslib", "../Api/Gridviews/GetRow", "../Api/Gridvi }); return filter; } + #setupSorting(sortField, sortOrder) { + const sorting = new Sorting_1.default(this.#table, sortField, sortOrder); + sorting.addEventListener("switchPage", (event) => { + this.#switchPage(event.detail.pageNo); + }); + return sorting; + } } exports.GridView = GridView; }); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Sorting.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Sorting.js new file mode 100644 index 0000000000..008fd19ed2 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Sorting.js @@ -0,0 +1,67 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Sorting = void 0; + // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging + class Sorting extends EventTarget { + #defaultSortField; + #defaultSortOrder; + #sortField; + #sortOrder; + #table; + constructor(table, sortField, sortOrder) { + super(); + this.#sortField = sortField; + this.#defaultSortField = sortField; + this.#sortOrder = sortOrder; + this.#defaultSortOrder = sortOrder; + this.#table = table; + this.#table + .querySelectorAll('.gridView__headerColumn[data-sortable="1"]') + .forEach((element) => { + const button = element.querySelector(".gridView__headerColumn__button"); + button?.addEventListener("click", () => { + this.#sort(element.dataset.id); + }); + }); + this.#renderActiveSorting(); + } + getSortField() { + return this.#sortField; + } + getSortOrder() { + return this.#sortOrder; + } + resetSorting() { + this.#sortField = this.#defaultSortField; + this.#sortOrder = this.#defaultSortOrder; + } + setSortField(sortField) { + this.#sortField = sortField; + } + setSortOrder(sortOrder) { + this.#sortOrder = sortOrder; + } + #sort(sortField) { + if (this.#sortField == sortField && this.#sortOrder == "ASC") { + this.#sortOrder = "DESC"; + } + else { + this.#sortField = sortField; + this.#sortOrder = "ASC"; + } + this.dispatchEvent(new CustomEvent("switchPage", { detail: { pageNo: 1 } })); + this.#renderActiveSorting(); + } + #renderActiveSorting() { + this.#table.querySelectorAll('th[data-sortable="1"]').forEach((element) => { + element.classList.remove("active", "ASC", "DESC"); + if (element.dataset.id == this.#sortField) { + element.classList.add("active", this.#sortOrder); + } + }); + } + } + exports.Sorting = Sorting; + exports.default = Sorting; +});