Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * @copyright  2020 Simey Lameze <simey@moodle.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
import * as Selectors from 'core_table/local/dynamic/selectors';
24
import Events from './local/dynamic/events';
25
import Pending from 'core/pending';
26
import {addIconToContainer} from 'core/loadingicon';
27
import {fetch as fetchTableData} from 'core_table/local/dynamic/repository';
28
import Notification from 'core/notification';
29
 
30
let watching = false;
31
 
32
/**
33
 * Ensure that a table is a dynamic table.
34
 *
35
 * @param {HTMLElement} tableRoot
36
 * @returns {Bool}
37
 */
38
const 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
 
44
    if (!tableRoot.matches(Selectors.main.region)) {
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
 */
58
const 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
66
 * @param {Bool} resetContent
67
 * @returns {Promise}
68
 */
69
export const refreshTableContent = (tableRoot, resetContent = false) => {
70
    const filterset = getFiltersetFromTable(tableRoot);
71
    addIconToContainer(tableRoot);
72
 
73
    const pendingPromise = new Pending('core_table/dynamic:refreshTableContent');
74
 
75
    return fetchTableData(
76
        tableRoot.dataset.tableComponent,
77
        tableRoot.dataset.tableHandler,
78
        tableRoot.dataset.tableUniqueid,
79
        {
80
            sortData: JSON.parse(tableRoot.dataset.tableSortData),
81
            joinType: filterset.jointype,
82
            filters: filterset.filters,
83
            firstinitial: tableRoot.dataset.tableFirstInitial,
84
            lastinitial: tableRoot.dataset.tableLastInitial,
85
            pageNumber: tableRoot.dataset.tablePageNumber,
86
            pageSize: tableRoot.dataset.tablePageSize,
87
            hiddenColumns: JSON.parse(tableRoot.dataset.tableHiddenColumns),
88
        },
89
        resetContent,
90
    )
91
    .then(data => {
92
        const placeholder = document.createElement('div');
93
        placeholder.innerHTML = data.html;
94
        tableRoot.replaceWith(...placeholder.childNodes);
95
 
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
 
103
        return tableRoot;
104
    })
105
    .then(tableRoot => {
106
        pendingPromise.resolve();
107
 
108
        return tableRoot;
109
    });
110
};
111
 
112
export const updateTable = (tableRoot, {
113
    sortBy = null,
114
    sortOrder = null,
115
    filters = null,
116
    firstInitial = null,
117
    lastInitial = null,
118
    pageNumber = null,
119
    pageSize = null,
120
    hiddenColumns = null,
121
} = {}, refreshContent = true) => {
122
    checkTableIsDynamic(tableRoot);
123
 
124
    const pendingPromise = new Pending('core_table/dynamic:updateTable');
125
    let tableConfigChanged = false;
126
 
127
    // Update sort fields.
128
    if (sortBy && sortOrder) {
129
        // Always update the table if requested and there were sort fields.
130
        // These fields are only ever normalised in the backend.
131
        tableConfigChanged = true;
132
 
133
        const sortData = JSON.parse(tableRoot.dataset.tableSortData);
134
        sortData.unshift({
135
            sortby: sortBy,
136
            sortorder: parseInt(sortOrder, 10),
137
        });
138
        tableRoot.dataset.tableSortData = JSON.stringify(sortData);
139
    }
140
 
141
    // Update initials.
142
    if (firstInitial !== null) {
143
        if (tableRoot.dataset.tableFirstInitial !== firstInitial) {
144
            tableConfigChanged = true;
145
        }
146
 
147
        tableRoot.dataset.tableFirstInitial = firstInitial;
148
    }
149
 
150
    if (lastInitial !== null) {
151
        if (tableRoot.dataset.tableLastInitial !== lastInitial) {
152
            tableConfigChanged = true;
153
        }
154
 
155
        tableRoot.dataset.tableLastInitial = lastInitial;
156
    }
157
 
158
    if (pageSize !== null) {
159
        if (tableRoot.dataset.tablePageSize != pageSize) {
160
            tableConfigChanged = true;
161
        }
162
 
163
        tableRoot.dataset.tablePageSize = pageSize;
164
    }
165
 
166
    // Update filters.
167
    if (filters) {
168
        const filterJson = JSON.stringify(filters);
169
 
170
        if (tableRoot.dataset.tableFilters !== filterJson) {
171
            tableConfigChanged = true;
172
        }
173
 
174
        tableRoot.dataset.tableFilters = filterJson;
175
    }
176
 
177
    // Reset to page 1 when table content is being altered by filtering or sorting.
178
    // This ensures the table page being loaded always exists, and gives a consistent experience.
179
    if (tableConfigChanged) {
180
        pageNumber = 1;
181
    }
182
 
183
    // Update hidden columns.
184
    if (hiddenColumns) {
185
        const columnJson = JSON.stringify(hiddenColumns);
186
 
187
        if (tableRoot.dataset.tableHiddenColumns !== columnJson) {
188
            tableConfigChanged = true;
189
        }
190
 
191
        tableRoot.dataset.tableHiddenColumns = columnJson;
192
    }
193
 
194
    if (pageNumber !== null) {
195
        if (tableRoot.dataset.tablePageNumber != pageNumber) {
196
            tableConfigChanged = true;
197
        }
198
 
199
        tableRoot.dataset.tablePageNumber = pageNumber;
200
    }
201
 
202
    // Refresh.
203
    if (refreshContent && tableConfigChanged) {
204
        return refreshTableContent(tableRoot)
205
        .then(tableRoot => {
206
            pendingPromise.resolve();
207
            return tableRoot;
208
        });
209
    } else {
210
        pendingPromise.resolve();
211
        return Promise.resolve(tableRoot);
212
    }
213
};
214
 
215
/**
216
 * Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.
217
 *
218
 * @param {HTMLElement} tableRoot
219
 * @returns {DOMStringMap}
220
 */
221
const getTableData = tableRoot => {
222
    checkTableIsDynamic(tableRoot);
223
 
224
    return tableRoot.dataset;
225
};
226
 
227
/**
228
 * Update the specified table using the new filters.
229
 *
230
 * @param {HTMLElement} tableRoot
231
 * @param {Object} filters
232
 * @param {Bool} refreshContent
233
 * @returns {Promise}
234
 */
235
export const setFilters = (tableRoot, filters, refreshContent = true) =>
236
    updateTable(tableRoot, {filters}, refreshContent);
237
 
238
/**
239
 * Get the filter data for the specified table.
240
 *
241
 * @param {HTMLElement} tableRoot
242
 * @returns {Object}
243
 */
244
export const getFilters = tableRoot => {
245
    checkTableIsDynamic(tableRoot);
246
 
247
    return getFiltersetFromTable(tableRoot);
248
};
249
 
250
/**
251
 * Update the sort order.
252
 *
253
 * @param {HTMLElement} tableRoot
254
 * @param {String} sortBy
255
 * @param {Number} sortOrder
256
 * @param {Bool} refreshContent
257
 * @returns {Promise}
258
 */
259
export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>
260
    updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);
