course/format/amd/src/activitychooser.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/>.

/**
 * A type of dialogue used as for choosing modules in a course.
 *
 * @module     core_courseformat/activitychooser
 * @copyright  2020 Mathew May <mathew.solutions>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

import * as ChooserDialogue from 'core_courseformat/local/activitychooser/dialogue';
import CustomEvents from 'core/custom_interaction_events';
import Log from 'core/log';
import Pending from 'core/pending';
import * as Repository from 'core_courseformat/local/activitychooser/repository';
import selectors from 'core_courseformat/local/activitychooser/selectors';

let initialized = false;

/**
 * Set up the activity chooser.
 *
 * @method init
 * @param {Number} courseId Course ID to use later on in fetchModules()
 * @param {Object|null} chooserConfig Any PHP config settings that we may need to reference
 */
export const init = (courseId, chooserConfig = null) => {
    const pendingPromise = new Pending();

    // TODO: Remove the chooserConfig in Moodle 6.0 (MDL-85655)
    if (chooserConfig?.tabmode !== undefined) {
        window.console.warn('The tabmode config option has been deprecated and will be ignored.');
    }

    registerListenerEvents(courseId);

    pendingPromise.resolve();
};

/**
 * Once a selection has been made make the modal & module information and pass it along
 *
 * @method registerListenerEvents
 * @param {Number} courseId
 */
const registerListenerEvents = (courseId) => {
    if (initialized) {
        return;
    }
    initialized = true;

    const eventsToHandle = [
        'click',
        CustomEvents.events.activate,
        CustomEvents.events.keyboardActivate
    ];

    CustomEvents.define(document, eventsToHandle);

    // Display module chooser event listeners.
    eventsToHandle.forEach((eventToHandle) => {
        document.addEventListener(eventToHandle, async(event) => {
            if (!event.target.closest(selectors.elements.sectionmodchooser)) {
                return;
            }
            const position = getCoursePositionFromTarget(event.target);

            const footerDataPromise = Repository.getModalFooterData(courseId, position.sectionNum);

            let modulesDataPromise;
            if (position.sectionId && position.sectionId !== '') {
                modulesDataPromise = Repository.getSectionModulesData(
                    courseId,
                    position.sectionId,
                    position.sectionReturnNum,
                    position.beforeMod,
                );
            } else {
                // Todo remove this else in Moodle 6.0 (MDL-86310)
                Log.debug(
                    'Having only the section number attribute in the activity chooser is deprecated. ' +
                    'Please add the data-section-id attribute.'
                );
                modulesDataPromise = Repository.getModulesData(
                    courseId,
                    position.sectionNum,
                    position.sectionReturnNum,
                    position.beforeMod,
                );
            }

            ChooserDialogue.displayActivityChooserModal(footerDataPromise, modulesDataPromise);
        });
    });
};

/**
 * Return the course position of a target add activity element.
 *
 * @param {HTMLElement} target The target element.
 * @return {Object} The course position of the target.
 * @property {Number} sectionNum The section number.
 * @property {Number|null} sectionId The section id.
 * @property {Number|null} sectionReturnNum The section return number.
 * @property {Number|null} sectionReturnId The section return id.
 * @property {Number|null} beforeMod The ID of the cm to add the modules before.
 */
function getCoursePositionFromTarget(target) {
    let caller;
    let sectionNum = null;
    let sectionId = null;
    // We need to know who called this.
    // Standard courses use the ID in the main section info.
    const sectionDiv = target.closest(selectors.elements.section);
    // Front page courses need some special handling.
    const button = target.closest(selectors.elements.sectionmodchooser);

    // If we don't have a section number use the fallback ID.
    // We always want the sectionDiv caller first as it keeps track of section number's after DnD changes.
    // The button attribute is always just a fallback for us as the section div is not always available.
    // A YUI change could be done maybe to only update the button attribute but we are going for minimal change here.
    if (
        sectionDiv !== null
        && (sectionDiv.hasAttribute('data-number') || sectionDiv.hasAttribute('data-id'))
    ) {
        // We check for attributes just in case of outdated contrib course formats.
        caller = sectionDiv;
        sectionNum = sectionDiv.getAttribute('data-number');
        sectionId = sectionDiv.getAttribute('data-id');
    } else {
        caller = button;
        if (caller.hasAttribute('data-sectionid')) {
            window.console.warn(
                'The data-sectionid attribute has been deprecated. ' +
                'Please update your code to use data-section-id passing the real section ID instead.'
            );
            caller.setAttribute('data-sectionnum', caller.dataset.sectionid);
        }
        sectionNum = caller.dataset.sectionnum;
        sectionId = caller.getAttribute('data-section-id');
    }
    return {
        sectionNum,
        sectionId,
        // The old data attribute for the section return number was data-sectionreturn.
        sectionReturnNum: caller.dataset?.sectionreturnnum ?? caller.dataset?.sectionreturn ?? null,
        sectionReturnId: caller.dataset?.sectionreturnid ?? null,
        beforeMod: caller.dataset?.beforemod ?? null,
    };
}