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
 * Bulk selection auxiliar methods.
18
 *
19
 * @module     core_courseformat/local/content/actions/bulkselection
20
 * @class      core_courseformat/local/content/actions/bulkselection
21
 * @copyright  2023 Ferran Recio <ferran@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
class BulkSelector {
25
 
26
    /**
27
     * The class constructor.
28
     * @param {CourseEditor} courseEditor the original actions component.
29
     */
30
    constructor(courseEditor) {
31
        this.courseEditor = courseEditor;
32
        this.selectors = {
33
            BULKCMCHECKBOX: `[data-bulkcheckbox][data-action='toggleSelectionCm']`,
34
            BULKSECTIONCHECKBOX: `[data-bulkcheckbox][data-action='toggleSelectionSection']`,
35
            CONTENT: `#region-main`,
36
        };
37
    }
38
 
39
    /**
40
     * Process a new selection.
41
     * @param {Number} id
42
     * @param {String} elementType cm or section
43
     * @param {Object} settings special selection settings
44
     * @param {Boolean} settings.all if the action is over all elements of the same type
45
     * @param {Boolean} settings.range if the action is over a range of elements
46
     */
47
    processNewSelection(id, elementType, settings) {
48
        const value = !this._isBulkSelected(id, elementType);
49
        if (settings.all && settings.range) {
50
            this.switchCurrentSelection();
51
            return;
52
        }
53
        if (!this._isSelectable(id, elementType)) {
54
            return;
55
        }
56
        if (settings.all) {
57
            if (elementType == 'cm') {
58
                this._updateBulkCmSiblings(id, value);
59
            } else {
60
                this._updateBulkSelectionAll(elementType, value);
61
            }
62
            return;
63
        }
64
        if (settings.range) {
65
            this._updateBulkSelectionRange(id, elementType, value);
66
            return;
67
        }
68
        this._updateBulkSelection([id], elementType, value);
69
    }
70
 
71
    /**
72
     * Switch between section and cm selection.
73
     */
74
    switchCurrentSelection() {
75
        const bulk = this.courseEditor.get('bulk');
76
        if (bulk.selectedType === '' || bulk.selection.length == 0) {
77
            return;
78
        }
79
        const newSelectedType = (bulk.selectedType === 'section') ? 'cm' : 'section';
80
        let newSelectedIds;
81
        if (bulk.selectedType === 'section') {
82
            newSelectedIds = this._getCmIdsFromSections(bulk.selection);
83
        } else {
84
            newSelectedIds = this._getSectionIdsFromCms(bulk.selection);
85
        }
86
        // Formats can display only a few activities of the section,
87
        // We need to select on the activities present in the page.
88
        const affectedIds = [];
89
        newSelectedIds.forEach(newId => {
90
            if (this._getSelector(newId, newSelectedType)) {
91
                affectedIds.push(newId);
92
            }
93
        });
94
        this.courseEditor.dispatch('bulkEnable', true);
95
        if (affectedIds.length != 0) {
96
            this._updateBulkSelection(affectedIds, newSelectedType, true);
97
        }
98
    }
99
 
100
    /**
101
     * Select all elements of the current type.
102
     * @param {Boolean} value the wanted selected value
103
     */
104
    selectAll(value) {
105
        const bulk = this.courseEditor.get('bulk');
106
        if (bulk.selectedType == '') {
107
            return;
108
        }
109
        if (!value) {
110
            this.courseEditor.dispatch('bulkEnable', true);
111
            return;
112
        }
113
        const elementType = bulk.selectedType;
114
        this._updateBulkSelectionAll(elementType, value);
115
    }
116
 
117
    /**
118
     * Checks if all selectable elements are selected.
119
     * @returns {Boolean} true if all are selected
120
     */
121
    checkAllSelected() {
122
        const bulk = this.courseEditor.get('bulk');
123
        if (bulk.selectedType == '') {
124
            return false;
125
        }
126
        return this._getContentCheckboxes(bulk.selectedType).every(bulkSelect => {
127
            if (bulkSelect.disabled) {
128
                return true;
129
            }
130
            // Some sections may not be selectale for bulk actions.
131
            if (bulk.selectedType == 'section') {
132
                const section = this.courseEditor.get('section', bulkSelect.dataset.id);
133
                if (!section.bulkeditable) {
134
                    return true;
135
                }
136
            }
137
            return bulk.selection.includes(bulkSelect.dataset.id);
138
        });
139
    }
140
 
141
    /**
142
     * Check if the id is part of the current bulk selection.
143
     * @private
144
     * @param {Number} id
145
     * @param {String} elementType
146
     * @returns {Boolean} if the element is present in the current selection.
147
     */
148
    _isBulkSelected(id, elementType) {
149
        const bulk = this.courseEditor.get('bulk');
150
        if (bulk.selectedType !== elementType) {
151
            return false;
152
        }
153
        return bulk.selection.includes(id);
154
    }
155
 
156
    /**
157
     * Update the current bulk selection removing or adding Ids.
158
     * @private
159
     * @param {Number[]} ids the user selected element id
160
     * @param {String} elementType cm or section
161
     * @param {Boolean} value the wanted selected value
162
     */
163
    _updateBulkSelection(ids, elementType, value) {
164
        let mutation = elementType;
165
        mutation += (value) ? 'Select' : 'Unselect';
166
        this.courseEditor.dispatch(mutation, ids);
167
    }
168
 
169
    /**
170
     * Get all content bulk selector checkboxes of one type (section/cm).
171
     * @private
172
     * @param {String} elementType section or cm
173
     * @returns {HTMLElement[]} an array with all checkboxes
174
     */
175
    _getContentCheckboxes(elementType) {
176
        const selector = (elementType == 'cm') ? this.selectors.BULKCMCHECKBOX : this.selectors.BULKSECTIONCHECKBOX;
177
        const checkboxes = document.querySelectorAll(`${this.selectors.CONTENT} ${selector}`);
178
        // Converting to array because NodeList has less iteration methods.
179
        return [...checkboxes];
180
    }
181
 
182
    /**
183
     * Validate if an element is selectable in the current page.
184
     * @private
185
     * @param {Number} id the user selected element id
186
     * @param {String} elementType cm or section
187
     * @return {Boolean}
188
     */
189
    _isSelectable(id, elementType) {
190
        const bulkSelect = this._getSelector(id, elementType);
191
        if (!bulkSelect || bulkSelect.disabled) {
192
            return false;
193
        }
194
        return true;
195
    }
196
 
197
    /**
198
     * Get as specific element checkbox.
199
     * @private
200
     * @param {Number} id
201
     * @param {String} elementType cm or section
202
     * @returns {HTMLElement|undefined}
203
     */
204
    _getSelector(id, elementType) {
205
        let selector = (elementType == 'cm') ? this.selectors.BULKCMCHECKBOX : this.selectors.BULKSECTIONCHECKBOX;
206
        selector += `[data-id='${id}']`;
207
        return document.querySelector(`${this.selectors.CONTENT} ${selector}`);
208
    }
209
 
210
    /**
211
     * Update the current bulk selection when a user uses shift to select a range.
212
     * @private
213
     * @param {Number} id the user selected element id
214
     * @param {String} elementType cm or section
215
     * @param {Boolean} value the wanted selected value
216
     */
217
    _updateBulkSelectionRange(id, elementType, value) {
218
        const bulk = this.courseEditor.get('bulk');
219
        let lastSelectedId = bulk.selection.at(-1);
220
        if (bulk.selectedType !== elementType || lastSelectedId == id) {
221
            this._updateBulkSelection([id], elementType, value);
222
            return;
223
        }
224
        const affectedIds = [];
225
        let found = 0;
226
        this._getContentCheckboxes(elementType).every(bulkSelect => {
227
            if (bulkSelect.disabled) {
228
                return true;
229
            }
230
            if (elementType == 'section') {
231
                const section = this.courseEditor.get('section', bulkSelect.dataset.id);
232
                if (value && !section?.bulkeditable) {
233
                    return true;
234
                }
235
            }
236
            if (bulkSelect.dataset.id == id || bulkSelect.dataset.id == lastSelectedId) {
237
                found++;
238
            }
239
            if (found == 0) {
240
                return true;
241
            }
242
            affectedIds.push(bulkSelect.dataset.id);
243
            return found != 2;
244
        });
245
        this._updateBulkSelection(affectedIds, elementType, value);
246
    }
247
 
248
    /**
249
     * Select or unselect all cm siblings.
250
     * @private
251
     * @param {Number} cmId the user selected element id
252
     * @param {Boolean} value the wanted selected value
253
     */
254
    _updateBulkCmSiblings(cmId, value) {
255
        const bulk = this.courseEditor.get('bulk');
256
        if (bulk.selectedType === 'section') {
257
            return;
258
        }
259
        const cm = this.courseEditor.get('cm', cmId);
260
        const section = this.courseEditor.get('section', cm.sectionid);
261
        // Formats can display only a few activities of the section,
262
        // We need to select on the activities selectable in the page.
263
        const affectedIds = [];
264
        section.cmlist.forEach(sectionCmId => {
265
            if (this._isSelectable(sectionCmId, 'cm')) {
266
                affectedIds.push(sectionCmId);
267
            }
268
        });
269
        this._updateBulkSelection(affectedIds, 'cm', value);
270
    }
271
 
272
    /**
273
     * Select or unselects al elements of the same type.
274
     * @private
275
     * @param {String} elementType section or cm
276
     * @param {Boolean} value if the elements must be selected or unselected.
277
     */
278
    _updateBulkSelectionAll(elementType, value) {
279
        const affectedIds = [];
280
        this._getContentCheckboxes(elementType).forEach(bulkSelect => {
281
            if (bulkSelect.disabled) {
282
                return;
283
            }
284
            if (elementType == 'section') {
285
                const section = this.courseEditor.get('section', bulkSelect.dataset.id);
286
                if (value && !section?.bulkeditable) {
287
                    return;
288
                }
289
            }
290
            affectedIds.push(bulkSelect.dataset.id);
291
        });
292
        this._updateBulkSelection(affectedIds, elementType, value);
293
    }
294
 
295
    /**
296
     * Get all cm ids from a specific section ids.
297
     * @private
298
     * @param {Number[]} sectionIds
299
     * @returns {Number[]} the cm ids
300
     */
301
    _getCmIdsFromSections(sectionIds) {
302
        const result = [];
303
        sectionIds.forEach(sectionId => {
304
            const section = this.courseEditor.get('section', sectionId);
305
            result.push(...section.cmlist);
306
        });
307
        return result;
308
    }
309
 
310
    /**
311
     * Get all section ids containing a specific cm ids.
312
     * @private
313
     * @param {Number[]} cmIds
314
     * @returns {Number[]} the section ids
315
     */
316
    _getSectionIdsFromCms(cmIds) {
317
        const result = new Set();
318
        cmIds.forEach(cmId => {
319
            const cm = this.courseEditor.get('cm', cmId);
320
            if (cm.sectionnumber == 0) {
321
                return;
322
            }
323
            result.add(cm.sectionid);
324
        });
325
        return [...result];
326
    }
327
}
328
 
