Skip to content

Commit

Permalink
Add filters
Browse files Browse the repository at this point in the history
  • Loading branch information
BurntimeX committed Sep 30, 2024
1 parent 0b75b22 commit 3570d72
Show file tree
Hide file tree
Showing 17 changed files with 500 additions and 26 deletions.
13 changes: 12 additions & 1 deletion com.woltlab.wcf/templates/shared_gridView.tpl
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
{if $view->isFilterable()}
<button type="button" class="button" id="{$view->getID()}_filterButton" data-endpoint="{$view->getFilterActionEndpoint()}">Filter</button>
<div id="{$view->getID()}_filters">
{foreach from=$view->getActiveFilters() item='value' key='key'}
<button type="button" class="button" data-filter="{$key}" data-filter-value="{$value}">{$view->getFilterLabel($key)}</button>
{/foreach}
</div>
{/if}

{if $view->countRows()}
<div class="paginationTop">
<woltlab-core-pagination id="{$view->getID()}_topPagination" page="{$view->getPageNo()}" count="{$view->countPages()}"></woltlab-core-pagination>
Expand All @@ -16,7 +25,9 @@
{unsafe:$column->getLabel()}
</th>
{/foreach}
<th></th>
{if $view->hasActions()}
<th></th>
{/if}
</td>
</thead>
<tbody>
Expand Down
27 changes: 15 additions & 12 deletions com.woltlab.wcf/templates/shared_gridViewRows.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
{unsafe:$view->renderColumn($column, $row)}
</td>
{/foreach}
<td>
<div class="dropdown">
<button type="button" class="gridViewActions button small dropdownToggle" aria-label="{lang}wcf.global.button.more{/lang}">{icon name='ellipsis-vertical'}</button>
{if $view->hasActions()}
<td>
<div class="dropdown">
<button type="button" class="gridViewActions button small dropdownToggle" aria-label="{lang}wcf.global.button.more{/lang}">{icon name='ellipsis-vertical'}</button>

<ul class="dropdownMenu">
{foreach from=$view->getActions() item='action'}
<li>
{unsafe:$view->renderAction($action, $row)}
</li>
{/foreach}
</ul>
</div>
</td>
<ul class="dropdownMenu">
{foreach from=$view->getActions() item='action'}
<li>
{unsafe:$view->renderAction($action, $row)}
</li>
{/foreach}

</ul>
</div>
</td>
{/if}
</tr>
{/foreach}
8 changes: 8 additions & 0 deletions ts/WoltLabSuite/Core/Api/GridViews/GetRows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";

type Response = {
template: string;
pages: number;
filterLabels: ArrayLike<string>;
};

