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