Skip to content

Commit d3edc52

Browse files
committed
Bulk interactions
1 parent 3d4b2ec commit d3edc52

27 files changed

+847
-24
lines changed

com.woltlab.wcf/templates/shared_gridView.tpl

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
<table class="gridView__table" id="{$view->getID()}_table"{if !$view->countRows()} hidden{/if}>
2323
<thead>
2424
<tr class="gridView__headerRow">
25-
{if true}
25+
{if $view->hasBulkInteractions()}
2626
<th class="gridView__headerColumn gridView__selectColumn">
27-
<input type="checkbox" class="gridView__selectAllRows" aria-label="todo: select all rows">
27+
<input type="checkbox" class="gridView__selectAllRows" aria-label="{lang}wcf.clipboard.item.markAll{/lang}">
2828
</th>
2929
{/if}
3030
{foreach from=$view->getVisibleColumns() item='column'}
@@ -57,9 +57,18 @@
5757
<woltlab-core-pagination id="{$view->getID()}_pagination" page="{$view->getPageNo()}" count="{$view->countPages()}"></woltlab-core-pagination>
5858
</div>
5959

60-
<div class="gridView__selectionBar">
61-
62-
</div>
60+
{if $view->hasBulkInteractions()}
61+
<div id="{$view->getID()}_selectionBar" class="gridView__selectionBar dropdown" hidden>
62+
<button type="button" id="{$view->getID()}_bulkInteractionButton" class="button gridView__bulkInteractionButton dropdownToggle">3 Entries Selected</button>
63+
<ul class="dropdownMenu">
64+
<li class="disabled"><span>Lädt ...</span></li>
65+
<li class="dropdownDivider"></li>
66+
<li>
67+
<button type="button" id="{$view->getID()}_resetSelectionButton">{lang}wcf.clipboard.item.unmarkAll{/lang}</button>
68+
</li>
69+
</ul>
70+
</div>
71+
{/if}
6372

6473
<woltlab-core-notice type="info" id="{$view->getID()}_noItemsNotice"{if $view->countRows()} hidden{/if}>{lang}wcf.global.noItems{/lang}</woltlab-core-notice>
6574
</div>
@@ -73,14 +82,18 @@
7382
'{unsafe:$view->getBaseUrl()|encodeJS}',
7483
'{unsafe:$view->getSortField()|encodeJS}',
7584
'{unsafe:$view->getSortOrder()|encodeJS}',
85+
'{unsafe:$view->getBulkInteractionProviderClassName()|encodeJS}',
7686
new Map([
7787
{foreach from=$view->getParameters() key='name' item='value'}
7888
['{unsafe:$name|encodeJs}', '{unsafe:$value|encodeJs}'],
7989
{/foreach}
80-
])
90+
]),
8191
);
8292
});
8393
</script>
8494
{if $view->hasInteractions()}
8595
{unsafe:$view->renderInteractionInitialization()}
8696
{/if}
97+
{if $view->hasBulkInteractions()}
98+
{unsafe:$view->renderBulkInteractionInitialization()}
99+
{/if}

com.woltlab.wcf/templates/shared_gridViewRows.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11

22
{foreach from=$view->getRows() item='row'}
33
<tr class="gridView__row" data-object-id="{$view->getObjectID($row)}">
4-
{if true}
4+
{if $view->hasBulkInteractions()}
55
<td class="gridView__column gridView__selectColumn">
6-
<input type="checkbox" class="gridView__selectRow" aria-label="todo: select row">
6+
<input type="checkbox" class="gridView__selectRow" aria-label="{lang}wcf.clipboard.item.mark{/lang}">
77
</td>
88
{/if}
99
{foreach from=$view->getVisibleColumns() item='column'}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
2+
import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";
3+
4+
type Response = {
5+
template: string;
6+
};
7+
8+
export async function getBulkContextMenuOptions(
9+
providerClassName: string,
10+
objectIds: number[],
11+
): Promise<ApiResult<Response>> {
12+
let response: Response;
13+
try {
14+
response = (await prepareRequest(`${window.WSC_RPC_API_URL}core/interactions/bulk-context-menu-options`)
15+
.post({ provider: providerClassName, objectIDs: objectIds })
16+
.disableLoadingIndicator()
17+
.fetchAsJson()) as Response;
18+
} catch (e) {
19+
return apiResultFromError(e);
20+
}
21+
22+
return apiResultFromValue(response);
23+
}

