MDL-68612 core_table: Pending checks for dyanmic updates
[moodle.git] / lib / table / amd / src / dynamic.js
CommitLineData
1592c3c4
SL
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * Module to handle dynamic table features.
18 *
19 * @module core_table/dynamic
20 * @package core_table
21 * @copyright 2020 Simey Lameze <simey@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
1592c3c4 24import * as Selectors from 'core_table/local/dynamic/selectors';
ca69d387 25import Events from './local/dynamic/events';
444ae8c8 26import Pending from 'core/pending';
eebe3ca6 27import {addIconToContainer} from 'core/loadingicon';
444ae8c8 28import {fetch as fetchTableData} from 'core_table/local/dynamic/repository';
1592c3c4
SL
29
30let watching = false;
31
32/**
33 * Ensure that a table is a dynamic table.
34 *
35 * @param {HTMLElement} tableRoot
36 * @returns {Bool}
37 */
38const checkTableIsDynamic = tableRoot => {
39 if (!tableRoot) {
40 // The table is not a dynamic table.
41 throw new Error("The table specified is not a dynamic table and cannot be updated");
42 }
43
c540a575 44 if (!tableRoot.matches(Selectors.main.region)) {
1592c3c4
SL
45 // The table is not a dynamic table.
46 throw new Error("The table specified is not a dynamic table and cannot be updated");
47 }
48
49 return true;
50};
51
52/**
53 * Get the filterset data from a known dynamic table.
54 *
55 * @param {HTMLElement} tableRoot
56 * @returns {Object}
57 */
58const getFiltersetFromTable = tableRoot => {
59 return JSON.parse(tableRoot.dataset.tableFilters);
60};
61
62/**
63 * Update the specified table based on its current values.
64 *
65 * @param {HTMLElement} tableRoot
68c46a28 66 * @param {Bool} resetContent
1592c3c4
SL
67 * @returns {Promise}
68 */
68c46a28 69export const refreshTableContent = (tableRoot, resetContent = false) => {
1592c3c4 70 const filterset = getFiltersetFromTable(tableRoot);
eebe3ca6 71 addIconToContainer(tableRoot);
1592c3c4 72
444ae8c8
AN
73 const pendingPromise = new Pending('core_table/dynamic:refreshTableContent');
74
1592c3c4 75 return fetchTableData(
4cf97b7f 76 tableRoot.dataset.tableComponent,
1592c3c4
SL
77 tableRoot.dataset.tableHandler,
78 tableRoot.dataset.tableUniqueid,
79 {
a31a2b6d 80 sortData: JSON.parse(tableRoot.dataset.tableSortData),
1592c3c4
SL
81 joinType: filterset.jointype,
82 filters: filterset.filters,
c540a575
AN
83 firstinitial: tableRoot.dataset.tableFirstInitial,
84 lastinitial: tableRoot.dataset.tableLastInitial,
f7b84afe
SL
85 pageNumber: tableRoot.dataset.tablePageNumber,
86 pageSize: tableRoot.dataset.tablePageSize,
e2f12c22 87 hiddenColumns: JSON.parse(tableRoot.dataset.tableHiddenColumns),
68c46a28
SL
88 },
89 resetContent,
1592c3c4
SL
90 )
91 .then(data => {
92 const placeholder = document.createElement('div');
93 placeholder.innerHTML = data.html;
94 tableRoot.replaceWith(...placeholder.childNodes);
95
ca69d387
AN
96 // Update the tableRoot.
97 return getTableFromId(tableRoot.dataset.tableUniqueid);
98 }).then(tableRoot => {
99 tableRoot.dispatchEvent(new CustomEvent(Events.tableContentRefreshed, {
100 bubbles: true,
101 }));
102
444ae8c8
AN
103 return tableRoot;
104 })
105 .then(tableRoot => {
106 pendingPromise.resolve();
107
ca69d387 108 return tableRoot;
1592c3c4
SL
109 });
110};
111
112export const updateTable = (tableRoot, {
113 sortBy = null,
114 sortOrder = null,
115 filters = null,
c540a575
AN
116 firstInitial = null,
117 lastInitial = null,
f7b84afe
SL
118 pageNumber = null,
119 pageSize = null,
e2f12c22 120 hiddenColumns = null,
1592c3c4
SL
121} = {}, refreshContent = true) => {
122 checkTableIsDynamic(tableRoot);
123
444ae8c8
AN
124 const pendingPromise = new Pending('core_table/dynamic:updateTable');
125
1592c3c4
SL
126 // Update sort fields.
127 if (sortBy && sortOrder) {
a31a2b6d
AN
128 const sortData = JSON.parse(tableRoot.dataset.tableSortData);
129 sortData.unshift({
130 sortby: sortBy,
131 sortorder: parseInt(sortOrder, 10),
132 });
133 tableRoot.dataset.tableSortData = JSON.stringify(sortData);
1592c3c4
SL
134 }
135
c540a575
AN
136 // Update initials.
137 if (firstInitial !== null) {
138 tableRoot.dataset.tableFirstInitial = firstInitial;
139 }
140
141 if (lastInitial !== null) {
142 tableRoot.dataset.tableLastInitial = lastInitial;
143 }
144
f7b84afe
SL
145 if (pageNumber !== null) {
146 tableRoot.dataset.tablePageNumber = pageNumber;
147 }
148
149 if (pageSize !== null) {
150 tableRoot.dataset.tablePageSize = pageSize;
151 }
152
1592c3c4
SL
153 // Update filters.
154 if (filters) {
155 tableRoot.dataset.tableFilters = JSON.stringify(filters);
156 }
157
e2f12c22
SL
158 // Update hidden columns.
159 if (hiddenColumns) {
160 tableRoot.dataset.tableHiddenColumns = JSON.stringify(hiddenColumns);
161 }
162
1592c3c4
SL
163 // Refresh.
164 if (refreshContent) {
444ae8c8
AN
165 return refreshTableContent(tableRoot)
166 .then(tableRoot => {
167 pendingPromise.resolve();
168 return tableRoot;
169 });
1592c3c4 170 } else {
444ae8c8 171 pendingPromise.resolve();
ca69d387 172 return Promise.resolve(tableRoot);
1592c3c4
SL
173 }
174};
175
084c955e
AN
176/**
177 * Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.
178 *
179 * @param {HTMLElement} tableRoot
180 * @returns {DOMStringMap}
181 */
182const getTableData = tableRoot => {
183 checkTableIsDynamic(tableRoot);
184
185 return tableRoot.dataset;
186};
187
1592c3c4
SL
188/**
189 * Update the specified table using the new filters.
190 *
191 * @param {HTMLElement} tableRoot
192 * @param {Object} filters
193 * @param {Bool} refreshContent
194 * @returns {Promise}
195 */
196export const setFilters = (tableRoot, filters, refreshContent = true) =>
197 updateTable(tableRoot, {filters}, refreshContent);
198
084c955e
AN
199/**
200 * Get the filter data for the specified table.
201 *
202 * @param {HTMLElement} tableRoot
203 * @returns {Object}
204 */
205export const getFilters = tableRoot => {
206 checkTableIsDynamic(tableRoot);
207
208 return getFiltersetFromTable(tableRoot);
209};
210
1592c3c4
SL
211/**
212 * Update the sort order.
213 *
214 * @param {HTMLElement} tableRoot
215 * @param {String} sortBy
216 * @param {Number} sortOrder
217 * @param {Bool} refreshContent
218 * @returns {Promise}
219 */
220export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>
221 updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);
222
f7b84afe
SL
223/**
224 * Set the page number.
225 *
226 * @param {HTMLElement} tableRoot
227 * @param {String} pageNumber
228 * @param {Bool} refreshContent
229 * @returns {Promise}
230 */
231export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
232 updateTable(tableRoot, {pageNumber}, refreshContent);
233
084c955e
AN
234/**
235 * Get the current page number.
236 *
237 * @param {HTMLElement} tableRoot
238 * @returns {Number}
239 */
240export const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;
241
f7b84afe
SL
242/**
243 * Set the page size.
244 *
245 * @param {HTMLElement} tableRoot
246 * @param {Number} pageSize
247 * @param {Bool} refreshContent
248 * @returns {Promise}
249 */
250export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
251 updateTable(tableRoot, {pageSize, pageNumber: 0}, refreshContent);
252
084c955e
AN
253/**
254 * Get the current page size.
255 *
256 * @param {HTMLElement} tableRoot
257 * @returns {Number}
258 */
259export const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;
260
c540a575
AN
261/**
262 * Update the first initial to show.
263 *
264 * @param {HTMLElement} tableRoot
265 * @param {String} firstInitial
266 * @param {Bool} refreshContent
267 * @returns {Promise}
268 */
269export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>
270 updateTable(tableRoot, {firstInitial}, refreshContent);
271
084c955e
AN
272/**
273 * Get the current first initial filter.
274 *
275 * @param {HTMLElement} tableRoot
276 * @returns {String}
277 */
278export const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;
279
c540a575
AN
280/**
281 * Update the last initial to show.
282 *
283 * @param {HTMLElement} tableRoot
284 * @param {String} lastInitial
285 * @param {Bool} refreshContent
286 * @returns {Promise}
287 */
288export const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>
289 updateTable(tableRoot, {lastInitial}, refreshContent);
290
084c955e
AN
291/**
292 * Get the current last initial filter.
293 *
294 * @param {HTMLElement} tableRoot
295 * @returns {String}
296 */
297export const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;
298
e2f12c22
SL
299/**
300 * Hide a column in the participants table.
301 *
302 * @param {HTMLElement} tableRoot
303 * @param {String} columnToHide
304 * @param {Bool} refreshContent
305 */
306export const hideColumn = (tableRoot, columnToHide, refreshContent = true) => {
307 const hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
308 hiddenColumns.push(columnToHide);
309
310 updateTable(tableRoot, {hiddenColumns}, refreshContent);
311};
312
313/**
314 * Make a hidden column visible in the participants table.
315 *
316 * @param {HTMLElement} tableRoot
317 * @param {String} columnToShow
318 * @param {Bool} refreshContent
319 */
320export const showColumn = (tableRoot, columnToShow, refreshContent = true) => {
321 let hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
322 hiddenColumns = hiddenColumns.filter(columnName => columnName !== columnToShow);
323
324 updateTable(tableRoot, {hiddenColumns}, refreshContent);
325};
326
68c46a28
SL
327/**
328 * Reset table preferences.
329 *
330 * @param {HTMLElement} tableRoot
331 * @returns {Promise}
332 */
333const resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);
334
1592c3c4
SL
335/**
336 * Set up listeners to handle table updates.
337 */
338export const init = () => {
339 if (watching) {
340 // Already watching.
341 return;
342 }
343 watching = true;
344
345 document.addEventListener('click', e => {
c540a575 346 const tableRoot = e.target.closest(Selectors.main.region);
1592c3c4
SL
347
348 if (!tableRoot) {
349 return;
350 }
351
352 const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);
353 if (sortableLink) {
354 e.preventDefault();
355
356 setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder);
357 }
c540a575
AN
358
359 const firstInitialLink = e.target.closest(Selectors.initialsBar.links.firstInitial);
360 if (firstInitialLink !== null) {
361 e.preventDefault();
362
363 setFirstInitial(tableRoot, firstInitialLink.dataset.initial);
364 }
365
366 const lastInitialLink = e.target.closest(Selectors.initialsBar.links.lastInitial);
367 if (lastInitialLink !== null) {
368 e.preventDefault();
369
370 setLastInitial(tableRoot, lastInitialLink.dataset.initial);
371 }
f7b84afe
SL
372
373 const pageItem = e.target.closest(Selectors.paginationBar.links.pageItem);
374 if (pageItem) {
375 e.preventDefault();
376
377 setPageNumber(tableRoot, pageItem.dataset.pageNumber);
378 }
e2f12c22
SL
379
380 const hide = e.target.closest(Selectors.table.links.hide);
381 if (hide) {
382 e.preventDefault();
383
384 hideColumn(tableRoot, hide.dataset.column);
385 }
386
387 const show = e.target.closest(Selectors.table.links.show);
388 if (show) {
389 e.preventDefault();
390
391 showColumn(tableRoot, show.dataset.column);
392 }
393
68c46a28
SL
394 const resetTablePreferencesLink = e.target.closest('.resettable a');
395 if (resetTablePreferencesLink) {
396 e.preventDefault();
397
398 resetTablePreferences(tableRoot);
399 }
1592c3c4
SL
400 });
401};
478039f9
AN
402
403/**
ca69d387 404 * Fetch the table via its table region id.
478039f9
AN
405 *
406 * @param {String} tableRegionId
407 * @returns {HTMLElement}
408 */
409export const getTableFromId = tableRegionId => {
410 const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));
411
412
413 if (!tableRoot) {
414 // The table is not a dynamic table.
415 throw new Error("The table specified is not a dynamic table and cannot be updated");
416 }
417
418 return tableRoot;
419};
ca69d387
AN
420
421export {
422 Events
423};