course/amd/src/local/overview/overviewpage.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/>.

/**
 * Controls the fragment overview loadings.
 *
 * @module     core_course/local/overview/overviewpage
 * @copyright  2025 Ferran Recio <ferran@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

import CourseContent from 'core_courseformat/local/content';
import {eventTypes as collapsableSectionEventTypes} from 'core/local/collapsable_section/events';
import Fragment from 'core/fragment';
import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
import Pending from 'core/pending';
import Templates from 'core/templates';

/**
 * Initialize the overview page.
 *
 * @param {String} selector The selector where the overview page is located.
 */
export const init = async(selector) => {
    const pageElement = document.querySelector(selector);
    if (!pageElement) {
        throw new Error('No elements found with the selector: ' + selector);
    }

    pageElement.addEventListener(
        collapsableSectionEventTypes.shown,
        event => {
            const fragmentElement = getFragmentContainer(event.target);
            if (!fragmentElement) {
                return;
            }
            loadFragmentContent(fragmentElement);
        }
    );

    // The overview page is considered an alternative course view page so it must
    // include the course content component to capture any possible action. For example,
    // capturing manual completion toggles.
    return new CourseContent({
        element: pageElement,
        reactive: getCurrentCourseEditor(),
    });
};

/**
 * Load the fragment content.
 *
 * @private
 * @param {HTMLElement} element The element where the fragment content will be loaded.
 */
const loadFragmentContent = (element) => {
    if (element.dataset.loaded) {
        return;
    }

    const pendingReload = new Pending(`course_overviewtable_${element.dataset.modname}`);

    const promise = Fragment.loadFragment(
        'core_course',
        'course_overview',
        element.dataset.contextid,
        {
            courseid: element.dataset.courseid,
            modname: element.dataset.modname,
        }
    );

    promise.then(async(html, js) => {
        Templates.runTemplateJS(js);
        element.innerHTML = html;
        // Templates.replaceNode(element, html, js);
        element.dataset.loaded = true;
        pendingReload.resolve();
        return true;
    }).catch(() => {
        pendingReload.resolve();
    });
};

/**
 * Get the fragment container.
 *
 * @private
 * @param {HTMLElement} element The element where the fragment container is located.
 * @return {HTMLElement|null} The fragment container.
 */
const getFragmentContainer = (element) => {
    const result = element.querySelector('[data-region="loading-icon-container"]');
    if (!result) {
        return null;
    }
    if (!result.dataset.contextid || !result.dataset.courseid || !result.dataset.modname) {
        throw new Error('The element is missing required data attributes.');
    }
    return result;
};