ts/WoltLabSuite/Core/Component/GridView.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getRow } from "../Api/Gridviews/GetRow";
22
import { getRows } from "../Api/Gridviews/GetRows";
3+
import { getBulkContextMenuOptions } from "../Api/Interactions/GetBulkContextMenuOptions";
34
import DomChangeListener from "../Dom/Change/Listener";
45
import DomUtil from "../Dom/Util";
56
import { wheneverFirstSeen } from "../Helper/Selector";
@@ -10,9 +11,8 @@ export class GridView {
1011
readonly #gridClassName: string;
1112
readonly #table: HTMLTableElement;
1213
readonly #state: State;
13-
1414
readonly #noItemsNotice: HTMLElement;
15-
15+
readonly #bulkInteractionProviderClassName: string;
1616
#gridViewParameters?: Map<string, string>;
1717

1818
constructor(
@@ -22,12 +22,13 @@ export class GridView {
2222
baseUrl: string = "",
2323
sortField = "",
2424
sortOrder = "ASC",
25+
bulkInteractionProviderClassName: string,
2526
gridViewParameters?: Map<string, string>,
2627
) {
2728
this.#gridClassName = gridClassName;
2829
this.#table = document.getElementById(`${gridId}_table`) as HTMLTableElement;
2930
this.#noItemsNotice = document.getElementById(`${gridId}_noItemsNotice`) as HTMLElement;
30-
31+
this.#bulkInteractionProviderClassName = bulkInteractionProviderClassName;
3132
this.#gridViewParameters = gridViewParameters;
3233

3334
this.#initInteractions();
@@ -98,7 +99,15 @@ export class GridView {
9899
state.addEventListener("change", (event) => {
99100
void this.#loadRows(event.detail.source);
100101
});
102+
state.addEventListener("getBulkInteractions", (event) => {
103+
void this.#loadBulkInteractions(event.detail.objectIds);
104+
});
101105

102106
return state;
103107
}
108+
109+
async #loadBulkInteractions(objectIds: number[]): Promise<void> {
110+
const response = await getBulkContextMenuOptions(this.#bulkInteractionProviderClassName, objectIds);
111+
this.#state.setBulkInteractionContextMenuOptions(response.unwrap().template);
112+
}
104113
}

ts/WoltLabSuite/Core/Component/GridView/Selection.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
11
import { getStoragePrefix } from "WoltLabSuite/Core/Core";
22
import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector";
33

4-
export class Selection {
4+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
5+
export class Selection extends EventTarget {
56
readonly #markAll: HTMLInputElement | null = null;
67
readonly #table: HTMLTableElement;
8+
readonly #selectionBar: HTMLElement | null = null;
9+
readonly #bulkInteractionButton: HTMLButtonElement | null = null;
10+
#bulkInteractionContextMenuOptions: string | null = null;
11+
12+
constructor(gridId: string, table: HTMLTableElement) {
13+
super();
714

8-
constructor(table: HTMLTableElement) {
915
this.#table = table;
1016

1117
this.#markAll = this.#table.querySelector<HTMLInputElement>(".gridView__selectAllRows");
1218
this.#markAll?.addEventListener("change", () => {
1319
this.#change(this.#markAll!.checked);
1420
});
1521

22+
this.#selectionBar = document.getElementById(`${gridId}_selectionBar`) as HTMLElement;
23+
this.#bulkInteractionButton = document.getElementById(`${gridId}_bulkInteractionButton`) as HTMLButtonElement;
24+
this.#bulkInteractionButton?.addEventListener("click", () => {
25+
this.#showBulkInteractionMenu();
26+
});
27+
28+
document.getElementById(`${gridId}_resetSelectionButton`)?.addEventListener("click", () => {
29+
this.#resetSelection();
30+
});
31+
1632
wheneverFirstSeen(`#${this.#table.id} .gridView__selectRow`, (checkbox: HTMLInputElement) => {
1733
checkbox.addEventListener("change", () => {
1834
this.#change();
@@ -74,6 +90,10 @@ export class Selection {
7490
if (!skipStorage) {
7591
this.#saveSelection(checkboxes);
7692
}
93+
94+
this.#bulkInteractionContextMenuOptions = null;
95+
96+
this.#updateSelectionBar();
7797
}
7898

7999
#saveSelection(checkboxes: HTMLInputElement[]): void {
@@ -128,6 +148,67 @@ export class Selection {
128148
#getStorageKey(): string {
129149
return getStoragePrefix() + `gridView-${this.#table.id}-selection`;
130150
}
151+
152+
#updateSelectionBar(): void {
153+
const selectedIds = this.getSelectedIds();
154+
155+
if (!this.#selectionBar) {
156+
return;
157+
}
158+
159+
if (selectedIds.length === 0) {
160+
this.#selectionBar.hidden = true;
161+
return;
162+
}
163+
164+
this.#selectionBar.hidden = false;
165+
this.#bulkInteractionButton!.textContent = `${selectedIds.length} selected`;
166+
}
167+
168+
#showBulkInteractionMenu(): void {
169+
if (this.#bulkInteractionContextMenuOptions === null) {
170+
this.#loadBulkInteractionMenu();
171+
return;
172+
}
173+
}
174+
175+
#loadBulkInteractionMenu(): void {
176+
this.dispatchEvent(new CustomEvent("getBulkInteractions", { detail: { objectIds: this.getSelectedIds() } }));
177+
}
178+
179+
setBulkInteractionContextMenuOptions(options: string): void {
180+
this.#bulkInteractionContextMenuOptions = options;
181+
}
182+
183+
#resetSelection(): void {
184+
if (this.#markAll !== null) {
185+
this.#markAll.checked = false;
186+
this.#markAll.indeterminate = false;
187+
}
188+
189+
this.#table
190+
.querySelectorAll<HTMLInputElement>(".gridView__selectRow")
191+
.forEach((checkbox) => (checkbox.checked = false));
192+
193+
window.localStorage.removeItem(this.#getStorageKey());
194+
195+
this.#updateSelectionBar();
196+
}
197+
}
198+
199+
interface SelectionEventMap {
200+
getBulkInteractions: CustomEvent<{ objectIds: number[] }>;
201+
}
202+
203+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
204+
export interface Selection extends EventTarget {
205+
addEventListener: {
206+
<T extends keyof SelectionEventMap>(
207+
type: T,
208+
listener: (this: Selection, ev: SelectionEventMap[T]) => any,
209+
options?: boolean | AddEventListenerOptions,
210+
): void;
211+
} & HTMLElement["addEventListener"];
131212
}
132213

133214
export default Selection;

ts/WoltLabSuite/Core/Component/GridView/State.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ export class State extends EventTarget {
4545
this.#switchPage(1, StateChangeCause.Change);
4646
});
4747

48-
this.#selection = new Selection(table);
48+
this.#selection = new Selection(gridId, table);
49+
this.#selection.addEventListener("getBulkInteractions", (event) => {
50+
this.dispatchEvent(new CustomEvent("getBulkInteractions", { detail: { objectIds: event.detail.objectIds } }));
51+
});
4952

5053
window.addEventListener("popstate", () => {
5154
this.#handlePopState();
@@ -134,10 +137,15 @@ export class State extends EventTarget {
134137

135138
this.#switchPage(pageNo, StateChangeCause.History);
136139
}
140+
141+
setBulkInteractionContextMenuOptions(options: string): void {
142+
this.#selection.setBulkInteractionContextMenuOptions(options);
143+
}
137144
}
138145

139146
interface StateEventMap {
140147
change: CustomEvent<{ source: StateChangeCause }>;
148+
getBulkInteractions: CustomEvent<{ objectIds: number[] }>;
141149
}
142150

143151
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging

wcfsetup/install/files/js/WoltLabSuite/Core/Api/Interactions/GetBulkContextMenuOptions.js

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/Api/Interactions/GetBulkInteractions.js

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/Api/Interactions/GetContextMenuOptions copy.js

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView.js

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)