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 category list component.** The category list is a drop target, so that a category may be dropped at the top or bottom of the list.** @module qbank_managecategories/categorylist* @class qbank_managecategories/categorylist*/import {BaseComponent, DragDrop} from 'core/reactive';import Templates from 'core/templates';import {getString} from 'core/str';import {categorymanager} from 'qbank_managecategories/categorymanager';export default class extends BaseComponent {create(descriptor) {this.name = descriptor.element.id;this.selectors = {CATEGORY_LIST: '.qbank_managecategories-categorylist',CATEGORY_ITEM: '.qbank_managecategories-item[data-categoryid]',CATEGORY_CONTENTS: '.qbank_managecategories-item > .container',CATEGORY_DETAILS: '.qbank_managecategories-details',CATEGORY_NO_DRAGHANDLE: '.qbank_managecategories-item[data-categoryid]:not(.draghandle)',CATEGORY_ID: id => `#category-${id}`,};this.classes = {DROP_TARGET_BEFORE: 'qbank_managecategories-droptarget-before',DROP_TARGET: 'qbank_managecategories-droptarget',NO_BOTTOM_PADDING: 'pb-0',};this.ids = {CATEGORY: id => `category-${id}`,};}stateReady() {this.dragdrop = new DragDrop(this);}destroy() {// The draggable element must be unregistered.if (this.dragdrop !== undefined) {this.dragdrop.unregister();this.dragdrop = undefined;}}/*** Static method to create a component instance.** @param {string} target the DOM main element or its ID* @param {object} selectors optional css selector overrides* @return {Component}*/static init(target, selectors) {return new this({element: document.querySelector(target),selectors,reactive: categorymanager,});}validateDropData() {return true;}/*** Highlight the border of the list where the category will be moved.** If dropping at the top of the list, highlight the top border.* If dropping at the bottom, highlight the bottom border.** @param {Object} dropData* @param {Event} event*/showDropZone(dropData, event) {const dropTarget = this.getElement();if (dropTarget.closest(this.selectors.CATEGORY_ID(dropData.id))) {// Can't drop onto its own child.return false;}if (this.getInsertBefore(event, dropTarget)) {dropTarget.classList.add(this.classes.DROP_TARGET_BEFORE);dropTarget.classList.remove(this.classes.DROP_TARGET);} else {dropTarget.classList.add(this.classes.DROP_TARGET);dropTarget.classList.remove(this.classes.DROP_TARGET_BEFORE);}return true;}/*** Remove highlighting.** @param {Object} dropData* @param {Event} event*/hideDropZone(dropData, event) {const dropTarget = event.target.closest(this.selectors.CATEGORY_LIST);dropTarget.classList.remove(this.classes.DROP_TARGET_BEFORE);dropTarget.classList.remove(this.classes.DROP_TARGET);}/*** Determine whether we're dragging over the top or bottom half of the list.** @param {Event} event* @param {Element} dropTarget* @return {boolean}*/getInsertBefore(event, dropTarget) {// Get the current mouse position within the drop targetconst mouseY = event.clientY - dropTarget.getBoundingClientRect().top;// Get the height of the drop targetconst targetHeight = dropTarget.clientHeight;// Check if the mouse is over the top half of the drop targetreturn mouseY < targetHeight / 2;}/*** Find the new position of the dropped category, and trigger the move.** @param {Object} dropData* @param {Event} event*/drop(dropData, event) {const dropTarget = event.target.closest(this.selectors.CATEGORY_LIST);if (!dropTarget) {return;}if (dropTarget.closest(this.selectors.CATEGORY_ID(dropData.id))) {// Can't drop onto your own child.return;}const source = document.getElementById(this.ids.CATEGORY(dropData.id));if (!source) {return;}const targetParentId = dropTarget.dataset.categoryid;let precedingSibling;if (this.getInsertBefore(event, dropTarget)) {// Dropped at the top of the list.precedingSibling = null;} else {// Dropped at the bottom of the list.precedingSibling = dropTarget.lastElementChild;}// Insert the category after the target categorycategorymanager.moveCategory(dropData.id, targetParentId, precedingSibling?.dataset.categoryid);}/*** Watch for categories moving to a new parent.** @return {Array} A list of watchers.*/getWatchers() {return [// Watch for this category having its child count updated.{watch: `categoryLists[${this.element.dataset.categoryid}].childCount:updated`, handler: this.checkEmptyList},// Watch for any new category being created.{watch: `categories:created`, handler: this.addCategory},];}/*** If this list is now empty, remove it.** @param {Object} args* @param {Object} args.element The categoryList state element.*/async checkEmptyList({element}) {if (element.childCount === 0) {// Display a new child drop zone.const categoryItem = this.getElement().closest(this.selectors.CATEGORY_ITEM);const {html, js} = await Templates.renderForPromise('qbank_managecategories/newchild',{categoryid: this.getElement().dataset.categoryid,tooltip: getString('newchild', 'qbank_managecategories', categoryItem.dataset.categoryname)});const activityNameArea = categoryItem.querySelector(this.selectors.CATEGORY_DETAILS);await Templates.appendNodeContents(activityNameArea, html, js);// Reinstate padding on the parent element.this.element.closest(this.selectors.CATEGORY_CONTENTS).classList.remove(this.classes.NO_BOTTOM_PADDING);// Remove this list.this.remove();}}/*** If a newly-created category has this list's category as its parent, add it to this list.** @param {Object} args* @param {Object} args.element* @return {Promise<void>}*/async addCategory({element}) {if (element.parent !== this.getElement().dataset.categoryid) {return; // Not for me.}const {html, js} = await Templates.renderForPromise('qbank_managecategories/category', element.templatecontext);Templates.appendNodeContents(this.getElement(), html, js);// If one of the children has no draghandle, it should do now it has a sibling.const noDragHandle = this.getElement(this.selectors.CATEGORY_NO_DRAGHANDLE);if (noDragHandle) {this.reactive.dispatch('showDragHandle', noDragHandle.dataset.categoryid);}}}