Merge branch 'MDL-67262' of git://github.com/stronk7/moodle
[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.content_items.forEach((module) => {
104         module.link += '&section=' + id;
105     });
106     return newData.content_items;
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     // Filter the incoming data to find favourite & recommended modules.
127     const favourites = [];
128     const recommended = [];
130     // Given the results of the above filters lets figure out what tab to set active.
132     // We have some favourites.
133     const favouritesFirst = !!favourites.length;
134     // Check if we have no favourites but have some recommended.
135     const recommendedFirst = !!(recommended.length && favouritesFirst === false);
136     // We have nothing fallback to show all modules.
137     const fallback = favouritesFirst === false && recommendedFirst === false;
139     return {
140         'default': data,
141         favourites: favourites,
142         recommended: recommended,
143         favouritesFirst: favouritesFirst,
144         recommendedFirst: recommendedFirst,
145         fallback: fallback,
146     };
147 };
149 /**
150  * Given an object we want to prebuild a modal ready to store into a map
151  *
152  * @method buildModal
153  * @param {Object} data The template data which contains arrays of modules
154  * @return {Object} The modal for the calling section with everything already set up
155  */
156 const buildModal = data => {
157     return ModalFactory.create({
158         type: ModalFactory.types.DEFAULT,
159         title: getString('addresourceoractivity'),
160         body: Templates.render('core_course/chooser', data),
161         large: true,
162         templateContext: {
163             classes: 'modchooser'
164         }
165     });
166 };