MDL-67264 core_course: Activity chooser new feature
[moodle.git] / course / amd / src / activitychooser.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/>.
16 /**
17  * A type of dialogue used as for choosing modules in a course.
18  *
19  * @module     core_course/activitychooser
20  * @package    core_course
21  * @copyright  2020 Mathew May <mathew.solutions>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 import * as ChooserDialogue from 'core_course/local/activitychooser/dialogue';
26 import * as Repository from 'core_course/local/activitychooser/repository';
27 import selectors from 'core_course/local/activitychooser/selectors';
28 import CustomEvents from 'core/custom_interaction_events';
29 import * as Templates from 'core/templates';
30 import * as ModalFactory from 'core/modal_factory';
31 import {get_string as getString} from 'core/str';
32 import Pending from 'core/pending';
34 /**
35  * Set up the activity chooser.
36  *
37  * @method init
38  * @param {Number} courseId Course ID to use later on in fetchModules()
39  */
40 export const init = courseId => {
41     const pendingPromise = new Pending();
43     registerListenerEvents(courseId);
45     pendingPromise.resolve();
46 };
48 /**
49  * Once a selection has been made make the modal & module information and pass it along
50  *
51  * @method registerListenerEvents
52  * @param {Number} courseId
53  */
54 const registerListenerEvents = (courseId) => {
55     const events = [
56         'click',
57         CustomEvents.events.activate,
58         CustomEvents.events.keyboardActivate
59     ];
61     const fetchModuleData = (() => {
62         let innerPromise = null;
64         return () => {
65             if (!innerPromise) {
66                 innerPromise = new Promise((resolve) => {
67                     resolve(Repository.activityModules(courseId));
68                 });
69             }
71             return innerPromise;
72         };
73     })();
75     CustomEvents.define(document, events);
77     // Display module chooser event listeners.
78     events.forEach((event) => {
79         document.addEventListener(event, async(e) => {
80             if (e.target.closest(selectors.elements.sectionmodchooser)) {
81                 const caller = e.target.closest(selectors.elements.sectionmodchooser);
82                 const builtModuleData = sectionIdMapper(await fetchModuleData(), caller.dataset.sectionid);
83                 const sectionModal = await modalBuilder(builtModuleData);
85                 ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData);
86             }
87         });
88     });
89 };
91 /**
92  * Given the web service data and an ID we want to make a deep copy
93  * of the WS data then add on the section ID to the addoption URL
94  *
95  * @method sectionIdMapper
96  * @param {Object} webServiceData Our original data from the Web service call
97  * @param {Array} id The ID of the section we need to append to the links
98  * @return {Array} [modules] with URL's built
99  */
100 const sectionIdMapper = (webServiceData, id) => {
101     // We need to take a fresh deep copy of the original data as an object is a reference type.
102     const newData = JSON.parse(JSON.stringify(webServiceData));
103     newData.allmodules.forEach((module) => {
104         module.urls.addoption += '&section=' + id;
105     });
106     return newData.allmodules;
107 };
109 /**
110  * Build a modal for each section ID and store it into a map for quick access
111  *
112  * @method modalBuilder
113  * @param {Map} data our map of section ID's & modules to generate modals for
114  * @return {Object} TODO
115  */
116 const modalBuilder = data => buildModal(templateDataBuilder(data));
118 /**
119  * Given an array of modules we want to figure out where & how to place them into our template object
120  *
121  * @method templateDataBuilder
122  * @param {Array} data our modules to manipulate into a Templatable object
123  * @return {Object} Our built object ready to render out
124  */
125 const templateDataBuilder = (data) => {
126     return {
127         'default': data,
128     };
129 };
131 /**
132  * Given an object we want to prebuild a modal ready to store into a map
133  *
134  * @method buildModal
135  * @param {Object} data The template data which contains arrays of modules
136  * @return {Object} The modal for the calling section with everything already set up
137  */
138 const buildModal = data => {
139     return ModalFactory.create({
140         type: ModalFactory.types.DEFAULT,
141         title: getString('addresourceoractivity'),
142         body: Templates.render('core_course/chooser', data),
143         large: true,
144         templateContext: {
145             classes: 'modchooser'
146         }
147     });
148 };