10abe968f043cca7169f07dc16478d92dec4ed28
[moodle.git] / lib / table / amd / src / dynamic.js
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/>.
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  */
24 import {fetch as fetchTableData} from 'core_table/local/dynamic/repository';
25 import * as Selectors from 'core_table/local/dynamic/selectors';
26 import Events from './local/dynamic/events';
27 import {addIconToContainer} from 'core/loadingicon';
29 let watching = false;
31 /**
32  * Ensure that a table is a dynamic table.
33  *
34  * @param {HTMLElement} tableRoot
35  * @returns {Bool}
36  */
37 const checkTableIsDynamic = tableRoot => {
38     if (!tableRoot) {
39         // The table is not a dynamic table.
40         throw new Error("The table specified is not a dynamic table and cannot be updated");
41     }
43     if (!tableRoot.matches(Selectors.main.region)) {
44         // The table is not a dynamic table.
45         throw new Error("The table specified is not a dynamic table and cannot be updated");
46     }
48     return true;
49 };
51 /**
52  * Get the filterset data from a known dynamic table.
53  *
54  * @param {HTMLElement} tableRoot
55  * @returns {Object}
56  */
57 const getFiltersetFromTable = tableRoot => {
58     return JSON.parse(tableRoot.dataset.tableFilters);
59 };
61 /**
62  * Update the specified table based on its current values.
63  *
64  * @param {HTMLElement} tableRoot
65  * @param {Bool} resetContent
66  * @returns {Promise}
67  */
68 export const refreshTableContent = (tableRoot, resetContent = false) => {
69     const filterset = getFiltersetFromTable(tableRoot);
70     addIconToContainer(tableRoot);
72     return fetchTableData(
73         tableRoot.dataset.tableComponent,
74         tableRoot.dataset.tableHandler,
75         tableRoot.dataset.tableUniqueid,
76         {
77             sortData: JSON.parse(tableRoot.dataset.tableSortData),
78             joinType: filterset.jointype,
79             filters: filterset.filters,
80             firstinitial: tableRoot.dataset.tableFirstInitial,
81             lastinitial: tableRoot.dataset.tableLastInitial,
82             pageNumber: tableRoot.dataset.tablePageNumber,
83             pageSize: tableRoot.dataset.tablePageSize,
84             hiddenColumns: JSON.parse(tableRoot.dataset.tableHiddenColumns),
85         },
86         resetContent,
87     )
88     .then(data => {
89         const placeholder = document.createElement('div');
90         placeholder.innerHTML = data.html;
91         tableRoot.replaceWith(...placeholder.childNodes);
93         // Update the tableRoot.
94         return getTableFromId(tableRoot.dataset.tableUniqueid);
95     }).then(tableRoot => {
96         tableRoot.dispatchEvent(new CustomEvent(Events.tableContentRefreshed, {
97             bubbles: true,
98         }));
100         return tableRoot;
101     });
102 };
104 export const updateTable = (tableRoot, {
105     sortBy = null,
106     sortOrder = null,
107     filters = null,
108     firstInitial = null,
109     lastInitial = null,
110     pageNumber = null,
111     pageSize = null,
112     hiddenColumns = null,
113 } = {}, refreshContent = true) => {
114     checkTableIsDynamic(tableRoot);
116     // Update sort fields.
117     if (sortBy && sortOrder) {
118         const sortData = JSON.parse(tableRoot.dataset.tableSortData);
119         sortData.unshift({
120             sortby: sortBy,
121             sortorder: parseInt(sortOrder, 10),
122         });
123         tableRoot.dataset.tableSortData = JSON.stringify(sortData);
124     }
126     // Update initials.
127     if (firstInitial !== null) {
128         tableRoot.dataset.tableFirstInitial = firstInitial;
129     }
131     if (lastInitial !== null) {
132         tableRoot.dataset.tableLastInitial = lastInitial;
133     }
135     if (pageNumber !== null) {
136         tableRoot.dataset.tablePageNumber = pageNumber;
137     }
139     if (pageSize !== null) {
140         tableRoot.dataset.tablePageSize = pageSize;
141     }
143     // Update filters.
144     if (filters) {
145         tableRoot.dataset.tableFilters = JSON.stringify(filters);
146     }
148     // Update hidden columns.
149     if (hiddenColumns) {
150         tableRoot.dataset.tableHiddenColumns = JSON.stringify(hiddenColumns);
151     }
153     // Refresh.
154     if (refreshContent) {
155         return refreshTableContent(tableRoot);
156     } else {
157         return Promise.resolve(tableRoot);
158     }
159 };
161 /**
162  * Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.
163  *
164  * @param {HTMLElement} tableRoot
165  * @returns {DOMStringMap}
166  */
167 const getTableData = tableRoot => {
168     checkTableIsDynamic(tableRoot);
170     return tableRoot.dataset;
171 };
173 /**
174  * Update the specified table using the new filters.
175  *
176  * @param {HTMLElement} tableRoot
177  * @param {Object} filters
178  * @param {Bool} refreshContent
179  * @returns {Promise}
180  */
181 export const setFilters = (tableRoot, filters, refreshContent = true) =>
182     updateTable(tableRoot, {filters}, refreshContent);
184 /**
185  * Get the filter data for the specified table.
186  *
187  * @param {HTMLElement} tableRoot
188  * @returns {Object}
189  */
190 export const getFilters = tableRoot => {
191     checkTableIsDynamic(tableRoot);
193     return getFiltersetFromTable(tableRoot);
194 };
196 /**
197  * Update the sort order.
198  *
199  * @param {HTMLElement} tableRoot
200  * @param {String} sortBy
201  * @param {Number} sortOrder
202  * @param {Bool} refreshContent
203  * @returns {Promise}
204  */
205 export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>
206     updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);
208 /**
209  * Set the page number.
210  *
211  * @param {HTMLElement} tableRoot
212  * @param {String} pageNumber
213  * @param {Bool} refreshContent
214  * @returns {Promise}
215  */
216 export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
217     updateTable(tableRoot, {pageNumber}, refreshContent);
219 /**
220  * Get the current page number.
221  *
222  * @param {HTMLElement} tableRoot
223  * @returns {Number}
224  */
225 export const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;
227 /**
228  * Set the page size.
229  *
230  * @param {HTMLElement} tableRoot
231  * @param {Number} pageSize
232  * @param {Bool} refreshContent
233  * @returns {Promise}
234  */
235 export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
236     updateTable(tableRoot, {pageSize, pageNumber: 0}, refreshContent);
238 /**
239  * Get the current page size.
240  *
241  * @param {HTMLElement} tableRoot
242  * @returns {Number}
243  */
244 export const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;
246 /**
247  * Update the first initial to show.
248  *
249  * @param {HTMLElement} tableRoot
250  * @param {String} firstInitial
251  * @param {Bool} refreshContent
252  * @returns {Promise}
253  */
254 export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>
255     updateTable(tableRoot, {firstInitial}, refreshContent);
257 /**
258  * Get the current first initial filter.
259  *
260  * @param {HTMLElement} tableRoot
261  * @returns {String}
262  */
263 export const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;
265 /**
266  * Update the last initial to show.
267  *
268  * @param {HTMLElement} tableRoot
269  * @param {String} lastInitial
270  * @param {Bool} refreshContent
271  * @returns {Promise}
272  */
273 export const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>
274     updateTable(tableRoot, {lastInitial}, refreshContent);
276 /**
277  * Get the current last initial filter.
278  *
279  * @param {HTMLElement} tableRoot
280  * @returns {String}
281  */
282 export const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;
284 /**
285  * Hide a column in the participants table.
286  *
287  * @param {HTMLElement} tableRoot
288  * @param {String} columnToHide
289  * @param {Bool} refreshContent
290  */
291 export const hideColumn = (tableRoot, columnToHide, refreshContent = true) => {
292     const hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
293     hiddenColumns.push(columnToHide);
295     updateTable(tableRoot, {hiddenColumns}, refreshContent);
296 };
298 /**
299  * Make a hidden column visible in the participants table.
300  *
301  * @param {HTMLElement} tableRoot
302  * @param {String} columnToShow
303  * @param {Bool} refreshContent
304  */
305 export const showColumn = (tableRoot, columnToShow, refreshContent = true) => {
306     let hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
307     hiddenColumns = hiddenColumns.filter(columnName => columnName !== columnToShow);
309     updateTable(tableRoot, {hiddenColumns}, refreshContent);
310 };
312 /**
313  * Reset table preferences.
314  *
315  * @param {HTMLElement} tableRoot
316  * @returns {Promise}
317  */
318 const resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);
320 /**
321  * Set up listeners to handle table updates.
322  */
323 export const init = () => {
324     if (watching) {
325         // Already watching.
326         return;
327     }
328     watching = true;
330     document.addEventListener('click', e => {
331         const tableRoot = e.target.closest(Selectors.main.region);
333         if (!tableRoot) {
334             return;
335         }
337         const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);
338         if (sortableLink) {
339             e.preventDefault();
341             setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder);
342         }
344         const firstInitialLink = e.target.closest(Selectors.initialsBar.links.firstInitial);
345         if (firstInitialLink !== null) {
346             e.preventDefault();
348             setFirstInitial(tableRoot, firstInitialLink.dataset.initial);
349         }
351         const lastInitialLink = e.target.closest(Selectors.initialsBar.links.lastInitial);
352         if (lastInitialLink !== null) {
353             e.preventDefault();
355             setLastInitial(tableRoot, lastInitialLink.dataset.initial);
356         }
358         const pageItem = e.target.closest(Selectors.paginationBar.links.pageItem);
359         if (pageItem) {
360             e.preventDefault();
362             setPageNumber(tableRoot, pageItem.dataset.pageNumber);
363         }
365         const hide = e.target.closest(Selectors.table.links.hide);
366         if (hide) {
367             e.preventDefault();
369             hideColumn(tableRoot, hide.dataset.column);
370         }
372         const show = e.target.closest(Selectors.table.links.show);
373         if (show) {
374             e.preventDefault();
376             showColumn(tableRoot, show.dataset.column);
377         }
379         const resetTablePreferencesLink = e.target.closest('.resettable a');
380         if (resetTablePreferencesLink) {
381             e.preventDefault();
383             resetTablePreferences(tableRoot);
384         }
385     });
386 };
388 /**
389  * Fetch the table via its table region id.
390  *
391  * @param {String} tableRegionId
392  * @returns {HTMLElement}
393  */
394 export const getTableFromId = tableRegionId => {
395     const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));
398     if (!tableRoot) {
399         // The table is not a dynamic table.
400         throw new Error("The table specified is not a dynamic table and cannot be updated");
401     }
403     return tableRoot;
404 };
406 export {
407     Events
408 };