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/>.
  15. /**
  16. * This module will tie together all of the different calls the gradable module will make.
  17. *
  18. * @module mod_forum/local/grades/local/grader/user_picker
  19. * @copyright 2019 Mathew May <mathew.solutions>
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. import Templates from 'core/templates';
  23. import Selectors from './user_picker/selectors';
  24. import {get_string as getString} from 'core/str';
  25. const templatePath = 'mod_forum/local/grades/local/grader';
  26. /**
  27. * The Grader User Picker.
  28. *
  29. * @class mod_forum/local/grades/local/grader/user_picker
  30. */
  31. class UserPicker {
  32. /**
  33. * Constructor for the User Picker.
  34. *
  35. * @constructor mod_forum/local/grades/local/grader/user_picker
  36. * @param {Array} userList List of users
  37. * @param {Function} showUserCallback The callback used to display the user
  38. * @param {Function} preChangeUserCallback The callback to use before changing user
  39. */
  40. constructor(userList, showUserCallback, preChangeUserCallback) {
  41. this.userList = userList;
  42. this.showUserCallback = showUserCallback;
  43. this.preChangeUserCallback = preChangeUserCallback;
  44. 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. }
  49. /**
  50. * Set the current userid without rendering the change.
  51. * To show the user, call showUser too.
  52. *
  53. * @param {Number} userId
  54. */
  55. setUserId(userId) {
  56. // Determine the current index based on the user ID.
  57. const userIndex = this.userList.findIndex(user => {
  58. return user.id === parseInt(userId);
  59. });
  60. if (userIndex === -1) {
  61. throw Error(`User with id ${userId} not found`);
  62. }
  63. this.currentUserIndex = userIndex;
  64. }
  65. /**
  66. * Render the user picker.
  67. */
  68. async render() {
  69. // Create the root node.
  70. this.root = document.createElement('div');
  71. const {html, js} = await this.renderNavigator();
  72. Templates.replaceNodeContents(this.root, html, js);
  73. // Call the showUser function to show the first user immediately.
  74. await this.showUser(this.currentUser);
  75. // Ensure that the event listeners are all bound.
  76. this.registerEventListeners();
  77. }
  78. /**
  79. * Render the navigator itself.
  80. *
  81. * @returns {Promise}
  82. */
  83. renderNavigator() {
  84. return Templates.renderForPromise(`${templatePath}/user_picker`, {});
  85. }
  86. /**
  87. * Render the current user details for the picker.
  88. *
  89. * @param {Object} context The data used to render the user picker.
  90. * @returns {Promise}
  91. */
  92. renderUserChange(context) {
  93. return Templates.renderForPromise(`${templatePath}/user_picker/user`, context);
  94. }
  95. /**
  96. * Show the specified user in the picker.
  97. *
  98. * @param {Object} user
  99. */
  100. async showUser(user) {
  101. const [{html, js}] = await Promise.all([this.renderUserChange(user), this.showUserCallback(user)]);
  102. const userRegion = this.root.querySelector(Selectors.regions.userRegion);
  103. Templates.replaceNodeContents(userRegion, html, js);
  104. // Update the hidden now-grading region so screen readers can announce the user that's currently being graded.
  105. const currentUserRegion = this.root.querySelector(Selectors.regions.currentUser);
  106. currentUserRegion.textContent = await getString('nowgradinguser', 'mod_forum', user.fullname);
  107. }
  108. /**
  109. * Register the event listeners for the user picker.
  110. */
  111. registerEventListeners() {
  112. this.root.addEventListener('click', async(e) => {
  113. const button = e.target.closest(Selectors.actions.changeUser);
  114. if (button) {
  115. const result = await this.preChangeUserCallback(this.currentUser);
  116. if (!result.failed) {
  117. this.updateIndex(parseInt(button.dataset.direction));
  118. await this.showUser(this.currentUser);
  119. }
  120. }
  121. });
  122. }
  123. /**
  124. * Update the current user index.
  125. *
  126. * @param {Number} direction
  127. * @returns {Number}}
  128. */
  129. updateIndex(direction) {
  130. this.currentUserIndex += direction;
  131. // Loop around the edges.
  132. if (this.currentUserIndex < 0) {
  133. this.currentUserIndex = this.userList.length - 1;
  134. } else if (this.currentUserIndex > this.userList.length - 1) {
  135. this.currentUserIndex = 0;
  136. }
  137. return this.currentUserIndex;
  138. }
  139. /**
  140. * Get the details of the user currently shown with the total number of users, and the 1-indexed count of the
  141. * current user.
  142. *
  143. * @returns {Object}
  144. */
  145. get currentUser() {
  146. return {
  147. ...this.userList[this.currentUserIndex],
  148. total: this.userList.length,
  149. displayIndex: this.currentUserIndex + 1,
  150. };
  151. }
  152. /**
  153. * Get the root node for the User Picker.
  154. *
  155. * @returns {HTMLElement}
  156. */
  157. get rootNode() {
  158. return this.root;
  159. }
  160. }
  161. /**
  162. * Create a new user picker.
  163. *
  164. * @param {Array} users The list of users
  165. * @param {Function} showUserCallback The function to call to show a specific user
  166. * @param {Function} preChangeUserCallback The fucntion to call to save the grade for the current user
  167. * @param {Number} [currentUserID] The userid of the current user
  168. * @returns {UserPicker}
  169. */
  170. export default async(
  171. users,
  172. showUserCallback,
  173. preChangeUserCallback,
  174. {
  175. initialUserId = null,
  176. } = {}
  177. ) => {
  178. const userPicker = new UserPicker(users, showUserCallback, preChangeUserCallback);
  179. if (initialUserId) {
  180. userPicker.setUserId(initialUserId);
  181. }
  182. await userPicker.render();
  183. return userPicker;
  184. };