course/amd/src/local/overview/overviewpage.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Controls the fragment overview loadings.
  17. *
  18. * @module core_course/local/overview/overviewpage
  19. * @copyright 2025 Ferran Recio <ferran@moodle.com>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. import CourseContent from 'core_courseformat/local/content';
  23. import {eventTypes as collapsableSectionEventTypes} from 'core/local/collapsable_section/events';
  24. import Fragment from 'core/fragment';
  25. import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
  26. import Pending from 'core/pending';
  27. import Templates from 'core/templates';
  28. /**
  29. * Initialize the overview page.
  30. *
  31. * @param {String} selector The selector where the overview page is located.
  32. */
  33. export const init = async(selector) => {
  34. const pageElement = document.querySelector(selector);
  35. if (!pageElement) {
  36. throw new Error('No elements found with the selector: ' + selector);
  37. }
  38. pageElement.addEventListener(
  39. collapsableSectionEventTypes.shown,
  40. event => {
  41. const fragmentElement = getFragmentContainer(event.target);
  42. if (!fragmentElement) {
  43. return;
  44. }
  45. loadFragmentContent(fragmentElement);
  46. }
  47. );
  48. // The overview page is considered an alternative course view page so it must
  49. // include the course content component to capture any possible action. For example,
  50. // capturing manual completion toggles.
  51. return new CourseContent({
  52. element: pageElement,
  53. reactive: getCurrentCourseEditor(),
  54. });
  55. };
  56. /**
  57. * Load the fragment content.
  58. *
  59. * @private
  60. * @param {HTMLElement} element The element where the fragment content will be loaded.
  61. */
  62. const loadFragmentContent = (element) => {
  63. if (element.dataset.loaded) {
  64. return;
  65. }
  66. const pendingReload = new Pending(`course_overviewtable_${element.dataset.modname}`);
  67. const promise = Fragment.loadFragment(
  68. 'core_course',
  69. 'course_overview',
  70. element.dataset.contextid,
  71. {
  72. courseid: element.dataset.courseid,
  73. modname: element.dataset.modname,
  74. }
  75. );
  76. promise.then(async(html, js) => {
  77. Templates.runTemplateJS(js);
  78. element.innerHTML = html;
  79. // Templates.replaceNode(element, html, js);
  80. element.dataset.loaded = true;
  81. pendingReload.resolve();
  82. return true;
  83. }).catch(() => {
  84. pendingReload.resolve();
  85. });
  86. };
  87. /**
  88. * Get the fragment container.
  89. *
  90. * @private
  91. * @param {HTMLElement} element The element where the fragment container is located.
  92. * @return {HTMLElement|null} The fragment container.
  93. */
  94. const getFragmentContainer = (element) => {
  95. const result = element.querySelector('[data-region="loading-icon-container"]');
  96. if (!result) {
  97. return null;
  98. }
  99. if (!result.dataset.contextid || !result.dataset.courseid || !result.dataset.modname) {
  100. throw new Error('The element is missing required data attributes.');
  101. }
  102. return result;
  103. };