export async function getRows(
gridViewClass: string,
pageNo: number,
sortField: string = "",
sortOrder: string = "ASC",
filters?: Map<string, string>,
): Promise<ApiResult<Response>> {
const url = new URL(`${window.WSC_RPC_API_URL}core/gridViews/rows`);
url.searchParams.set("gridView", gridViewClass);
url.searchParams.set("pageNo", pageNo.toString());
url.searchParams.set("sortField", sortField);
url.searchParams.set("sortOrder", sortOrder);
if (filters) {
filters.forEach((value, key) => {
url.searchParams.set(`filters[${key}]`, value);
});
}

let response: Response;
try {
Expand Down
97 changes: 95 additions & 2 deletions ts/WoltLabSuite/Core/Component/GridView.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { getRows } from "../Api/GridViews/GetRows";
import DomUtil from "../Dom/Util";
import { promiseMutex } from "../Helper/PromiseMutex";
import UiDropdownSimple from "../Ui/Dropdown/Simple";
import { dialogFactory } from "./Dialog";

export class GridView {
readonly #gridClassName: string;
readonly #table: HTMLTableElement;
readonly #topPagination: WoltlabCorePaginationElement;
readonly #bottomPagination: WoltlabCorePaginationElement;
readonly #baseUrl: string;
readonly #filterButton: HTMLButtonElement;
readonly #filterPills: HTMLElement;
#pageNo: number;
#sortField: string;
#sortOrder: string;
#defaultSortField: string;
#defaultSortOrder: string;
#filters: Map<string, string>;

constructor(
gridId: string,
Expand All @@ -26,6 +31,8 @@ export class GridView {
this.#table = document.getElementById(`${gridId}_table`) as HTMLTableElement;
this.#topPagination = document.getElementById(`${gridId}_topPagination`) as WoltlabCorePaginationElement;
this.#bottomPagination = document.getElementById(`${gridId}_bottomPagination`) as WoltlabCorePaginationElement;
this.#filterButton = document.getElementById(`${gridId}_filterButton`) as HTMLButtonElement;
this.#filterPills = document.getElementById(`${gridId}_filters`) as HTMLElement;
this.#pageNo = pageNo;
this.#baseUrl = baseUrl;
this.#sortField = sortField;
Expand All @@ -36,6 +43,7 @@ export class GridView {
this.#initPagination();
this.#initSorting();
this.#initActions();
this.#initFilters();

window.addEventListener("popstate", () => {
this.#handlePopState();
Expand Down Expand Up @@ -98,11 +106,19 @@ export class GridView {
}

async #loadRows(updateQueryString: boolean = true): Promise<void> {
const response = await getRows(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder);
DomUtil.setInnerHtml(this.#table.querySelector("tbody")!, response.unwrap().template);
const response = (
await getRows(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder, this.#filters)
).unwrap();
DomUtil.setInnerHtml(this.#table.querySelector("tbody")!, response.template);

this.#topPagination.count = response.pages;
this.#bottomPagination.count = response.pages;

if (updateQueryString) {
this.#updateQueryString();
}

this.#renderFilters(response.filterLabels);
this.#initActions();
}

Expand All @@ -121,6 +137,9 @@ export class GridView {
parameters.push(["sortField", this.#sortField]);
parameters.push(["sortOrder", this.#sortOrder]);
}
this.#filters.forEach((value, key) => {
parameters.push([`filters[${key}]`, value]);
});

if (parameters.length > 0) {
url.search += url.search !== "" ? "&" : "?";
Expand Down Expand Up @@ -148,10 +167,79 @@ export class GridView {
});
}

#initFilters(): void {
if (!this.#filterButton) {
return;
}

this.#filterButton.addEventListener(
"click",
promiseMutex(() => this.#showFilterDialog()),
);

if (!this.#filterPills) {
return;
}

const filterButtons = this.#filterPills.querySelectorAll<HTMLButtonElement>("[data-filter]");
if (!filterButtons.length) {
return;
}

this.#filters = new Map<string, string>();
filterButtons.forEach((button) => {
this.#filters.set(button.dataset.filter!, button.dataset.filterValue!);
button.addEventListener("click", () => {
this.#removeFilter(button.dataset.filter!);
});
});
}

async #showFilterDialog(): Promise<void> {
const url = new URL(this.#filterButton.dataset.endpoint!);
if (this.#filters) {
this.#filters.forEach((value, key) => {
url.searchParams.set(`filters[${key}]`, value);
});
}

const { ok, result } = await dialogFactory().usingFormBuilder().fromEndpoint(url.toString());

if (ok) {
this.#filters = new Map(Object.entries(result as ArrayLike<string>));
this.#switchPage(1);
}
}

#renderFilters(labels: ArrayLike<string>): void {
this.#filterPills.innerHTML = "";
if (!this.#filters) {
return;
}

this.#filters.forEach((value, key) => {
const button = document.createElement("button");
button.type = "button";
button.classList.add("button");
button.innerText = labels[key];
button.addEventListener("click", () => {
this.#removeFilter(key);
});

this.#filterPills.append(button);
});
}

#removeFilter(filter: string): void {
this.#filters.delete(filter);
this.#switchPage(1);
}

#handlePopState(): void {
let pageNo = 1;
this.#sortField = this.#defaultSortField;
this.#sortOrder = this.#defaultSortOrder;
this.#filters = new Map<string, string>();

const url = new URL(window.location.href);
url.searchParams.forEach((value, key) => {
Expand All @@ -167,6 +255,11 @@ export class GridView {
if (key === "sortOrder") {
this.#sortOrder = value;
}

const matches = key.match(/^filters\[([a-z0-9_]+)\]$/i);
if (matches) {
this.#filters.set(matches[1], value);
}
});

this.#switchPage(pageNo, false);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3570d72

Please sign in to comment.