MDL-68442 user: Rewrite participant status to work with dynamic tables
authorAndrew Nicols <andrew@nicols.co.uk>
Fri, 17 Apr 2020 08:28:07 +0000 (16:28 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 28 Apr 2020 02:21:45 +0000 (10:21 +0800)
13 files changed:
lib/table/amd/build/dynamic.min.js
lib/table/amd/build/dynamic.min.js.map
lib/table/amd/build/local/dynamic/selectors.min.js
lib/table/amd/build/local/dynamic/selectors.min.js.map
lib/table/amd/src/dynamic.js
lib/table/amd/src/local/dynamic/selectors.js
user/amd/build/repository.min.js [new file with mode: 0644]
user/amd/build/repository.min.js.map [new file with mode: 0644]
user/amd/build/status_field.min.js
user/amd/build/status_field.min.js.map
user/amd/src/repository.js [new file with mode: 0644]
user/amd/src/status_field.js
user/classes/table/participants.php

index 96cc2ca..224602f 100644 (file)
Binary files a/lib/table/amd/build/dynamic.min.js and b/lib/table/amd/build/dynamic.min.js differ
index d457b4f..3873913 100644 (file)
Binary files a/lib/table/amd/build/dynamic.min.js.map and b/lib/table/amd/build/dynamic.min.js.map differ
index f4dc44a..916c3ee 100644 (file)
Binary files a/lib/table/amd/build/local/dynamic/selectors.min.js and b/lib/table/amd/build/local/dynamic/selectors.min.js differ
index 73059fc..994561c 100644 (file)
Binary files a/lib/table/amd/build/local/dynamic/selectors.min.js.map and b/lib/table/amd/build/local/dynamic/selectors.min.js.map differ
index c3f264f..a68d7d3 100644 (file)
@@ -249,3 +249,21 @@ export const init = () => {
         }
     });
 };
