{"version":3,"file":"courseindex.min.js","sources":["https:\/\/www.alsg.org\/home\/course\/format\/amd\/src\/local\/courseindex\/courseindex.js"],"sourcesContent":["\/\/ This file is part of Moodle - http:\/\/moodle.org\/\n\/\/\n\/\/ Moodle is free software: you can redistribute it and\/or modify\n\/\/ it under the terms of the GNU General Public License as published by\n\/\/ the Free Software Foundation, either version 3 of the License, or\n\/\/ (at your option) any later version.\n\/\/\n\/\/ Moodle is distributed in the hope that it will be useful,\n\/\/ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\/\/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\/\/ GNU General Public License for more details.\n\/\/\n\/\/ You should have received a copy of the GNU General Public License\n\/\/ along with Moodle. If not, see .\n\n\/**\n * Course index main component.\n *\n * @module core_courseformat\/local\/courseindex\/courseindex\n * @class core_courseformat\/local\/courseindex\/courseindex\n * @copyright 2021 Ferran Recio \n * @license http:\/\/www.gnu.org\/copyleft\/gpl.html GNU GPL v3 or later\n *\/\n\nimport {BaseComponent} from 'core\/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat\/courseeditor';\nimport jQuery from 'jquery';\nimport ContentTree from 'core_courseformat\/local\/courseeditor\/contenttree';\n\nexport default class Component extends BaseComponent {\n\n \/**\n * Constructor hook.\n *\/\n create() {\n \/\/ Optional component name for debugging.\n this.name = 'courseindex';\n \/\/ Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n \/\/ Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n \/\/ Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n \/**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n *\/\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n \/**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n *\/\n stateReady(state) {\n \/\/ Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n \/\/ Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n \/\/ Set the page item if any.\n this._refreshPageItem({element: state.course, state});\n\n \/\/ Configure Aria Tree.\n this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing);\n }\n\n getWatchers() {\n return [\n {watch: `section.indexcollapsed:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n {watch: `section:created`, handler: this._createSection},\n {watch: `section:deleted`, handler: this._deleteSection},\n {watch: `course.pageItem:created`, handler: this._refreshPageItem},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n \/\/ Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n \/**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n *\/\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n \/\/ Update the state.\n const sectionId = section.getAttribute('data-id');\n if ((!sectionlink)) {\n this.reactive.dispatch(\n 'sectionIndexCollapsed',\n [sectionId],\n !isCollapsed\n );\n } else if (isCollapsed) {\n \/\/ Always expand the section when clicking on section name.\n this.reactive.dispatch(\n 'sectionIndexCollapsed',\n [sectionId],\n !isCollapsed\n );\n }\n }\n }\n\n \/**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n *\/\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n \/\/ Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.indexcollapsed !== isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n \/**\n * Expand a section node.\n *\n * By default the method will use element.indexcollapsed to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the indexcollapsed attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n *\/\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.indexcollapsed) ? false : true;\n }\n\n \/\/ Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n \/\/ interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n \/\/ it does not require jQuery anymore (when MDL-71979 is integrated).\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n \/**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data.\n * @param {Object} details.element the course state data.\n *\/\n _refreshPageItem({element, state}) {\n if (!element?.pageItem?.isStatic || element.pageItem.type != 'cm') {\n return;\n }\n \/\/ Check if we need to uncollapse the section and scroll to the element.\n const section = state.section.get(element.pageItem.sectionId);\n if (section.indexcollapsed) {\n this._expandSectionNode(section, true);\n setTimeout(\n () => this.cms[element.pageItem.id]?.scrollIntoView({block: \"nearest\"}),\n 250\n );\n }\n }\n\n \/**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n *\/\n async _createCm({state, element}) {\n \/\/ Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n \/\/ Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n \/\/ Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n \/\/ Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat\/local\/courseindex\/cm', data);\n \/\/ Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n \/**\n * Create a new section instance.\n *\n * @param {Object} details the update details.\n * @param {Object} details.state the state data.\n * @param {Object} details.element the element data.\n *\/\n async _createSection({state, element}) {\n \/\/ Create a fake node while the component is loading.\n const fakeelement = document.createElement('div');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.sections[element.id] = fakeelement;\n \/\/ Place the fake node on the correct position.\n this._refreshCourseSectionlist({\n state,\n element: state.course,\n });\n \/\/ Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.section(state, element);\n \/\/ Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat\/local\/courseindex\/section', data);\n \/\/ Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.sections[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n \/**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n *\/\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n \/**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n *\/\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n \/**\n * Fix\/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n *\/\n _fixOrder(container, neworder, allitems) {\n\n \/\/ Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n \/\/ Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n \/\/ Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n \/\/ Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n \/\/ Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n \/**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n *\/\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n\n \/**\n * Remove a section from the list.\n *\n * The actual DOM element removal is delegated to the section component.\n *\n * @param {Object} details the update details.\n * @param {Object} details.element the element data.\n *\/\n _deleteSection({element}) {\n delete this.sections[element.id];\n }\n}\n"],"names":["Component","BaseComponent","create","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","target","element","document","getElementById","reactive","stateReady","state","addEventListener","this","_sectionTogglers","getElements","forEach","section","dataset","id","cm","_refreshPageItem","course","contentTree","ContentTree","isEditing","getWatchers","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","getElement","Error","indexcollapsed","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","undefined","togglerValue","collapse","pageItem","_element$pageItem","isStatic","type","get","setTimeout","_this$cms$element$pag","scrollIntoView","block","fakeelement","createElement","add","innerHTML","sectionid","data","getExporter","newelement","renderComponent","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","insertBefore","append","removeChild","lastChild"],"mappings":"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"}