329
/**
330
 * Process a bulk selection toggle action.
331
 * @method
332
 * @param {CourseEditor} courseEditor
333
 * @param {HTMLElement} target the action element
334
 * @param {Event} event
335
 * @param {String} elementType cm or section
336
 */
337
export const toggleBulkSelectionAction = function(courseEditor, target, event, elementType) {
338
    const id = target.dataset.id;
339
    if (!id) {
340
        return;
341
    }
342
    // When the action cames from a form element (checkbox) we should not preventDefault.
343
    // If we do it the changechecker module will execute the state change twice.
344
    if (target.dataset.preventDefault) {
345
        event.preventDefault();
346
    }
347
    // Using shift or alt key can produce text selection.
348
    document.getSelection().removeAllRanges();
349
 
350
    const bulkSelector = new BulkSelector(courseEditor);
351
    bulkSelector.processNewSelection(
352
        id,
353
        elementType,
354
        {
355
            range: event.shiftKey,
356
            all: event.altKey,
357
        }
358
    );
359
};
360
 
361
/**
362
 * Switch the current bulk selection.
363
 * @method
364
 * @param {CourseEditor} courseEditor
365
 */
366
export const switchBulkSelection = function(courseEditor) {
367
    const bulkSelector = new BulkSelector(courseEditor);
368
    bulkSelector.switchCurrentSelection();
369
};
370
 
371
/**
372
 * Select/unselect all element of the selected type.
373
 * @method
374
 * @param {CourseEditor} courseEditor
375
 * @param {Boolean} value if the elements must be selected or unselected.
376
 */
377
export const selectAllBulk = function(courseEditor, value) {
378
    const bulkSelector = new BulkSelector(courseEditor);
379
    bulkSelector.selectAll(value);
380
};
381
 
382
/**
383
 * Check if all possible elements are selected.
384
 * @method
385
 * @param {CourseEditor} courseEditor
386
 * @return {Boolean} if all elements of the current type are selected.
387
 */
388
export const checkAllBulkSelected = function(courseEditor) {
389
    const bulkSelector = new BulkSelector(courseEditor);
390
    return bulkSelector.checkAllSelected();
391
};