+
+/**
+ * Fetch the table via its table region id
+ *
+ * @param {String} tableRegionId
+ * @returns {HTMLElement}
+ */
+export const getTableFromId = tableRegionId => {
+    const tableRoot = document.querySelector(Selectors.main.fromRegionId(tableRegionId));
+
+
+    if (!tableRoot) {
+        // The table is not a dynamic table.
+        throw new Error("The table specified is not a dynamic table and cannot be updated");
+    }
+
+    return tableRoot;
+};
index 118f328..8b07a19 100644 (file)
@@ -24,6 +24,7 @@
 export default {
     main: {
         region: '[data-region="core_table/dynamic"]',
+        fromRegionId: regionId => `[data-region="core_table/dynamic"][data-table-uniqueid="${regionId}"]`,
     },
     table: {
         links: {
diff --git a/user/amd/build/repository.min.js b/user/amd/build/repository.min.js
new file mode 100644 (file)
index 0000000..5ff57f0
Binary files /dev/null and b/user/amd/build/repository.min.js differ
diff --git a/user/amd/build/repository.min.js.map b/user/amd/build/repository.min.js.map
new file mode 100644 (file)
index 0000000..7d60e60
Binary files /dev/null and b/user/amd/build/repository.min.js.map differ
index 727cb9a..fe46b00 100644 (file)
Binary files a/user/amd/build/status_field.min.js and b/user/amd/build/status_field.min.js differ
index 5bdddb0..4a1d964 100644 (file)
Binary files a/user/amd/build/status_field.min.js.map and b/user/amd/build/status_field.min.js.map differ
diff --git a/user/amd/src/repository.js b/user/amd/src/repository.js
new file mode 100644 (file)
index 0000000..7be9378
--- /dev/null
@@ -0,0 +1,53 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Module to handle AJAX interactions.
+ *
+ * @module     core_user/repository
+ * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+import {call as fetchMany} from 'core/ajax';
+
+/**
+ * Unenrol the user with the specified user enrolmentid ID.
+ *
+ * @param {Number} userEnrolmentId
+ * @return {Promise}
+ */
+export const unenrolUser = userEnrolmentId => {
+    return fetchMany([{
+        methodname: 'core_enrol_unenrol_user_enrolment',
+        args: {
+            ueid: userEnrolmentId,
+        },
+    }])[0];
+};
+
+/**
+ * Submit the user enrolment form with the specified form data.
+ *
+ * @param {String} formdata
+ * @return {Promise}
+ */
+export const submitUserEnrolmentForm = formdata => {
+    return fetchMany([{
+        methodname: 'core_enrol_submit_user_enrolment_form',
+        args: {
+            formdata,
+        },
+    }])[0];
+};
index 5fdca82..b282cc9 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-import Ajax from 'core/ajax';
+import * as DynamicTable from 'core_table/dynamic';
+import * as Repository from './repository';
+import * as Str from 'core/str';
+import DynamicTableSelectors from 'core_table/local/dynamic/selectors';
 import Fragment from 'core/fragment';
 import ModalEvents from 'core/modal_events';
 import ModalFactory from 'core/modal_factory';
 import Notification from 'core/notification';
-import * as Str from 'core/str';
 import Templates from 'core/templates';
-import jQuery from 'jquery';
 
 const Selectors = {
     editEnrolment: '[data-action="editenrolment"]',
     showDetails: '[data-action="showdetails"]',
-    unenrol: '[data-action="unenrol"]'
+    unenrol: '[data-action="unenrol"]',
+    statusElement: '[data-status]',
 };
 
-class StatusFieldActions {
-    /**
-     * Constructor
-     *
-     * @param {Object} options Object containing options. The only valid option at this time is contextid.
-     * Each call to templates.render gets it's own instance of this class.
-     */
-    constructor(options) {
-        this.contextid = options.contextid;
-        this.courseid = options.courseid;
-
-        // Bind click event to editenrol buttons.
-        this.bindEditEnrol();
-
-        // Bind click event to unenrol buttons.
-        this.bindUnenrol();
-
-        // Bind click event to status details buttons.
-        this.bindStatusDetails();
-    }
+/**
+ * Get the dynamic table from the specified link.
+ *
+ * @param {HTMLElement} link
+ * @returns {HTMLElement}
+ */
+const getDynamicTableFromLink = link => link.closest(DynamicTableSelectors.main.region);
+
+/**
+ * Get the status container from the specified link.
+ *
+ * @param {HTMLElement} link
+ * @returns {HTMLElement}
+ */
+const getStatusContainer = link => link.closest(Selectors.statusElement);
+
+/**
+ * Get user enrolment id from the specified link
+ *
+ * @param {HTMLElement} link
+ * @returns {Number}
+ */
+const getUserEnrolmentIdFromLink = link => link.getAttribute('rel');
 
-    bindEditEnrol() {
-        var statusFieldInstsance = this;
+/**
+ * Register all event listeners for the status fields.
+ *
+ * @param {Number} contextId
+ * @param {Number} uniqueId
+ */
+const registerEventListeners = (contextId, uniqueId) => {
+    const getBodyFunction = (userEnrolmentId, formData) => getBody(contextId, userEnrolmentId, formData);
 
-        jQuery(Selectors.editEnrolment).click(function(e) {
+    document.addEventListener('click', e => {
+        const tableRoot = e.target.closest(DynamicTableSelectors.main.fromRegionId(uniqueId));
+        if (!tableRoot) {
+            return;
+        }
+
+        const editLink = e.target.closest(Selectors.editEnrolment);
+        if (editLink) {
             e.preventDefault();
 
-            // The particular edit button that was clicked.
-            var clickedEditTrigger = jQuery(this);
-            // Get the parent container (it contains the data attributes associated with the status field).
-            var parentContainer = clickedEditTrigger.parent();
-            // Get the name of the user whose enrolment status is being edited.
-            var fullname = parentContainer.data('fullname');
-            // Get the user enrolment ID.
-            var ueid = clickedEditTrigger.attr('rel');
-
-            jQuery.when(Str.get_string('edituserenrolment', 'enrol', fullname)).then(function(modalTitle) {
-                return ModalFactory.create({
-                    large: true,
-                    title: modalTitle,
-                    type: ModalFactory.types.SAVE_CANCEL
-                });
-            }).done(function(modal) {
-                // Handle save event.
-                modal.getRoot().on(ModalEvents.save, function(e) {
-                    // Don't close the modal yet.
-                    e.preventDefault();
-                    // Submit form data.
-                    statusFieldInstsance.submitEditFormAjax(modal);
-                });
-
-                // Handle hidden event.
-                modal.getRoot().on(ModalEvents.hidden, function() {
-                    // Destroy when hidden.
-                    modal.destroy();
-                });
-
-                // Set the modal body.
-                modal.setBody(statusFieldInstsance.getBody(ueid));
-
-                // Show the modal!
-                modal.show();
-            }).fail(Notification.exception);
-        });
-    }
+            showEditDialogue(editLink, getBodyFunction);
+        }
 
-    bindUnenrol() {
-        var statusFieldInstsance = this;
+        const unenrolLink = e.target.closest(Selectors.unenrol);
+        if (unenrolLink) {
+            e.preventDefault();
+
+            showUnenrolConfirmation(unenrolLink);
+        }
 
-        jQuery(Selectors.unenrol).click(function(e) {
+        const showDetailsLink = e.target.closest(Selectors.showDetails);
+        if (showDetailsLink) {
             e.preventDefault();
-            var unenrolLink = jQuery(this);
-            var parentContainer = unenrolLink.parent();
-            var strings = [
-                {
-                    key: 'unenrol',
-                    component: 'enrol'
-                },
-                {
-                    key: 'unenrolconfirm',
-                    component: 'enrol',
-                    param: {
-                        user: parentContainer.data('fullname'),
-                        course: parentContainer.data('coursename'),
-                        enrolinstancename: parentContainer.data('enrolinstancename')
-                    }
-                }
-            ];
 
-            var deleteModalPromise = ModalFactory.create({
-                type: ModalFactory.types.SAVE_CANCEL
-            });
+            showStatusDetails(showDetailsLink);
+        }
+    });
+};
+
+/**
+ * Show the edit dialogue.
+ *
+ * @param {HTMLElement} link
+ * @param {Function} getBody Function to get the body for the specified user enrolment
+ */
+const showEditDialogue = (link, getBody) => {
+    const container = getStatusContainer(link);
+    const userEnrolmentId = getUserEnrolmentIdFromLink(link);
+
+    ModalFactory.create({
+        large: true,
+        title: Str.get_string('edituserenrolment', 'enrol', container.dataset.fullname),
+        type: ModalFactory.types.SAVE_CANCEL,
+        body: getBody(userEnrolmentId)
+    })
+    .then(modal => {
+        // Handle save event.
+        modal.getRoot().on(ModalEvents.save, e => {
+            // Don't close the modal yet.
+            e.preventDefault();
 
-            jQuery.when(Str.get_strings(strings), deleteModalPromise).done(function(results, modal) {
-                var title = results[0];
-                var confirmMessage = results[1];
-                modal.setTitle(title);
-                modal.setBody(confirmMessage);
-                modal.setSaveButtonText(title);
-
-                // Handle confirm event.
-                modal.getRoot().on(ModalEvents.save, function() {
-                    // Build params.
-                    var unenrolParams = {
-                        'ueid': jQuery(unenrolLink).attr('rel')
-                    };
-                    // Don't close the modal yet.
-                    e.preventDefault();
-                    // Submit data.
-                    statusFieldInstsance.submitUnenrolFormAjax(modal, unenrolParams);
-                });
-
-                // Handle hidden event.
-                modal.getRoot().on(ModalEvents.hidden, function() {
-                    // Destroy when hidden.
-                    modal.destroy();
-                });
-
-                // Display the delete confirmation modal.
-                modal.show();
-            }).fail(Notification.exception);
+            // Submit form data.
+            submitEditFormAjax(link, getBody, modal, userEnrolmentId);
         });
-    }
 
-    bindStatusDetails() {
-        jQuery(Selectors.showDetails).click(function(e) {
+        // Handle hidden event.
+        modal.getRoot().on(ModalEvents.hidden, () => {
+            // Destroy when hidden.
+            modal.destroy();
+        });
+
+        // Show the modal.
+        modal.show();
+
+        return modal;
+    })
+    .catch(Notification.exception);
+};
+
+/**
+ * Show and handle the unenrolment confirmation dialogue.
+ *
+ * @param {HTMLElement} link
+ */
+const showUnenrolConfirmation = link => {
+    const container = getStatusContainer(link);
+    const userEnrolmentId = getUserEnrolmentIdFromLink(link);
+
+    ModalFactory.create({
+        type: ModalFactory.types.SAVE_CANCEL,
+    })
+    .then(modal => {
+        // Handle confirm event.
+        modal.getRoot().on(ModalEvents.save, e => {
+            // Don't close the modal yet.
             e.preventDefault();
 
-            var detailsButton = jQuery(this);
-            var parentContainer = detailsButton.parent();
-            var context = {
-                "fullname": parentContainer.data('fullname'),
-                "coursename": parentContainer.data('coursename'),
-                "enrolinstancename": parentContainer.data('enrolinstancename'),
-                "status": parentContainer.data('status'),
-                "statusclass": parentContainer.find('span').attr('class'),
-                "timestart": parentContainer.data('timestart'),
-                "timeend": parentContainer.data('timeend'),
-                "timeenrolled": parentContainer.data('timeenrolled')
-            };
-
-            // Get default string for the modal and modal type.
-            var strings = [
-                {
-                    key: 'enroldetails',
-                    component: 'enrol'
+            // Submit data.
+            submitUnenrolFormAjax(
+                link,
+                modal, {
+                    ueid: userEnrolmentId,
                 }
-            ];
+            );
+        });
 
-            // Find the edit enrolment link.
-            var editEnrolLink = detailsButton.next(Selectors.editEnrolment);
-            if (editEnrolLink.length) {
-                // If there's an edit enrolment link for this user, clone it into the context for the modal.
-                context.editenrollink = jQuery('<div>').append(editEnrolLink.clone()).html();
-            }
+        // Handle hidden event.
+        modal.getRoot().on(ModalEvents.hidden, () => {
+            // Destroy when hidden.
+            modal.destroy();
+        });
 
-            var modalStringsPromise = Str.get_strings(strings);
-            var modalPromise = ModalFactory.create({large: true, type: ModalFactory.types.CANCEL});
-            jQuery.when(modalStringsPromise, modalPromise).done(function(strings, modal) {
-                var modalBodyPromise = Templates.render('core_user/status_details', context);
-                modal.setTitle(strings[0]);
-                modal.setBody(modalBodyPromise);
-
-                if (editEnrolLink.length) {
-                    modal.getRoot().on('click', Selectors.editEnrolment, function(e) {
-                        e.preventDefault();
-                        modal.hide();
-                        // Trigger click event for the edit enrolment link to show the edit enrolment modal.
-                        jQuery(editEnrolLink).trigger('click');
-                    });
+        // Display the delete confirmation modal.
+        modal.show();
+
+        const stringData = [
+            {
+                key: 'unenrol',
+                component: 'enrol',
+            },
+            {
+                key: 'unenrolconfirm',
+                component: 'enrol',
+                param: {
+                    user: container.dataset.fullname,
+                    course: container.dataset.coursename,
+                    enrolinstancename: container.dataset.enrolinstancename,
                 }
+            }
+        ];
+
+        return Promise.all([Str.get_strings(stringData), modal]);
+    })
+    .then(([strings, modal]) => {
+        modal.setTitle(strings[0]);
+        modal.setSaveButtonText(strings[0]);
+        modal.setBody(strings[1]);
+
+        return modal;
+    })
+    .catch(Notification.exception);
+};
 
-                modal.show();
-
-                // Handle hidden event.
-                modal.getRoot().on(ModalEvents.hidden, function() {
-                    // Destroy when hidden.
-                    modal.destroy();
-                });
-            }).fail(Notification.exception);
-        });
+/**
+ * Show the user details dialogue.
+ *
+ * @param {HTMLElement} link
+ */
+const showStatusDetails = link => {
+    const container = getStatusContainer(link);
+
+    const context = {
+        editenrollink: '',
+        statusclass: container.querySelector('span.badge').getAttribute('class'),
+        ...container.dataset,
+    };
+
+    // Find the edit enrolment link.
+    const editEnrolLink = container.querySelector(Selectors.editEnrolment);
+    if (editEnrolLink) {
+        // If there's an edit enrolment link for this user, clone it into the context for the modal.
+        context.editenrollink = editEnrolLink.outerHTML;
     }
 
-    submitEditFormAjax(modal) {
-        var statusFieldInstsance = this;
-        var form = modal.getRoot().find('form');
+    ModalFactory.create({
+        large: true,
+        type: ModalFactory.types.CANCEL,
+        title: Str.get_string('enroldetails', 'enrol'),
+        body: Templates.render('core_user/status_details', context),
+    })
+    .then(modal => {
+        if (editEnrolLink) {
+            modal.getRoot().on('click', Selectors.editEnrolment, e => {
+                e.preventDefault();
+                modal.hide();
 
-        // User enrolment ID.
-        var ueid = jQuery(form).find('[name="ue"]').val();
+                // Trigger click event for the edit enrolment link to show the edit enrolment modal.
+                editEnrolLink.click();
+            });
+        }
 
-        var request = {
-            methodname: 'core_enrol_submit_user_enrolment_form',
-            args: {
-                formdata: form.serialize()
-            }
-        };
+        modal.show();
 
-        Ajax.call([request])[0].done(function(data) {
-            if (data.result) {
-                // Dismiss the modal.
-                modal.hide();
+        // Handle hidden event.
+        modal.getRoot().on(ModalEvents.hidden, () => modal.destroy());
 
-                // Reload the page, don't show changed data warnings.
-                if (typeof window.M.core_formchangechecker !== "undefined") {
-                    window.M.core_formchangechecker.reset_form_dirty_state();
-                }
-                window.location.reload();
-            } else {
-                // Serialise the form data and reload the form fragment to show validation errors.
-                var formData = JSON.stringify(form.serialize());
-                modal.setBody(statusFieldInstsance.getBody(ueid, formData));
-            }
-        }).fail(Notification.exception);
-    }
+        return modal;
+    })
+    .catch(Notification.exception);
+};
 
-    submitUnenrolFormAjax(modal, unenrolParams) {
-        var request = {
-            methodname: 'core_enrol_unenrol_user_enrolment',
-            args: unenrolParams
-        };
+/**
+ * Submit the edit dialogue.
+ *
+ * @param {HTMLElement} clickedLink
+ * @param {Function} getBody
+ * @param {Object} modal
+ * @param {Number} userEnrolmentId
+ */
+const submitEditFormAjax = (clickedLink, getBody, modal, userEnrolmentId) => {
+    const form = modal.getRoot().find('form');
 
-        Ajax.call([request])[0].done(function(data) {
-            if (data.result) {
-                // Dismiss the modal.
-                modal.hide();
+    Repository.submitUserEnrolmentForm(form.serialize())
+    .then(data => {
+        if (!data.result) {
+            throw data.result;
+        }
 
-                // Reload the page, don't show changed data warnings.
-                if (typeof window.M.core_formchangechecker !== "undefined") {
-                    window.M.core_formchangechecker.reset_form_dirty_state();
-                }
-                window.location.reload();
-            } else {
-                // Display an alert containing the error message
-                Notification.alert(data.errors[0].key, data.errors[0].message);
-            }
-        }).fail(Notification.exception);
-    }
+        // Dismiss the modal.
+        modal.hide();
+        modal.destroy();
+
+        DynamicTable.refreshTableContent(getDynamicTableFromLink(clickedLink));
+
+        return data;
+    })
+    .catch(() => {
+        modal.setBody(getBody(userEnrolmentId, JSON.stringify(form.serialize())));
 
-    getBody(ueid, formData) {
-        var params = {
-            'ueid': ueid
-        };
-        if (typeof formData !== 'undefined') {
-            params.formdata = formData;
+        return modal;
+    });
+};
+
+/**
+ * Submit the unenrolment form.
+ *
+ * @param {HTMLElement} clickedLink
+ * @param {Object} modal
+ * @param {Object} args
+ */
+const submitUnenrolFormAjax = (clickedLink, modal, args) => {
+    Repository.unenrolUser(args.ueid)
+    .then(data => {
+        if (!data.result) {
+            // Display an alert containing the error message
+            Notification.alert(data.errors[0].key, data.errors[0].message);
+
+            return data;
         }
-        return Fragment.loadFragment('enrol', 'user_enrolment_form', this.contextid, params).fail(Notification.exception);
+
+        // Dismiss the modal.
+        modal.hide();
+        modal.destroy();
+
+        DynamicTable.refreshTableContent(getDynamicTableFromLink(clickedLink));
+
+        return data;
+    })
+    .catch(Notification.exception);
+};
+
+/**
+ * Get the body fragment.
+ *
+ * @param {Number} contextId
+ * @param {Number} ueid The user enrolment id
+ * @param {Object} formdata
+ * @returns {Promise}
+ */
+const getBody = (contextId, ueid, formdata = null) => Fragment.loadFragment(
+    'enrol',
+    'user_enrolment_form',
+    contextId,
+    {
+        ueid,
+        formdata,
     }
-}
+);
 
-export const init = config => {
-    new StatusFieldActions(config);
+/**
+ * Initialise the statu field handler.
+ *
+ * @param {Number} contextid
+ * @param {Number} uniqueid
+ */
+export const init = ({contextid, uniqueid}) => {
+    registerEventListeners(contextid, uniqueid);
 };
index 2f9c5ca..b1f4395 100644 (file)
@@ -239,7 +239,10 @@ class participants extends \table_sql implements dynamic_table {
         parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
 
         if (has_capability('moodle/course:enrolreview', $this->context)) {
-            $params = ['contextid' => $this->context->id, 'courseid' => $this->course->id];
+            $params = [
+                'contextid' => $this->context->id,
+                'uniqueid' => $this->uniqueid,
+            ];
             $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
         }
     }