Merge branch 'MDL-68320-master' of git://github.com/lameze/moodle
[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  * Update the specified table using the new filters.
163  *
164  * @param {HTMLElement} tableRoot
165  * @param {Object} filters
166  * @param {Bool} refreshContent
167  * @returns {Promise}
168  */
169 export const setFilters = (tableRoot, filters, refreshContent = true) =>
170     updateTable(tableRoot, {filters}, refreshContent);
172 /**
173  * Update the sort order.
174  *
175  * @param {HTMLElement} tableRoot
176  * @param {String} sortBy
177  * @param {Number} sortOrder
178  * @param {Bool} refreshContent
179  * @returns {Promise}
180  */
181 export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>
182     updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);
184 /**
185  * Set the page number.
186  *
187  * @param {HTMLElement} tableRoot
188  * @param {String} pageNumber
189  * @param {Bool} refreshContent
190  * @returns {Promise}
191  */
192 export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
193     updateTable(tableRoot, {pageNumber}, refreshContent);
195 /**
196  * Set the page size.
197  *
198  * @param {HTMLElement} tableRoot
199  * @param {Number} pageSize
200  * @param {Bool} refreshContent
201  * @returns {Promise}
202  */
203 export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
204     updateTable(tableRoot, {pageSize, pageNumber: 0}, refreshContent);
206 /**
207  * Update the first initial to show.
208  *
209  * @param {HTMLElement} tableRoot
210  * @param {String} firstInitial
211  * @param {Bool} refreshContent
212  * @returns {Promise}
213  */
214 export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>
215     updateTable(tableRoot, {firstInitial}, refreshContent);
217 /**
218  * Update the last initial to show.
219  *
220  * @param {HTMLElement} tableRoot
221  * @param {String} lastInitial
222  * @param {Bool} refreshContent
223  * @returns {Promise}
224  */
225 export const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>
226     updateTable(tableRoot, {lastInitial}, refreshContent);
228 /**
229  * Hide a column in the participants table.
230  *
231  * @param {HTMLElement} tableRoot
232  * @param {String} columnToHide
233  * @param {Bool} refreshContent
234  */
235 export const hideColumn = (tableRoot, columnToHide, refreshContent = true) => {
236     const hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
237     hiddenColumns.push(columnToHide);
239     updateTable(tableRoot, {hiddenColumns}, refreshContent);
240 };
242 /**
243  * Make a hidden column visible in the participants table.
244  *
245  * @param {HTMLElement} tableRoot
246  * @param {String} columnToShow
247  * @param {Bool} refreshContent
248  */
249 export const showColumn = (tableRoot, columnToShow, refreshContent = true) => {
250     let hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
251     hiddenColumns = hiddenColumns.filter(columnName => columnName !== columnToShow);
253     updateTable(tableRoot, {hiddenColumns}, refreshContent);
254 };
256 /**
257  * Reset table preferences.
258  *
259  * @param {HTMLElement} tableRoot
260  * @returns {Promise}
261  */
262 const resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);
264 /**
265  * Set up listeners to handle table updates.
266  */
267 export const init = () => {
268     if (watching) {
269         // Already watching.
270         return;
271     }
272     watching = true;
274     document.addEventListener('click', e => {
275         const tableRoot = e.target.closest(Selectors.main.region);
277         if (!tableRoot) {
278             return;
279         }
281         const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);
282         if (sortableLink) {
283             e.preventDefault();
285             setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder);
286         }
288         const firstInitialLink = e.target.closest(Selectors.initialsBar.links.firstInitial);
289         if (firstInitialLink !== null) {
290             e.preventDefault();
292             setFirstInitial(tableRoot, firstInitialLink.dataset.initial);
293         }
295         const lastInitialLink = e.target.closest(Selectors.initialsBar.links.lastInitial);
296         if (lastInitialLink !== null) {
297             e.preventDefault();
299             setLastInitial(tableRoot, lastInitialLink.dataset.initial);
300         }
302         const pageItem = e.target.closest(Selectors.paginationBar.links.pageItem);
303         if (pageItem) {
304             e.preventDefault();
306             setPageNumber(tableRoot, pageItem.dataset.pageNumber);
307         }
309         const hide = e.target.closest(Selectors.table.links.hide);
310         if (hide) {
311             e.preventDefault();
313             hideColumn(tableRoot, hide.dataset.column);
314         }
316         const show = e.target.closest(Selectors.table.links.show);
317         if (show) {
318             e.preventDefault();
320             showColumn(tableRoot, show.dataset.column);
321         }
323         const resetTablePreferencesLink = e.target.closest('.resettable a');
324         if (resetTablePreferencesLink) {
325             e.preventDefault();
327             resetTablePreferences(tableRoot);
328         }
329     });
330 };
332 /**
333  * Fetch the table via its table region id.
334  *
335  * @param {String} tableRegionId
336  * @returns {HTMLElement}
337  */
338 export const getTableFromId = tableRegionId => {
339     const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));
342     if (!tableRoot) {
343         // The table is not a dynamic table.
344         throw new Error("The table specified is not a dynamic table and cannot be updated");
345     }
347     return tableRoot;
348 };
350 export {
351     Events
352 };