1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * This module will tie together all of the different calls the gradable module will make.
19 * @module mod_forum/local/grades/grader
21 * @copyright 2019 Mathew May <mathew.solutions>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 import Templates from 'core/templates';
25 import Selectors from './local/grader/selectors';
26 import getUserPicker from './local/grader/user_picker';
27 import {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';
28 import getGradingPanelFunctions from './local/grader/gradingpanel';
29 import {add as addToast} from 'core/toast';
30 import {get_string as getString} from 'core/str';
31 import {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';
32 import {addIconToContainerWithPromise} from 'core/loadingicon';
34 const templateNames = {
36 app: 'mod_forum/local/grades/grader',
38 error: 'mod_forum/local/grades/local/grader/gradingpanel/error',
44 * Helper function that replaces the user picker placeholder with what we get back from the user picker class.
46 * @param {HTMLElement} root
47 * @param {String} html
49 const displayUserPicker = (root, html) => {
50 const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);
51 Templates.replaceNodeContents(pickerRegion, html, '');
55 * To be removed, this is now done as a part of Templates.renderForPromise()
57 * @param {String} html
61 const fetchContentFromRender = (html, js) => {
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.
69 * @param {HTMLElement} root
70 * @param {Function} getContentForUser
71 * @param {Function} getGradeForUser
74 const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {
75 return async(user) => {
76 const spinner = addIconToContainerWithPromise(root);
80 ] = await Promise.all([
81 getContentForUser(user.id).then(fetchContentFromRender),
82 getGradeForUser(user.id),
84 Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);
89 ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);
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;
99 * Add click handlers to the buttons in the header of the grading interface.
101 * @param {HTMLElement} graderLayout
102 * @param {Object} userPicker
103 * @param {Function} saveGradeFunction
105 const registerEventListeners = (graderLayout, userPicker, saveGradeFunction) => {
106 const graderContainer = graderLayout.getContainer();
107 graderContainer.addEventListener('click', (e) => {
108 if (e.target.closest(Selectors.buttons.toggleFullscreen)) {
109 e.stopImmediatePropagation();
111 graderLayout.toggleFullscreen();
116 if (e.target.closest(Selectors.buttons.closeGrader)) {
117 e.stopImmediatePropagation();
120 graderLayout.close();
125 if (e.target.closest(Selectors.buttons.saveGrade)) {
126 saveGradeFunction(userPicker.currentUser);
132 * Get the function used to save a user grade.
134 * @param {HTMLElement} root The container for the grader
135 * @param {Function} setGradeForUser The function that will be called.
138 const getSaveUserGradeFunction = (root, setGradeForUser) => {
139 return async(user) => {
141 root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';
142 const result = await setGradeForUser(user.id, root.querySelector(Selectors.regions.gradingPanel));
143 if (result.success) {
144 addToast(await getString('grades:gradesavedfor', 'mod_forum', user));
147 displayGradingError(root, user, result.error);
152 displayGradingError(root, user, err);
154 return failedUpdate(err);
160 * Display a grading error, typically from a failed save.
162 * @param {HTMLElement} root The container for the grader
163 * @param {Object} user The user who was errored
164 * @param {Object} err The details of the error
166 const displayGradingError = async(root, user, err) => {
170 ] = await Promise.all([
171 Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),
172 await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),
175 Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);
176 addToast(errorString);
180 * Launch the grader interface with the specified parameters.
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
187 export const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {
188 initialUserId = null, moduleName, courseName, courseUrl
191 // We need all of these functions to be executed in series, if one step runs before another the interface
197 ] = await Promise.all([
198 createFullScreenWindow({fullscreen: false, showLoader: false}),
199 Templates.renderForPromise(templateNames.grader.app, {
207 const graderContainer = graderLayout.getContainer();
209 const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);
211 Templates.replaceNodeContents(graderContainer, html, js);
212 const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser);
214 // Fetch the userpicker for display.
215 const userPicker = await getUserPicker(
224 // Register all event listeners.
225 registerEventListeners(graderLayout, userPicker, saveGradeFunction);
227 // Display the newly created user picker.
228 displayUserPicker(graderContainer, userPicker.rootNode);
231 export {getGradingPanelFunctions};