261
 
262
/**
263
 * Set the page number.
264
 *
265
 * @param {HTMLElement} tableRoot
266
 * @param {String} pageNumber
267
 * @param {Bool} refreshContent
268
 * @returns {Promise}
269
 */
270
export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
271
    updateTable(tableRoot, {pageNumber}, refreshContent);
272
 
273
/**
274
 * Get the current page number.
275
 *
276
 * @param {HTMLElement} tableRoot
277
 * @returns {Number}
278
 */
279
export const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;
280
 
281
/**
282
 * Set the page size.
283
 *
284
 * @param {HTMLElement} tableRoot
285
 * @param {Number} pageSize
286
 * @param {Bool} refreshContent
287
 * @returns {Promise}
288
 */
289
export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
290
    updateTable(tableRoot, {pageSize, pageNumber: 1}, refreshContent);
291
 
292
/**
293
 * Get the current page size.
294
 *
295
 * @param {HTMLElement} tableRoot
296
 * @returns {Number}
297
 */
298
export const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;
299
 
300
/**
301
 * Update the first initial to show.
302
 *
303
 * @param {HTMLElement} tableRoot
304
 * @param {String} firstInitial
305
 * @param {Bool} refreshContent
306
 * @returns {Promise}
307
 */
308
export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>
309
    updateTable(tableRoot, {firstInitial}, refreshContent);
310
 
311
/**
312
 * Get the current first initial filter.
313
 *
314
 * @param {HTMLElement} tableRoot
315
 * @returns {String}
316
 */
317
export const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;
318
 
319
/**
320
 * Update the last initial to show.
321
 *
322
 * @param {HTMLElement} tableRoot
323
 * @param {String} lastInitial
324
 * @param {Bool} refreshContent
325
 * @returns {Promise}
326
 */
327
export const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>
328
    updateTable(tableRoot, {lastInitial}, refreshContent);
329
 
