'{unsafe:$view->getID()|encodeJs}',
'{unsafe:$view->getClassName()|encodeJS}',
{$view->getPageNo()},
- '{unsafe:$view->getBaseUrl()|encodeJS}'
+ '{unsafe:$view->getBaseUrl()|encodeJS}',
+ '{unsafe:$view->getSortField()|encodeJS}',
+ '{unsafe:$view->getSortOrder()|encodeJS}'
);
});
</script>
template: string;
};
-export async function getRows(gridViewClass: string, pageNo: number): Promise<ApiResult<Response>> {
+export async function getRows(
+ gridViewClass: string,
+ pageNo: number,
+ sortField: string = "",
+ sortOrder: string = "ASC",
+): 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);
let response: Response;
try {
readonly #bottomPagination: WoltlabCorePaginationElement;
readonly #baseUrl: string;
#pageNo: number;
+ #sortField: string;
+ #sortOrder: string;
- constructor(gridId: string, gridClassName: string, pageNo: number, baseUrl: string = "") {
+ constructor(
+ gridId: string,
+ gridClassName: string,
+ pageNo: number,
+ baseUrl: string = "",
+ sortField = "",
+ sortOrder = "ASC",
+ ) {
this.#gridClassName = gridClassName;
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.#pageNo = pageNo;
this.#baseUrl = baseUrl;
+ this.#sortField = sortField;
+ this.#sortOrder = sortOrder;
this.#initPagination();
+ this.#initSorting();
}
#initPagination(): void {
this.#topPagination.addEventListener("switchPage", (event: CustomEvent) => {
- this.#switchPage(event.detail);
+ void this.#switchPage(event.detail);
});
this.#bottomPagination.addEventListener("switchPage", (event: CustomEvent) => {
- this.#switchPage(event.detail);
+ void this.#switchPage(event.detail);
});
}
- async #switchPage(pageNo: number): Promise<void> {
+ #initSorting(): void {
+ this.#table.querySelectorAll<HTMLTableCellElement>('th[data-sortable="1"]').forEach((element) => {
+ const link = document.createElement("a");
+ link.role = "button";
+ link.addEventListener("click", () => {
+ this.#sort(element.dataset.id!);
+ });
+
+ link.textContent = element.textContent;
+ element.innerHTML = "";
+ element.append(link);
+ });
+
+ this.#renderActiveSorting();
+ }
+
+ #sort(sortField: string): void {
+ if (this.#sortField == sortField && this.#sortOrder == "ASC") {
+ this.#sortOrder = "DESC";
+ } else {
+ this.#sortField = sortField;
+ this.#sortOrder = "ASC";
+ }
+
+ this.#loadRows();
+ this.#renderActiveSorting();
+ }
+
+ #renderActiveSorting(): void {
+ this.#table.querySelectorAll<HTMLTableCellElement>('th[data-sortable="1"]').forEach((element) => {
+ element.classList.remove("active", "ASC", "DESC");
+
+ if (element.dataset.id == this.#sortField) {
+ element.classList.add("active", this.#sortOrder);
+ }
+ });
+ }
+
+ #switchPage(pageNo: number): void {
this.#topPagination.page = pageNo;
this.#bottomPagination.page = pageNo;
-
- const response = await getRows(this.#gridClassName, pageNo);
this.#pageNo = pageNo;
+
+ this.#loadRows();
+ }
+
+ async #loadRows(): Promise<void> {
+ const response = await getRows(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder);
DomUtil.setInnerHtml(this.#table.querySelector("tbody")!, response.unwrap().template);
this.#updateQueryString();
}
if (this.#pageNo > 1) {
parameters.push(["pageNo", this.#pageNo.toString()]);
}
+ if (this.#sortField) {
+ parameters.push(["sortField", this.#sortField]);
+ parameters.push(["sortOrder", this.#sortOrder]);
+ }
if (parameters.length > 0) {
url.search += url.search !== "" ? "&" : "?";
url.search += new URLSearchParams(parameters).toString();
}
- window.history.pushState({ name: "gridView" }, document.title, url.toString());
+ window.history.pushState({}, document.title, url.toString());
}
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRows = void 0;
- async function getRows(gridViewClass, pageNo) {
+ async function getRows(gridViewClass, pageNo, sortField = "", sortOrder = "ASC") {
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);
let response;
try {
response = (await (0, Backend_1.prepareRequest)(url).get().allowCaching().disableLoadingIndicator().fetchAsJson());
#bottomPagination;
#baseUrl;
#pageNo;
- constructor(gridId, gridClassName, pageNo, baseUrl = "") {
+ #sortField;
+ #sortOrder;
+ constructor(gridId, gridClassName, pageNo, baseUrl = "", sortField = "", sortOrder = "ASC") {
this.#gridClassName = gridClassName;
this.#table = document.getElementById(`${gridId}_table`);
this.#topPagination = document.getElementById(`${gridId}_topPagination`);
this.#bottomPagination = document.getElementById(`${gridId}_bottomPagination`);
this.#pageNo = pageNo;
this.#baseUrl = baseUrl;
+ this.#sortField = sortField;
+ this.#sortOrder = sortOrder;
this.#initPagination();
+ this.#initSorting();
}
#initPagination() {
this.#topPagination.addEventListener("switchPage", (event) => {
- this.#switchPage(event.detail);
+ void this.#switchPage(event.detail);
});
this.#bottomPagination.addEventListener("switchPage", (event) => {
- this.#switchPage(event.detail);
+ void this.#switchPage(event.detail);
});
}
- async #switchPage(pageNo) {
+ #initSorting() {
+ this.#table.querySelectorAll('th[data-sortable="1"]').forEach((element) => {
+ const link = document.createElement("a");
+ link.role = "button";
+ link.addEventListener("click", () => {
+ this.#sort(element.dataset.id);
+ });
+ link.textContent = element.textContent;
+ element.innerHTML = "";
+ element.append(link);
+ });
+ this.#renderActiveSorting();
+ }
+ #sort(sortField) {
+ if (this.#sortField == sortField && this.#sortOrder == "ASC") {
+ this.#sortOrder = "DESC";
+ }
+ else {
+ this.#sortField = sortField;
+ this.#sortOrder = "ASC";
+ }
+ this.#loadRows();
+ 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);
+ }
+ });
+ }
+ #switchPage(pageNo) {
this.#topPagination.page = pageNo;
this.#bottomPagination.page = pageNo;
- const response = await (0, GetRows_1.getRows)(this.#gridClassName, pageNo);
this.#pageNo = pageNo;
+ this.#loadRows();
+ }
+ async #loadRows() {
+ const response = await (0, GetRows_1.getRows)(this.#gridClassName, this.#pageNo, this.#sortField, this.#sortOrder);
Util_1.default.setInnerHtml(this.#table.querySelector("tbody"), response.unwrap().template);
this.#updateQueryString();
}
if (this.#pageNo > 1) {
parameters.push(["pageNo", this.#pageNo.toString()]);
}
+ if (this.#sortField) {
+ parameters.push(["sortField", this.#sortField]);
+ parameters.push(["sortOrder", this.#sortOrder]);
+ }
if (parameters.length > 0) {
url.search += url.search !== "" ? "&" : "?";
url.search += new URLSearchParams(parameters).toString();
}
- window.history.pushState({ name: "gridView" }, document.title, url.toString());
+ window.history.pushState({}, document.title, url.toString());
}
}
exports.GridView = GridView;
$parameters = Helper::mapApiParameters($request, GetRowsParameters::class);
if (!\is_subclass_of($parameters->gridView, AbstractGridView::class)) {
- throw new UserInputException('gridView', $parameters->gridView);
+ throw new UserInputException('gridView', 'invalid');
}
$view = new $parameters->gridView($parameters->pageNo);
throw new PermissionDeniedException();
}
+ if ($parameters->sortField) {
+ $view->setSortField($parameters->sortField);
+ }
+ if ($parameters->sortOrder) {
+ $view->setSortOrder($parameters->sortOrder);
+ }
+
return new JsonResponse([
'template' => $view->renderRows(),
]);
public function __construct(
/** @var non-empty-string */
public readonly string $gridView,
- public readonly int $pageNo
+ public readonly int $pageNo,
+ public readonly string $sortField,
+ public readonly string $sortOrder
) {}
}
private array $columns = [];
private int $rowsPerPage = 2;
private string $baseUrl = '';
+ private string $sortField = '';
+ private string $sortOrder = 'ASC';
public function __construct(private readonly int $pageNo = 1)
{
foreach ($this->getColumns() as $column) {
$header .= <<<EOT
- <th class="{$column->getClasses()}">{$column->getLabel()}</th>
+ <th
+ class="{$column->getClasses()}"
+ data-id="{$column->getID()}"
+ data-sortable="{$column->isSortable()}"
+ >{$column->getLabel()}</th>
EOT;
}
{
return $this->baseUrl;
}
+
+ /**
+ * @return GridViewColumn[]
+ */
+ public function getSortableColumns(): array
+ {
+ return \array_filter($this->getColumns(), fn($column) => $column->isSortable());
+ }
+
+ public function setSortField(string $sortField): void
+ {
+ if (!\in_array($sortField, \array_map(fn($column) => $column->getID(), $this->getSortableColumns()))) {
+ throw new \InvalidArgumentException("Invalid value '{$sortField}' as sort field given.");
+ }
+
+ $this->sortField = $sortField;
+ }
+
+ public function setSortOrder(string $sortOrder): void
+ {
+ if ($sortOrder !== 'ASC' && $sortOrder !== 'DESC') {
+ throw new \InvalidArgumentException("Invalid value '{$sortOrder}' as sort order given.");
+ }
+
+ $this->sortOrder = $sortOrder;
+ }
+
+ public function getSortField(): string
+ {
+ return $this->sortField;
+ }
+
+ public function getSortOrder(): string
+ {
+ return $this->sortOrder;
+ }
}
* @var IColumnRenderer[]
*/
private array $renderer = [];
-
private string $label = '';
-
private static DefaultColumnRenderer $defaultRenderer;
+ private bool $sortable = false;
private function __construct(private readonly string $id) {}
return $this;
}
+ public function sortable(bool $sortable = true): static
+ {
+ $this->sortable = $sortable;
+
+ return $this;
+ }
+
/**
* @return IColumnRenderer[]
*/
return $this->label;
}
+ public function isSortable(): bool
+ {
+ return $this->sortable;
+ }
+
private static function getDefaultRenderer(): DefaultColumnRenderer
{
if (!isset(self::$defaultRenderer)) {
$this->addColumns([
GridViewColumn::for('rankID')
->label('wcf.global.objectID')
- ->renderer(new NumberColumnRenderer()),
+ ->renderer(new NumberColumnRenderer())
+ ->sortable(),
GridViewColumn::for('rankTitle')
->label('wcf.acp.user.rank.title')
+ ->sortable()
->renderer([
new class extends TitleColumnRenderer {
public function render(mixed $value, mixed $context = null): string
]),
GridViewColumn::for('rankImage')
->label('wcf.acp.user.rank.image')
+ ->sortable()
->renderer([
new class extends DefaultColumnRenderer {
public function render(mixed $value, mixed $context = null): string
]),
GridViewColumn::for('groupID')
->label('wcf.user.group')
+ ->sortable()
->renderer([
new class extends DefaultColumnRenderer {
public function render(mixed $value, mixed $context = null): string
]),
GridViewColumn::for('requiredGender')
->label('wcf.user.option.gender')
+ ->sortable()
->renderer([
new class extends DefaultColumnRenderer {
public function render(mixed $value, mixed $context = null): string
]),
GridViewColumn::for('requiredPoints')
->label('wcf.acp.user.rank.requiredPoints')
+ ->sortable()
->renderer(new NumberColumnRenderer()),
]);
+
+ $this->setSortField('rankTitle');
}
public function getRows(int $limit, int $offset = 0): array
$list = new UserRankList();
$list->sqlLimit = $limit;
$list->sqlOffset = $offset;
+ if ($this->getSortField()) {
+ $list->sqlOrderBy = $this->getSortField() . ' ' . $this->getSortOrder();
+ }
$list->readObjects();
return $list->getObjects();