MDL-66074 mod_forum: Implement spinner for user change
[moodle.git] / mod / forum / amd / src / local / grades / local / grader / user_picker.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/local/grader/user_picker
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 */
24
25import Templates from 'core/templates';
26import Selectors from './user_picker/selectors';
18dd283a 27import {addIconToContainerWithPromise} from 'core/loadingicon';
bae67469 28
45c0584c 29const templatePath = 'mod_forum/local/grades/local/grader';
bae67469 30
45c0584c 31class UserPicker {
bae67469 32
45c0584c
AN
33 /**
34 * Constructor for the User Picker.
35 *
36 * @param {Array} userList List of users
45c0584c
AN
37 * @param {Function} showUserCallback The callback used to display the user
38 * @param {Function} preChangeUserCallback The callback to use before changing user
45c0584c 39 */
aa04b722 40 constructor(userList, showUserCallback, preChangeUserCallback) {
45c0584c
AN
41 this.userList = userList;
42 this.showUserCallback = showUserCallback;
43 this.preChangeUserCallback = preChangeUserCallback;
aa04b722 44 this.currentUserIndex = 0;
45c0584c
AN
45
46 // Ensure that render is bound correctly.
47 this.render = this.render.bind(this);
aa04b722
AN
48 this.setUserId = this.setUserId.bind(this);
49 }
50
51 /**
52 * Set the current userid without rendering the change.
53 * To show the user, call showUser too.
54 *
55 * @param {Number} userId
56 */
57 setUserId(userId) {
58 // Determine the current index based on the user ID.
59 const userIndex = this.userList.findIndex(user => {
60 return user.id === parseInt(userId);
61 });
bae67469 62
aa04b722
AN
63 if (userIndex === -1) {
64 throw Error(`User with id ${userId} not found`);
bae67469 65 }
aa04b722
AN
66
67 this.currentUserIndex = userIndex;
45c0584c
AN
68 }
69
70 /**
71 * Render the user picker.
72 */
73 async render() {
74 // Create the root node.
75 this.root = document.createElement('div');
76
77 const {html, js} = await this.renderNavigator();
78 Templates.replaceNodeContents(this.root, html, js);
79
80 // Call the showUser function to show the first user immediately.
81 await this.showUser(this.currentUser);
82
83 // Ensure that the event listeners are all bound.
84 this.registerEventListeners();
85 }
bae67469 86
45c0584c
AN
87 /**
88 * Render the navigator itself.
89 *
90 * @returns {Promise}
91 */
92 renderNavigator() {
93 return Templates.renderForPromise(`${templatePath}/user_picker`, {});
94 }
bae67469 95
45c0584c
AN
96 /**
97 * Render the current user details for the picker.
98 *
99 * @param {Object} context The data used to render the user picker.
100 * @returns {Promise}
101 */
102 renderUserChange(context) {
103 return Templates.renderForPromise(`${templatePath}/user_picker/user`, context);
104 }
bae67469 105
45c0584c
AN
106 /**
107 * Show the specified user in the picker.
108 *
109 * @param {Object} user
110 */
111 async showUser(user) {
112 const [{html, js}] = await Promise.all([this.renderUserChange(user), this.showUserCallback(user)]);
113 const userRegion = this.root.querySelector(Selectors.regions.userRegion);
114 Templates.replaceNodeContents(userRegion, html, js);
115 }
bae67469 116
45c0584c
AN
117 /**
118 * Register the event listeners for the user picker.
119 */
120 registerEventListeners() {
eaee6477 121 this.root.addEventListener('click', async e => {
45c0584c
AN
122 const button = e.target.closest(Selectors.actions.changeUser);
123 if (button) {
18dd283a
MM
124 const whole = document.querySelector('[data-region="unified-grader"]');
125 const spinner = addIconToContainerWithPromise(whole);
126
eaee6477 127 await this.preChangeUserCallback(this.currentUser);
45c0584c 128 this.updateIndex(parseInt(button.dataset.direction));
eaee6477 129 await this.showUser(this.currentUser);
18dd283a
MM
130
131 spinner.resolve();
45c0584c
AN
132 }
133 });
134 }
bae67469 135
45c0584c
AN
136 /**
137 * Update the current user index.
138 *
139 * @param {Number} direction
140 * @returns {Number}}
141 */
142 updateIndex(direction) {
143 this.currentUserIndex += direction;
144
145 // Loop around the edges.
146 if (this.currentUserIndex < 0) {
147 this.currentUserIndex = this.userList.length - 1;
148 } else if (this.currentUserIndex > this.userList.length - 1) {
149 this.currentUserIndex = 0;
150 }
151
152 return this.currentUserIndex;
153 }
154
155 /**
156 * Get the details of the user currently shown with the total number of users, and the 1-indexed count of the
157 * current user.
158 *
159 * @returns {Object}
160 */
161 get currentUser() {
162 return {
163 ...this.userList[this.currentUserIndex],
164 total: this.userList.length,
165 displayIndex: this.currentUserIndex + 1,
166 };
167 }
168
169 /**
170 * Get the root node for the User Picker.
171 *
172 * @returns {HTMLElement}
173 */
174 get rootNode() {
175 return this.root;
176 }
177}
178
179/**
180 * Create a new user picker.
181 *
182 * @param {Array} users The list of users
45c0584c
AN
183 * @param {Function} showUserCallback The function to call to show a specific user
184 * @param {Function} preChangeUserCallback The fucntion to call to save the grade for the current user
aa04b722 185 * @param {Number} [currentUserID] The userid of the current user
45c0584c
AN
186 * @returns {UserPicker}
187 */
aa04b722
AN
188export default async(users, showUserCallback, preChangeUserCallback, {
189 initialUserId = null,
190 } = {}
191) => {
192 const userPicker = new UserPicker(users, showUserCallback, preChangeUserCallback);
193 if (initialUserId) {
194 userPicker.setUserId(initialUserId);
195 }
45c0584c 196 await userPicker.render();
bae67469 197
45c0584c 198 return userPicker;
bae67469 199};