user/amd/src/status_field.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. * AMD module for the user enrolment status field in the course participants page.
  17. *
  18. * @module core_user/status_field
  19. * @copyright 2017 Jun Pataleta
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. import * as DynamicTable from 'core_table/dynamic';
  23. import * as Repository from './repository';
  24. import * as Str from 'core/str';
  25. import DynamicTableSelectors from 'core_table/local/dynamic/selectors';
  26. import Fragment from 'core/fragment';
  27. import ModalEvents from 'core/modal_events';
  28. import Notification from 'core/notification';
  29. import Templates from 'core/templates';
  30. import {add as notifyUser} from 'core/toast';
  31. import SaveCancelModal from 'core/modal_save_cancel';
  32. import CancelModal from 'core/modal_cancel';
  33. const Selectors = {
  34. editEnrolment: '[data-action="editenrolment"]',
  35. showDetails: '[data-action="showdetails"]',
  36. unenrol: '[data-action="unenrol"]',
  37. statusElement: '[data-status]',
  38. };
  39. /**
  40. * Get the dynamic table from the specified link.
  41. *
  42. * @param {HTMLElement} link
  43. * @returns {HTMLElement}
  44. */
  45. const getDynamicTableFromLink = link => link.closest(DynamicTableSelectors.main.region);
  46. /**
  47. * Get the status container from the specified link.
  48. *
  49. * @param {HTMLElement} link
  50. * @returns {HTMLElement}
  51. */
  52. const getStatusContainer = link => link.closest(Selectors.statusElement);
  53. /**
  54. * Get user enrolment id from the specified link
  55. *
  56. * @param {HTMLElement} link
  57. * @returns {Number}
  58. */
  59. const getUserEnrolmentIdFromLink = link => link.getAttribute('rel');
  60. /**
  61. * Register all event listeners for the status fields.
  62. *
  63. * @param {Number} contextId
  64. * @param {Number} uniqueId
  65. */
  66. const registerEventListeners = (contextId, uniqueId) => {
  67. const getBodyFunction = (userEnrolmentId, formData) => getBody(contextId, userEnrolmentId, formData);
  68. document.addEventListener('click', e => {
  69. const tableRoot = e.target.closest(DynamicTableSelectors.main.fromRegionId(uniqueId));
  70. if (!tableRoot) {
  71. return;
  72. }
  73. const editLink = e.target.closest(Selectors.editEnrolment);
  74. if (editLink) {
  75. e.preventDefault();
  76. showEditDialogue(editLink, getBodyFunction);
  77. }
  78. const unenrolLink = e.target.closest(Selectors.unenrol);
  79. if (unenrolLink) {
  80. e.preventDefault();
  81. showUnenrolConfirmation(unenrolLink);
  82. }
  83. const showDetailsLink = e.target.closest(Selectors.showDetails);
  84. if (showDetailsLink) {
  85. e.preventDefault();
  86. showStatusDetails(showDetailsLink);
  87. }
  88. });
  89. };
  90. /**
  91. * Show the edit dialogue.
  92. *
  93. * @param {HTMLElement} link
  94. * @param {Function} getBody Function to get the body for the specified user enrolment
  95. */
  96. const showEditDialogue = (link, getBody) => {
  97. const container = getStatusContainer(link);
  98. const userEnrolmentId = getUserEnrolmentIdFromLink(link);
  99. SaveCancelModal.create({
  100. large: true,
  101. title: Str.get_string('edituserenrolment', 'enrol', container.dataset.fullname),
  102. body: getBody(userEnrolmentId)
  103. })
  104. .then(modal => {
  105. // Handle save event.
  106. modal.getRoot().on(ModalEvents.save, e => {
  107. // Don't close the modal yet.
  108. e.preventDefault();
  109. // Submit form data.
  110. submitEditFormAjax(link, getBody, modal, userEnrolmentId, container.dataset);
  111. });
  112. // Handle hidden event.
  113. modal.getRoot().on(ModalEvents.hidden, () => {
  114. // Destroy when hidden.
  115. modal.destroy();
  116. });
  117. // Show the modal.
  118. modal.show();
  119. return modal;
  120. })
  121. .catch(Notification.exception);
  122. };
  123. /**
  124. * Show and handle the unenrolment confirmation dialogue.
  125. *
  126. * @param {HTMLElement} link
  127. */
  128. const showUnenrolConfirmation = link => {
  129. const container = getStatusContainer(link);
  130. const userEnrolmentId = getUserEnrolmentIdFromLink(link);
  131. SaveCancelModal.create()
  132. .then(modal => {
  133. // Handle confirm event.
  134. modal.getRoot().on(ModalEvents.save, e => {
  135. // Don't close the modal yet.
  136. e.preventDefault();
  137. // Submit data.
  138. submitUnenrolFormAjax(
  139. link,
  140. modal,
  141. {
  142. ueid: userEnrolmentId,
  143. },
  144. container.dataset
  145. );
  146. });
  147. // Handle hidden event.
  148. modal.getRoot().on(ModalEvents.hidden, () => {
  149. // Destroy when hidden.
  150. modal.destroy();
  151. });
  152. // Display the delete confirmation modal.
  153. modal.show();
  154. const stringData = [
  155. {
  156. key: 'unenrol',
  157. component: 'enrol',
  158. },
  159. {
  160. key: 'unenrolconfirm',
  161. component: 'enrol',
  162. param: {
  163. user: container.dataset.fullname,
  164. course: container.dataset.coursename,
  165. enrolinstancename: container.dataset.enrolinstancename,
  166. }
  167. }
  168. ];
  169. return Promise.all([Str.get_strings(stringData), modal]);
  170. })
  171. .then(([strings, modal]) => {
  172. modal.setTitle(strings[0]);
  173. modal.setSaveButtonText(strings[0]);
  174. modal.setBody(strings[1]);
  175. return modal;
  176. })
  177. .catch(Notification.exception);
  178. };
  179. /**
  180. * Show the user details dialogue.
  181. *
  182. * @param {HTMLElement} link
  183. */
  184. const showStatusDetails = link => {
  185. const container = getStatusContainer(link);
  186. const context = {
  187. editenrollink: '',
  188. statusclass: container.querySelector('span.badge').getAttribute('class'),
  189. ...container.dataset,
  190. };
  191. // Find the edit enrolment link.
  192. const editEnrolLink = container.querySelector(Selectors.editEnrolment);
  193. if (editEnrolLink) {
  194. // If there's an edit enrolment link for this user, clone it into the context for the modal.
  195. context.editenrollink = editEnrolLink.outerHTML;
  196. }
  197. CancelModal.create({
  198. large: true,
  199. title: Str.get_string('enroldetails', 'enrol'),
  200. body: Templates.render('core_user/status_details', context),
  201. })
  202. .then(modal => {
  203. if (editEnrolLink) {
  204. modal.getRoot().on('click', Selectors.editEnrolment, e => {
  205. e.preventDefault();
  206. modal.hide();
  207. // Trigger click event for the edit enrolment link to show the edit enrolment modal.
  208. editEnrolLink.click();
  209. });
  210. }
  211. modal.show();
  212. // Handle hidden event.
  213. modal.getRoot().on(ModalEvents.hidden, () => modal.destroy());
  214. return modal;
  215. })
  216. .catch(Notification.exception);
  217. };
  218. /**
  219. * Submit the edit dialogue.
  220. *
  221. * @param {HTMLElement} clickedLink
  222. * @param {Function} getBody
  223. * @param {Object} modal
  224. * @param {Number} userEnrolmentId
  225. * @param {Object} userData
  226. */
  227. const submitEditFormAjax = (clickedLink, getBody, modal, userEnrolmentId, userData) => {
  228. const form = modal.getRoot().find('form');
  229. Repository.submitUserEnrolmentForm(form.serialize())
  230. .then(data => {
  231. if (!data.result) {
  232. throw data.result;
  233. }
  234. // Dismiss the modal.
  235. modal.hide();
  236. modal.destroy();
  237. return data;
  238. })
  239. .then(() => {
  240. DynamicTable.refreshTableContent(getDynamicTableFromLink(clickedLink))
  241. .catch(Notification.exception);
  242. return Str.get_string('enrolmentupdatedforuser', 'core_enrol', userData);
  243. })
  244. .then(notificationString => {
  245. notifyUser(notificationString);
  246. return;
  247. })
  248. .catch(() => {
  249. modal.setBody(getBody(userEnrolmentId, JSON.stringify(form.serialize())));
  250. return modal;
  251. });
  252. };
  253. /**
  254. * Submit the unenrolment form.
  255. *
  256. * @param {HTMLElement} clickedLink
  257. * @param {Object} modal
  258. * @param {Object} args
  259. * @param {Object} userData
  260. */
  261. const submitUnenrolFormAjax = (clickedLink, modal, args, userData) => {
  262. Repository.unenrolUser(args.ueid)
  263. .then(data => {
  264. if (!data.result) {
  265. // Display an alert containing the error message
  266. Notification.alert(data.errors[0].key, data.errors[0].message);
  267. return data;
  268. }
  269. // Dismiss the modal.
  270. modal.hide();
  271. modal.destroy();
  272. return data;
  273. })
  274. .then(() => {
  275. DynamicTable.refreshTableContent(getDynamicTableFromLink(clickedLink))
  276. .catch(Notification.exception);
  277. return Str.get_string('unenrolleduser', 'core_enrol', userData);
  278. })
  279. .then(notificationString => {
  280. notifyUser(notificationString);
  281. return;
  282. })
  283. .catch(Notification.exception);
  284. };
  285. /**
  286. * Get the body fragment.
  287. *
  288. * @param {Number} contextId
  289. * @param {Number} ueid The user enrolment id
  290. * @param {Object} formdata
  291. * @returns {Promise}
  292. */
  293. const getBody = (contextId, ueid, formdata = null) => Fragment.loadFragment(
  294. 'enrol',
  295. 'user_enrolment_form',
  296. contextId,
  297. {
  298. ueid,
  299. formdata,
  300. }
  301. );
  302. /**
  303. * Initialise the statu field handler.
  304. *
  305. * @param {object} param
  306. * @param {Number} param.contextid
  307. * @param {Number} param.uniqueid
  308. */
  309. export const init = ({contextid, uniqueid}) => {
  310. registerEventListeners(contextid, uniqueid);
  311. };