AutorÃa | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** The bulk editor tools bar.** @module core_courseformat/local/content/bulkedittools* @class core_courseformat/local/content/bulkedittools* @copyright 2023 Ferran Recio <ferran@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/import {BaseComponent} from 'core/reactive';import {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';import {getString} from 'core/str';import Pending from 'core/pending';import {prefetchStrings} from 'core/prefetch';import {selectAllBulk,switchBulkSelection,checkAllBulkSelected} from 'core_courseformat/local/content/actions/bulkselection';import Notification from 'core/notification';// Load global strings.prefetchStrings('core_courseformat',['bulkselection']);export default class Component extends BaseComponent {/*** Constructor hook.*/create() {// Optional component name for debugging.this.name = 'bulk_editor_tools';// Default query selectors.this.selectors = {ACTIONS: `[data-for="bulkaction"]`,ACTIONTOOL: `[data-for="bulkactions"] li`,CANCEL: `[data-for="bulkcancel"]`,COUNT: `[data-for='bulkcount']`,SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,SELECTALL: `[data-for="selectall"]`,BULKBTN: `[data-for="enableBulk"]`,};// Most classes will be loaded later by DndCmItem.this.classes = {HIDE: 'd-none',DISABLED: 'disabled',};}/*** Static method to create a component instance from the mustache template.** @param {string} target optional altentative DOM main element CSS selector* @param {object} selectors optional css selector overrides* @return {Component}*/static init(target, selectors) {return new this({element: document.querySelector(target),reactive: getCurrentCourseEditor(),selectors});}/*** Initial state ready method.*/stateReady() {const cancelBtn = this.getElement(this.selectors.CANCEL);if (cancelBtn) {this.addEventListener(cancelBtn, 'click', this._cancelBulk);}const selectAll = this.getElement(this.selectors.SELECTALL);if (selectAll) {this.addEventListener(selectAll, 'click', this._selectAllClick);}}/*** Component watchers.** @returns {Array} of watchers*/getWatchers() {return [{watch: `bulk.enabled:updated`, handler: this._refreshEnabled},{watch: `bulk:updated`, handler: this._refreshTools},];}/*** Hide and show the bulk edit tools.** @param {object} param* @param {Object} param.element details the update details (state.bulk in this case).*/_refreshEnabled({element}) {this._updatePageTitle(element.enabled).catch(Notification.exception);if (element.enabled) {enableStickyFooter();} else {disableStickyFooter();}}/*** Refresh the tools depending on the current selection.** @param {object} param the state watcher information* @param {Object} param.state the full state data.* @param {Object} param.element the affected element (bulk in this case).*/_refreshTools(param) {this._refreshSelectCount(param);this._refreshSelectAll(param);this._refreshActions(param);}/*** Refresh the selection count.** @param {object} param* @param {Object} param.element the affected element (bulk in this case).*/async _refreshSelectCount({element: bulk}) {const stringName = (bulk.selection.length > 1) ? 'bulkselection_plural' : 'bulkselection';const selectedCount = await getString(stringName, 'core_courseformat', bulk.selection.length);const selectedElement = this.getElement(this.selectors.COUNT);if (selectedElement) {selectedElement.innerHTML = selectedCount;}}/*** Refresh the select all element.** @param {object} param* @param {Object} param.element the affected element (bulk in this case).*/_refreshSelectAll({element: bulk}) {const selectall = this.getElement(this.selectors.SELECTALL);if (!selectall) {return;}selectall.disabled = (bulk.selectedType === '');// The changechecker module can prevent the checkbox form changing it's value.// To avoid that we leave the sniffer to act before changing the value.const pending = new Pending(`courseformat/bulktools:refreshSelectAll`);setTimeout(() => {selectall.checked = checkAllBulkSelected(this.reactive);pending.resolve();},100);}/*** Refresh the visible action buttons depending on the selection type.** @param {object} param* @param {Object} param.element the affected element (bulk in this case).*/_refreshActions({element: bulk}) {// By default, we show the cm options.const displayType = (bulk.selectedType == 'section') ? 'section' : 'cm';const enabled = (bulk.selectedType !== '');this.getElements(this.selectors.ACTIONS).forEach(action => {action.classList.toggle(this.classes.DISABLED, !enabled);action.tabIndex = (enabled) ? 0 : -1;const actionTool = action.closest(this.selectors.ACTIONTOOL);const isHidden = (action.dataset.bulk != displayType);actionTool?.classList.toggle(this.classes.HIDE, isHidden);});}/*** Cancel bulk handler.*/_cancelBulk() {const pending = new Pending(`courseformat/content:bulktoggle_off`);this.reactive.dispatch('bulkEnable', false);// Wait for a while and focus on enable bulk button.setTimeout(() => {document.querySelector(this.selectors.BULKBTN)?.focus();pending.resolve();}, 150);}/*** Handle special select all cases.* @param {Event} event*/_selectAllClick(event) {event.preventDefault();if (event.altKey) {switchBulkSelection(this.reactive);return;}if (checkAllBulkSelected(this.reactive)) {this._handleUnselectAll();return;}selectAllBulk(this.reactive, true);}/*** Process unselect all elements.*/_handleUnselectAll() {const pending = new Pending(`courseformat/content:bulktUnselectAll`);selectAllBulk(this.reactive, false);// Wait for a while and focus on the first checkbox.setTimeout(() => {document.querySelector(this.selectors.SELECTABLE)?.focus();pending.resolve();}, 150);}/*** Updates the <title> attribute of the page whenever bulk editing is toggled.** This helps users, especially screen reader users, to understand the current state of the course homepage.** @param {Boolean} enabled True when bulk editing is turned on. False, otherwise.* @returns {Promise<void>}* @private*/async _updatePageTitle(enabled) {const enableBulk = document.querySelector(this.selectors.BULKBTN);let params, bulkEditTitle, editingTitle;if (enableBulk.dataset.sectiontitle) {// Section editing mode.params = {course: enableBulk.dataset.coursename,sectionname: enableBulk.dataset.sectionname,sectiontitle: enableBulk.dataset.sectiontitle,};bulkEditTitle = await getString('coursesectiontitlebulkediting', 'moodle', params);editingTitle = await getString('coursesectiontitleediting', 'moodle', params);} else {// Whole course editing mode.params = {course: enableBulk.dataset.coursename};bulkEditTitle = await getString('coursetitlebulkediting', 'moodle', params);editingTitle = await getString('coursetitleediting', 'moodle', params);}const pageTitle = document.title;if (enabled) {// Use bulk editing string for the page title.// At this point, the current page title should be the normal editing title.// So replace the normal editing title with the bulk editing title.document.title = pageTitle.replace(editingTitle, bulkEditTitle);} else {// Use the normal editing string for the page title.// At this point, the current page title should be the bulk editing title.// So replace the bulk editing title with the normal editing title.document.title = pageTitle.replace(bulkEditTitle, editingTitle);}}}