330
/**
331
 * Get the current last initial filter.
332
 *
333
 * @param {HTMLElement} tableRoot
334
 * @returns {String}
335
 */
336
export const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;
337
 
338
/**
339
 * Hide a column in the participants table.
340
 *
341
 * @param {HTMLElement} tableRoot
342
 * @param {String} columnToHide
343
 * @param {Bool} refreshContent
344
 * @returns {Promise}
345
 */
346
export const hideColumn = (tableRoot, columnToHide, refreshContent = true) => {
347
    const hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
348
    hiddenColumns.push(columnToHide);
349
 
350
    return updateTable(tableRoot, {hiddenColumns}, refreshContent);
351
};
352
 
353
/**
354
 * Make a hidden column visible in the participants table.
355
 *
356
 * @param {HTMLElement} tableRoot
357
 * @param {String} columnToShow
358
 * @param {Bool} refreshContent
359
 * @returns {Promise}
360
 */
361
export const showColumn = (tableRoot, columnToShow, refreshContent = true) => {
362
    let hiddenColumns = JSON.parse(tableRoot.dataset.tableHiddenColumns);
363
    hiddenColumns = hiddenColumns.filter(columnName => columnName !== columnToShow);
364
 
365
    return updateTable(tableRoot, {hiddenColumns}, refreshContent);
366
};
367
 
368
/**
369
 * Reset table preferences.
370
 *
371
 * @param {HTMLElement} tableRoot
372
 * @returns {Promise}
373
 */
374
const resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);
375
 
376
/**
377
 * Set up listeners to handle table updates.
378
 */
379
export const init = () => {
380
    if (watching) {
381
        // Already watching.
382
        return;
383
    }
384
    watching = true;
385
 
386
    document.addEventListener('click', e => {
387
        const tableRoot = e.target.closest(Selectors.main.region);
388
 
389
        if (!tableRoot) {
390
            return;
391
        }
392
 
393
        const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);
394
        if (sortableLink) {
395
            e.preventDefault();
396
 
397
            setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder)
398
            .catch(Notification.exception);
399
        }
400
 
401
        const firstInitialLink = e.target.closest(Selectors.initialsBar.links.firstInitial);
402
        if (firstInitialLink !== null) {
403
            e.preventDefault();
404
 
405
            setFirstInitial(tableRoot, firstInitialLink.dataset.initial).catch(Notification.exception);
406
        }
407
 
408
        const lastInitialLink = e.target.closest(Selectors.initialsBar.links.lastInitial);
409
        if (lastInitialLink !== null) {
410
            e.preventDefault();
411
 
412
            setLastInitial(tableRoot, lastInitialLink.dataset.initial).catch(Notification.exception);
413
        }
414
 
415
        const pageItem = e.target.closest(Selectors.paginationBar.links.pageItem);
416
        if (pageItem) {
417
            e.preventDefault();
418
 
419
            setPageNumber(tableRoot, pageItem.dataset.pageNumber).catch(Notification.exception);
420
        }
421
 
422
        const hide = e.target.closest(Selectors.table.links.hide);
423
        if (hide) {
424
            e.preventDefault();
425
 
426
            hideColumn(tableRoot, hide.dataset.column).catch(Notification.exception);
427
        }
428
 
429
        const show = e.target.closest(Selectors.table.links.show);
430
        if (show) {
431
            e.preventDefault();
432
 
433
            showColumn(tableRoot, show.dataset.column).catch(Notification.exception);
434
        }
435
 
436
        const resetTablePreferencesLink = e.target.closest('.resettable a');
437
        if (resetTablePreferencesLink) {
438
            e.preventDefault();
439
 
440
            resetTablePreferences(tableRoot).catch(Notification.exception);
441
        }
442
 
443
        const showCountLink = e.target.closest(Selectors.showCount.links.toggle);
444
        if (showCountLink) {
445
            e.preventDefault();
446
 
447
            setPageSize(tableRoot, showCountLink.dataset.targetPageSize).catch(Notification.exception);
448
        }
449
    });
450
};
451
 
452
/**
453
 * Fetch the table via its table region id.
454
 *
455
 * @param {String} tableRegionId
456
 * @returns {HTMLElement}
457
 */
458
export const getTableFromId = tableRegionId => {
459
    const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));
460
 
461
 
462
    if (!tableRoot) {
463
        // The table is not a dynamic table.
464
        throw new Error("The table specified is not a dynamic table and cannot be updated");
465
    }
466
 
467
    return tableRoot;
468
};
469
 
470
export {
471
    Events
472
};