question/bank/managecategories/amd/src/newchild.js

// 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 newchild component.
 *
 * This is a drop target for moving a category to an as-yet-nonexistant child list under another category.
 *
 * @module     qbank_managecategories/newchild
 * @class      qbank_managecategories/newchild
 */

import {BaseComponent, DragDrop} from 'core/reactive';
import $ from 'jquery';
import {categorymanager} from 'qbank_managecategories/categorymanager';

export default class extends BaseComponent {
    create(descriptor) {
        this.name = descriptor.element.id;
        this.selectors = {
            NEW_CHILD: '.qbank_managecategories-newchild',
            CATEGORY_ID: id => `#category-${id}`
        };
        this.classes = {
            DROP_TARGET: 'qbank_managecategories-droptarget',
        };
        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 form the mustache template.
     *
     * @param {string} target the DOM main element or its ID
     * @param {object} selectors optional css selector overrides
     * @return {Component}
     */
    static init(target, selectors) {
        const targetElement = document.querySelector(target);
        return new this({
            element: targetElement,
            selectors,
            reactive: categorymanager,
        });
    }

    /**
     * Cannot drop a category as a new child of its own descendant.
     *
     * @param {Object} dropData
     * @return {boolean}
     */
    validateDropData(dropData) {
        if (this.getElement().closest(this.selectors.CATEGORY_ID(dropData.id))) {
            return false;
        }
        return true;
    }

    showDropZone(dropData, event) {
        const dropTarget = event.target.closest(this.selectors.NEW_CHILD);
        dropTarget.classList.add(this.classes.DROP_TARGET);
        $(dropTarget).tooltip('show');
    }

    hideDropZone(dropData, event) {
        const dropTarget = event.target.closest(this.selectors.NEW_CHILD);
        dropTarget.classList.remove(this.classes.DROP_TARGET);
        $(dropTarget).tooltip('hide');
    }

    drop(dropData, event) {
        const dropTarget = event.target.closest(this.selectors.NEW_CHILD);

        if (!dropTarget) {
            return;
        }

        const source = document.getElementById(this.ids.CATEGORY(dropData.id));

        if (!source) {
            return;
        }

        const targetParentId = dropTarget.dataset.parent;

        // Insert the category as the first child of the new parent.
        categorymanager.moveCategory(dropData.id, targetParentId);
    }

    /**
     * Watch for categories moving to a new parent.
     *
     * @return {Array} A list of watchers.
     */
    getWatchers() {
        return [
            // Watch for any category having its parent changed.
            {watch: `categories.parent:updated`, handler: this.checkNewChild},
        ];
    }

    /**
     * If an element now has this category as the parent, remove this new child target.
     *
     * @param {Object} args
     * @param {Element} args.element
     */
    checkNewChild({element}) {
        if (element.parent === parseInt(this.element.dataset.parent)) {
            this.remove();
        }
    }
}