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
 * The bulk editor tools bar.
18
 *
19
 * @module     core_courseformat/local/content/bulkedittools
20
 * @class      core_courseformat/local/content/bulkedittools
21
 * @copyright  2023 Ferran Recio <ferran@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
import {BaseComponent} from 'core/reactive';
26
import {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';
27
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
28
import {getString} from 'core/str';
29
import Pending from 'core/pending';
30
import {prefetchStrings} from 'core/prefetch';
31
import {
32
    selectAllBulk,
33
    switchBulkSelection,
34
    checkAllBulkSelected
35
} from 'core_courseformat/local/content/actions/bulkselection';
36
import Notification from 'core/notification';
37
 
38
// Load global strings.
39
prefetchStrings(
40
    'core_courseformat',
41
    ['bulkselection']
42
);
43
 
44
export default class Component extends BaseComponent {
45
 
46
    /**
47
     * Constructor hook.
48
     */
49
    create() {
50
        // Optional component name for debugging.
51
        this.name = 'bulk_editor_tools';
52
        // Default query selectors.
53
        this.selectors = {
54
            ACTIONS: `[data-for="bulkaction"]`,
55
            ACTIONTOOL: `[data-for="bulkactions"] li`,
56
            CANCEL: `[data-for="bulkcancel"]`,
57
            COUNT: `[data-for='bulkcount']`,
58
            SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,
59
            SELECTALL: `[data-for="selectall"]`,
60
            BULKBTN: `[data-for="enableBulk"]`,
61
        };
62
        // Most classes will be loaded later by DndCmItem.
63
        this.classes = {
64
            HIDE: 'd-none',
65
            DISABLED: 'disabled',
66
        };
67
    }
68
 
69
    /**
70
     * Static method to create a component instance from the mustache template.
71
     *
72
     * @param {string} target optional altentative DOM main element CSS selector
73
     * @param {object} selectors optional css selector overrides
74
     * @return {Component}
75
     */
76
    static init(target, selectors) {
77
        return new this({
78
            element: document.querySelector(target),
79
            reactive: getCurrentCourseEditor(),
80
            selectors
81
        });
82
    }
83
 
84
    /**
85
     * Initial state ready method.
86
     */
87
    stateReady() {
88
        const cancelBtn = this.getElement(this.selectors.CANCEL);
89
        if (cancelBtn) {
90
            this.addEventListener(cancelBtn, 'click', this._cancelBulk);
91
        }
92
        const selectAll = this.getElement(this.selectors.SELECTALL);
93
        if (selectAll) {
94
            this.addEventListener(selectAll, 'click', this._selectAllClick);
95
        }
96
    }
97
 
98
    /**
99
     * Component watchers.
100
     *
101
     * @returns {Array} of watchers
102
     */
103
    getWatchers() {
104
        return [
105
            {watch: `bulk.enabled:updated`, handler: this._refreshEnabled},
106
            {watch: `bulk:updated`, handler: this._refreshTools},
107
        ];
108
    }
109
 
110
    /**
111
     * Hide and show the bulk edit tools.
112
     *
113
     * @param {object} param
114
     * @param {Object} param.element details the update details (state.bulk in this case).
115
     */
116
    _refreshEnabled({element}) {
117
        this._updatePageTitle(element.enabled).catch(Notification.exception);
118
 
119
        if (element.enabled) {
120
            enableStickyFooter();
121
        } else {
122
            disableStickyFooter();
123
        }
124
    }
125
 
126
    /**
127
     * Refresh the tools depending on the current selection.
128
     *
129
     * @param {object} param the state watcher information
130
     * @param {Object} param.state the full state data.
131
     * @param {Object} param.element the affected element (bulk in this case).
132
     */
133
    _refreshTools(param) {
134
        this._refreshSelectCount(param);
135
        this._refreshSelectAll(param);
136
        this._refreshActions(param);
137
    }
138
 
139
    /**
140
     * Refresh the selection count.
141
     *
142
     * @param {object} param
143
     * @param {Object} param.element the affected element (bulk in this case).
144
     */
145
    async _refreshSelectCount({element: bulk}) {
146
        const stringName = (bulk.selection.length > 1) ? 'bulkselection_plural' : 'bulkselection';
147
        const selectedCount = await getString(stringName, 'core_courseformat', bulk.selection.length);
148
        const selectedElement = this.getElement(this.selectors.COUNT);
149
        if (selectedElement) {
150
            selectedElement.innerHTML = selectedCount;
151
        }
152
    }
153
 
154
    /**
155
     * Refresh the select all element.
156
     *
157
     * @param {object} param
158
     * @param {Object} param.element the affected element (bulk in this case).
159
     */
160
    _refreshSelectAll({element: bulk}) {
161
        const selectall = this.getElement(this.selectors.SELECTALL);
162
        if (!selectall) {
163
            return;
164
        }
165
        selectall.disabled = (bulk.selectedType === '');
166
        // The changechecker module can prevent the checkbox form changing it's value.
167
        // To avoid that we leave the sniffer to act before changing the value.
168
        const pending = new Pending(`courseformat/bulktools:refreshSelectAll`);
169
        setTimeout(
170
            () => {
171
                selectall.checked = checkAllBulkSelected(this.reactive);
172
                pending.resolve();
173
            },
174
            100
175
        );
176
    }
177
 
178
    /**
179
     * Refresh the visible action buttons depending on the selection type.
180
     *
181
     * @param {object} param
182
     * @param {Object} param.element the affected element (bulk in this case).
183
     */
184
    _refreshActions({element: bulk}) {
185
        // By default, we show the cm options.
186
        const displayType = (bulk.selectedType == 'section') ? 'section' : 'cm';
187
        const enabled = (bulk.selectedType !== '');
188
        this.getElements(this.selectors.ACTIONS).forEach(action => {
189
            action.classList.toggle(this.classes.DISABLED, !enabled);
190
            action.tabIndex = (enabled) ? 0 : -1;
191
 
192
            const actionTool = action.closest(this.selectors.ACTIONTOOL);
193
            const isHidden = (action.dataset.bulk != displayType);
194
            actionTool?.classList.toggle(this.classes.HIDE, isHidden);
195
        });
196
    }
197
 
198
    /**
199
     * Cancel bulk handler.
200
     */
201
    _cancelBulk() {
202
        const pending = new Pending(`courseformat/content:bulktoggle_off`);
203
        this.reactive.dispatch('bulkEnable', false);
204
        // Wait for a while and focus on enable bulk button.
205
        setTimeout(() => {
206
            document.querySelector(this.selectors.BULKBTN)?.focus();
207
            pending.resolve();
208
        }, 150);
209
    }
210
 
211
    /**
212
     * Handle special select all cases.
213
     * @param {Event} event
214
     */
215
    _selectAllClick(event) {
216
        event.preventDefault();
217
        if (event.altKey) {
218
            switchBulkSelection(this.reactive);
219
            return;
220
        }
221
        if (checkAllBulkSelected(this.reactive)) {
222
            this._handleUnselectAll();
223
            return;
224
        }
225
        selectAllBulk(this.reactive, true);
226
    }
227
 
228
    /**
229
     * Process unselect all elements.
230
     */
231
    _handleUnselectAll() {
232
        const pending = new Pending(`courseformat/content:bulktUnselectAll`);
233
        selectAllBulk(this.reactive, false);
234
        // Wait for a while and focus on the first checkbox.
235
        setTimeout(() => {
236
            document.querySelector(this.selectors.SELECTABLE)?.focus();
237
            pending.resolve();
238
        }, 150);
239
    }
240
 
241
    /**
242
     * Updates the <title> attribute of the page whenever bulk editing is toggled.
243
     *
244
     * This helps users, especially screen reader users, to understand the current state of the course homepage.
245
     *
246
     * @param {Boolean} enabled True when bulk editing is turned on. False, otherwise.
247
     * @returns {Promise<void>}
248
     * @private
249
     */
250
    async _updatePageTitle(enabled) {
251
        const enableBulk = document.querySelector(this.selectors.BULKBTN);
252
        let params, bulkEditTitle, editingTitle;
253
        if (enableBulk.dataset.sectiontitle) {
254
            // Section editing mode.
255
            params = {
256
                course: enableBulk.dataset.coursename,
257
                sectionname: enableBulk.dataset.sectionname,
258
                sectiontitle: enableBulk.dataset.sectiontitle,
259
            };
260
            bulkEditTitle = await getString('coursesectiontitlebulkediting', 'moodle', params);
261
            editingTitle = await getString('coursesectiontitleediting', 'moodle', params);
262
        } else {
263
            // Whole course editing mode.
264
            params = {
265
                course: enableBulk.dataset.coursename
266
            };
267
            bulkEditTitle = await getString('coursetitlebulkediting', 'moodle', params);
268
            editingTitle = await getString('coursetitleediting', 'moodle', params);
269
        }
270
        const pageTitle = document.title;
271
        if (enabled) {
272
            // Use bulk editing string for the page title.
273
            // At this point, the current page title should be the normal editing title.
274
            // So replace the normal editing title with the bulk editing title.
275
            document.title = pageTitle.replace(editingTitle, bulkEditTitle);
276
        } else {
277
            // Use the normal editing string for the page title.
278
            // At this point, the current page title should be the bulk editing title.
279
            // So replace the bulk editing title with the normal editing title.
280
            document.title = pageTitle.replace(bulkEditTitle, editingTitle);
281
        }
282
    }
283
}