MDL-66893 mod_forum: update navigation bar in grader UI
[moodle.git] / mod / forum / amd / src / local / grades / grader.js
CommitLineData
bae67469
MM
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/**
17 * This module will tie together all of the different calls the gradable module will make.
18 *
19 * @module mod_forum/local/grades/grader
20 * @package mod_forum
21 * @copyright 2019 Mathew May <mathew.solutions>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24import Templates from 'core/templates';
bae67469 25import Selectors from './local/grader/selectors';
45c0584c 26import getUserPicker from './local/grader/user_picker';
bae67469 27import {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';
f281c616 28import getGradingPanelFunctions from './local/grader/gradingpanel';
77ee8778
AN
29import {add as addToast} from 'core/toast';
30import {get_string as getString} from 'core/str';
ce1c4701 31import {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';
bdcf8908 32import {addIconToContainerWithPromise} from 'core/loadingicon';
bae67469
MM
33
34const templateNames = {
35 grader: {
36 app: 'mod_forum/local/grades/grader',
ce1c4701
AN
37 gradingPanel: {
38 error: 'mod_forum/local/grades/local/grader/gradingpanel/error',
39 },
bae67469
MM
40 },
41};
42
cc1a7689
MM
43/**
44 * Helper function that replaces the user picker placeholder with what we get back from the user picker class.
45 *
46 * @param {HTMLElement} root
47 * @param {String} html
48 */
bae67469
MM
49const displayUserPicker = (root, html) => {
50 const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);
51 Templates.replaceNodeContents(pickerRegion, html, '');
52};
53
cc1a7689
MM
54/**
55 * To be removed, this is now done as a part of Templates.renderForPromise()
56 *
57 * @param {String} html
58 * @param {String} js
59 * @return {[*, *]}
60 */
f281c616
AN
61const fetchContentFromRender = (html, js) => {
62 return [html, js];
63};
64
cc1a7689
MM
65/**
66 * Here we build the function that is passed to the user picker that'll handle updating the user content area
67 * of the grading interface.
68 *
69 * @param {HTMLElement} root
70 * @param {Function} getContentForUser
71 * @param {Function} getGradeForUser
72 * @return {Function}
73 */
f281c616 74const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {
bae67469 75 return async(user) => {
bdcf8908 76 const spinner = addIconToContainerWithPromise(root);
bae67469 77 const [
f281c616
AN
78 [html, js],
79 userGrade,
bae67469 80 ] = await Promise.all([
f281c616
AN
81 getContentForUser(user.id).then(fetchContentFromRender),
82 getGradeForUser(user.id),
bae67469
MM
83 ]);
84 Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);
f281c616
AN
85
86 const [
87 gradingPanelHtml,
88 gradingPanelJS
89 ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);
46d51c8c
RW
90 const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);
91 const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);
92 Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);
93 panelContainer.scrollTop = 0;
bdcf8908 94 spinner.resolve();
bae67469
MM
95 };
96};
97
cc1a7689
MM
98/**
99 * Add click handlers to the buttons in the header of the grading interface.
100 *
101 * @param {HTMLElement} graderLayout
102 * @param {Object} userPicker
103 * @param {Function} saveGradeFunction
104 */
eaee6477 105const registerEventListeners = (graderLayout, userPicker, saveGradeFunction) => {
bae67469
MM
106 const graderContainer = graderLayout.getContainer();
107 graderContainer.addEventListener('click', (e) => {
108 if (e.target.closest(Selectors.buttons.toggleFullscreen)) {
109 e.stopImmediatePropagation();
110 e.preventDefault();
111 graderLayout.toggleFullscreen();
eaee6477
AN
112
113 return;
114 }
115
116 if (e.target.closest(Selectors.buttons.closeGrader)) {
bae67469
MM
117 e.stopImmediatePropagation();
118 e.preventDefault();
119
120 graderLayout.close();
eaee6477
AN
121
122 return;
123 }
124
125 if (e.target.closest(Selectors.buttons.saveGrade)) {
126 saveGradeFunction(userPicker.currentUser);
bae67469
MM
127 }
128 });
129};
130
77ee8778
AN
131/**
132 * Get the function used to save a user grade.
133 *
cc1a7689 134 * @param {HTMLElement} root The container for the grader
77ee8778
AN
135 * @param {Function} setGradeForUser The function that will be called.
136 * @return {Function}
137 */
f281c616 138const getSaveUserGradeFunction = (root, setGradeForUser) => {
cc1a7689 139 return async(user) => {
77ee8778 140 try {
ce1c4701 141 root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';
77ee8778 142 const result = await setGradeForUser(user.id, root.querySelector(Selectors.regions.gradingPanel));
ce1c4701
AN
143 if (result.success) {
144 addToast(await getString('grades:gradesavedfor', 'mod_forum', user));
145 }
146 if (result.failed) {
147 displayGradingError(root, user, result.error);
148 }
77ee8778
AN
149
150 return result;
ce1c4701
AN
151 } catch (err) {
152 displayGradingError(root, user, err);
153
154 return failedUpdate(err);
77ee8778 155 }
f281c616
AN
156 };
157};
158
ce1c4701
AN
159/**
160 * Display a grading error, typically from a failed save.
161 *
cc1a7689 162 * @param {HTMLElement} root The container for the grader
ce1c4701
AN
163 * @param {Object} user The user who was errored
164 * @param {Object} err The details of the error
165 */
166const displayGradingError = async(root, user, err) => {
167 const [
168 {html, js},
169 errorString
170 ] = await Promise.all([
171 Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),
172 await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),
173 ]);
174
175 Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);
176 addToast(errorString);
177};
178
eaee6477
AN
179/**
180 * Launch the grader interface with the specified parameters.
181 *
182 * @param {Function} getListOfUsers A function to get the list of users
183 * @param {Function} getContentForUser A function to get the content for a specific user
184 * @param {Function} getGradeForUser A function get the grade details for a specific user
185 * @param {Function} setGradeForUser A function to set the grade for a specific user
186 */
f281c616 187export const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {
4c98e56c 188 initialUserId = null, moduleName, courseName, courseUrl
bae67469
MM
189} = {}) => {
190
cc1a7689
MM
191 // We need all of these functions to be executed in series, if one step runs before another the interface
192 // will not work.
bae67469
MM
193 const [
194 graderLayout,
46d51c8c 195 {html, js},
bae67469
MM
196 userList,
197 ] = await Promise.all([
198 createFullScreenWindow({fullscreen: false, showLoader: false}),
46d51c8c
RW
199 Templates.renderForPromise(templateNames.grader.app, {
200 moduleName,
4c98e56c
RW
201 courseName,
202 courseUrl,
46d51c8c
RW
203 drawer: {show: true}
204 }),
bae67469
MM
205 getListOfUsers(),
206 ]);
207 const graderContainer = graderLayout.getContainer();
208
45c0584c
AN
209 const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);
210
46d51c8c 211 Templates.replaceNodeContents(graderContainer, html, js);
f281c616
AN
212 const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser);
213
45c0584c
AN
214 // Fetch the userpicker for display.
215 const userPicker = await getUserPicker(
f281c616 216 userList,
f281c616 217 updateUserContent,
aa04b722
AN
218 saveGradeFunction,
219 {
220 initialUserId,
221 },
f281c616 222 );
bae67469 223
eaee6477
AN
224 // Register all event listeners.
225 registerEventListeners(graderLayout, userPicker, saveGradeFunction);
226
45c0584c
AN
227 // Display the newly created user picker.
228 displayUserPicker(graderContainer, userPicker.rootNode);
bae67469 229};
f281c616
AN
230
231export {getGradingPanelFunctions};