MDL-68409 js: Convert core/notification to ES6
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 14 Apr 2020 01:08:06 +0000 (09:08 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 4 May 2020 07:07:49 +0000 (15:07 +0800)
lib/amd/build/notification.min.js
lib/amd/build/notification.min.js.map
lib/amd/src/notification.js

index 4e559a2..2729ff1 100644 (file)
Binary files a/lib/amd/build/notification.min.js and b/lib/amd/build/notification.min.js differ
index a7dba21..106bfa7 100644 (file)
Binary files a/lib/amd/build/notification.min.js.map and b/lib/amd/build/notification.min.js.map differ
index 3bbe59f..8abb41a 100644 (file)
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+import Pending from 'core/pending';
+import Log from 'core/log';
+
+let currentContextId = M.cfg.contextid;
+
+const notificationTypes = {
+    success:  'core/notification_success',
+    info:     'core/notification_info',
+    warning:  'core/notification_warning',
+    error:    'core/notification_error',
+};
+
+const notificationRegionId = 'user-notifications';
+
+const Selectors = {
+    notificationRegion: `#${notificationRegionId}`,
+    fallbackRegionParents: [
+        '#region-main',
+        '[role="main"]',
+        'body',
+    ],
+};
+
+const setupTargetRegion = () => {
+    let targetRegion = getNotificationRegion();
+    if (targetRegion) {
+        return false;
+    }
+
+    const newRegion = document.createElement('span');
+    newRegion.id = notificationRegionId;
+
+    return Selectors.fallbackRegionParents.some(selector => {
+        const targetRegion = document.querySelector(selector);
+
+        if (targetRegion) {
+            targetRegion.prepend(newRegion);
+            return true;
+        }
+
+        return false;
+    });
+};
+
+
 /**
- * A system for displaying notifications to users from the session.
+ * Poll the server for any new notifications.
  *
- * Wrapper for the YUI M.core.notification class. Allows us to
- * use the YUI version in AMD code until it is replaced.
- *
- * @module     core/notification
- * @class      notification
- * @package    core
- * @copyright  2015 Damyon Wiese <damyon@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since      2.9
+ * @returns {Promise}
  */
-define(['core/yui', 'jquery', 'core/log', 'core/pending'],
-function(Y, $, log, Pending) {
-    var notificationModule = {
-        types: {
-            'success':  'core/notification_success',
-            'info':     'core/notification_info',
-            'warning':  'core/notification_warning',
-            'error':    'core/notification_error',
-        },
+export const fetchNotifications = async() => {
+    const Ajax = await import('core/ajax');
 
-        fieldName: 'user-notifications',
-
-        fetchNotifications: function() {
-            var pendingPromise = new Pending('core/notification:fetchNotifications');
-
-            require(['core/ajax'], function(ajax) {
-                var promises = ajax.call([{
-                    methodname: 'core_fetch_notifications',
-                    args: {
-                        contextid: notificationModule.contextid
-                    }
-                }]);
-
-                // This currently fails when not logged in.
-                // eslint-disable-next-line promise/catch-or-return
-                promises[0]
-                .then(notificationModule.addNotifications)
-                .always(pendingPromise.resolve);
-            });
-        },
+    return Ajax.call([{
+        methodname: 'core_fetch_notifications',
+        args: {
+            contextid: currentContextId
+        }
+    }])[0]
+    .then(addNotifications);
+};
 
-        addNotifications: function(notifications) {
-            var pendingPromise = new Pending('core/notification:addNotifications');
+/**
+ * Add all of the supplied notifications.
+ *
+ * @param {Array} notifications The list of notificaitons
+ * @returns {Promise}
+ */
+const addNotifications = notifications => {
+    if (!notifications.length) {
+        return Promise.resolve();
+    }
 
-            if (!notifications) {
-                notifications = [];
-            }
+    const pendingPromise = new Pending('core/notification:addNotifications');
+    notifications.forEach(notification => renderNotification(notification.template, notification.variables));
 
-            $.each(notifications, function(i, notification) {
-                notificationModule.renderNotification(notification.template, notification.variables);
-            });
+    return pendingPromise.resolve();
+};
 
-            pendingPromise.resolve();
-        },
+/**
+ * Add a notification to the page.
+ *
+ * Note: This does not cause the notification to be added to the session.
+ *
+ * @param {Object}  notification                The notification to add.
+ * @param {string}  notification.message        The body of the notification
+ * @param {string}  notification.type           The type of notification to add (error, warning, info, success).
+ * @param {Boolean} notification.closebutton    Whether to show the close button.
+ * @param {Boolean} notification.announce       Whether to announce to screen readers.
+ * @returns {Promise}
+ */
+export const addNotification = notification => {
+    const pendingPromise = new Pending('core/notification:addNotifications');
 
-        setupTargetRegion: function() {
-            var targetRegion = $('#' + notificationModule.fieldName);
-            if (targetRegion.length) {
-                return false;
-            }
+    let template = notificationTypes.error;
 
-            var newRegion = $('<span>').attr('id', notificationModule.fieldName);
+    notification = {
+        closebutton:    true,
+        announce:       true,
+        type:           'error',
+        ...notification,
+    };
 
-            targetRegion = $('#region-main');
-            if (targetRegion.length) {
-                return targetRegion.prepend(newRegion);
-            }
+    if (notification.template) {
+        template = notification.template;
+        delete notification.template;
+    } else if (notification.type) {
+        if (typeof notificationTypes[notification.type] !== 'undefined') {
+            template = notificationTypes[notification.type];
+        }
+        delete notification.type;
+    }
 
-            targetRegion = $('[role="main"]');
-            if (targetRegion.length) {
-                return targetRegion.prepend(newRegion);
-            }
+    return renderNotification(template, notification)
+    .then(pendingPromise.resolve);
+};
 
-            targetRegion = $('body');
-            return targetRegion.prepend(newRegion);
-        },
+const renderNotification = async(template, variables) => {
+    if (typeof variables.message === 'undefined' || !variables.message) {
+        Log.debug('Notification received without content. Skipping.');
+        return;
+    }
 
-        addNotification: function(notification) {
-            var pendingPromise = new Pending('core/notification:addNotifications');
-
-            var template = notificationModule.types.error;
-
-            notification = $.extend({
-                closebutton:    true,
-                announce:       true,
-                type:           'error'
-            }, notification);
-
-            if (notification.template) {
-                template = notification.template;
-                delete notification.template;
-            } else if (notification.type) {
-                if (typeof notificationModule.types[notification.type] !== 'undefined') {
-                    template = notificationModule.types[notification.type];
-                }
-                delete notification.type;
-            }
+    const pendingPromise = new Pending('core/notification:renderNotification');
+    const Templates = await import('core/templates');
 
-            pendingPromise.resolve();
+    Templates.renderForPromise(template, variables)
+    .then(({html, js = ''}) => {
+        Templates.prependNodeContents(getNotificationRegion(), html, js);
 
-            return notificationModule.renderNotification(template, notification);
-        },
+        return;
+    })
+    .then(pendingPromise.resolve)
+    .catch(exception);
+};
 
-        renderNotification: function(template, variables) {
-            if (typeof variables.message === 'undefined' || !variables.message) {
-                log.debug('Notification received without content. Skipping.');
-                return;
-            }
+const getNotificationRegion = () => document.querySelector(Selectors.notificationRegion);
 
-            var pendingPromise = new Pending('core/notification:renderNotification');
+/**
+ * Alert dialogue.
+ *
+ * @param {String|Promise} title
+ * @param {String|Promise} message
+ * @param {String|Promise} cancelText
+ * @returns {Promise}
+ */
+export const alert = async(title, message, cancelText) => {
+    var pendingPromise = new Pending('core/notification:alert');
 
-            require(['core/templates'], function(templates) {
-                templates.render(template, variables)
-                .then(function(html, js) {
-                    $('#' + notificationModule.fieldName).prepend(html);
-                    templates.runTemplateJS(js);
+    const ModalFactory = await import('core/modal_factory');
 
-                    return;
-                })
-                .always(pendingPromise.resolve)
-                .catch(notificationModule.exception);
-            });
+    return ModalFactory.create({
+        type: ModalFactory.types.ALERT,
+        body: message,
+        title: title,
+        buttons: {
+            cancel: cancelText,
         },
+        removeOnClose: true,
+    })
+    .then(function(modal) {
+        modal.show();
 
-        alert: function(title, message, yesLabel) {
-            var pendingPromise = new Pending('core/notification:alert');
-
-            // Here we are wrapping YUI. This allows us to start transitioning, but
-            // wait for a good alternative without having inconsistent dialogues.
-            Y.use('moodle-core-notification-alert', function() {
-                var alert = new M.core.alert({
-                    title: title,
-                    message: message,
-                    yesLabel: yesLabel
-                });
+        pendingPromise.resolve();
+        return modal;
+    });
+};
 
-                alert.show();
-
-                pendingPromise.resolve();
-            });
+/**
+ * The confirm has now been replaced with a save and cancel dialogue.
+ *
+ * @param {String|Promise} title
+ * @param {String|Promise} question
+ * @param {String|Promise} saveLabel
+ * @param {String|Promise} saveCallback
+ * @param {String|Promise} cancelCallback
+ * @returns {Promise}
+ */
+export const saveCancel = async(title, question, saveLabel, saveCallback, cancelCallback) => {
+    const pendingPromise = new Pending('core/notification:confirm');
+
+    const [
+        ModalFactory,
+        ModalEvents,
+    ] = await Promise.all([
+        import('core/modal_factory'),
+        import('core/modal_events'),
+    ]);
+
+    return ModalFactory.create({
+        type: ModalFactory.types.SAVE_CANCEL,
+        title: title,
+        body: question,
+        buttons: {
+            // Note: The noLabel is no longer supported.
+            save: saveLabel,
         },
+        removeOnClose: true,
+    })
+    .then(function(modal) {
+        modal.show();
 
-        confirm: function(title, question, yesLabel, noLabel, yesCallback, noCallback) {
-            var pendingPromise = new Pending('core/notification:confirm');
-
-            // Here we are wrapping YUI. This allows us to start transitioning, but
-            // wait for a good alternative without having inconsistent dialogues.
-            Y.use('moodle-core-notification-confirm', function() {
-                var modal = new M.core.confirm({
-                    title: title,
-                    question: question,
-                    yesLabel: yesLabel,
-                    noLabel: noLabel
-                });
-
-                modal.on('complete-yes', function() {
-                    yesCallback();
-                });
-                if (noCallback) {
-                    modal.on('complete-no', function() {
-                        noCallback();
-                    });
-                }
-                modal.show();
-
-                pendingPromise.resolve();
-            });
-        },
+        modal.getRoot().on(ModalEvents.save, saveCallback);
+        modal.getRoot().on(ModalEvents.cancel, cancelCallback);
+        pendingPromise.resolve();
 
-        exception: function(ex) {
-            var pendingPromise = new Pending('core/notification:addNotifications');
+        return modal;
+    });
+};
 
-            // Fudge some parameters.
-            if (typeof ex.stack == 'undefined') {
-                ex.stack = '';
-            }
-            if (ex.debuginfo) {
-                ex.stack += ex.debuginfo + '\n';
-            }
-            if (!ex.backtrace && ex.stacktrace) {
-                ex.backtrace = ex.stacktrace;
-            }
-            if (ex.backtrace) {
-                ex.stack += ex.backtrace;
-                var ln = ex.backtrace.match(/line ([^ ]*) of/);
-                var fn = ex.backtrace.match(/ of ([^:]*): /);
-                if (ln && ln[1]) {
-                    ex.lineNumber = ln[1];
-                }
-                if (fn && fn[1]) {
-                    ex.fileName = fn[1];
-                    if (ex.fileName.length > 30) {
-                        ex.fileName = '...' + ex.fileName.substr(ex.fileName.length - 27);
-                    }
-                }
-            }
-            if (typeof ex.name == 'undefined' && ex.errorcode) {
-                ex.name = ex.errorcode;
+/**
+ * Wrap M.core.exception.
+ *
+ * @param {Error} ex
+ */
+export const exception = async ex => {
+    const pendingPromise = new Pending('core/notification:displayException');
+
+    // Fudge some parameters.
+    if (!ex.stack) {
+        ex.stack = '';
+    }
+
+    if (ex.debuginfo) {
+        ex.stack += ex.debuginfo + '\n';
+    }
+
+    if (!ex.backtrace && ex.stacktrace) {
+        ex.backtrace = ex.stacktrace;
+    }
+
+    if (ex.backtrace) {
+        ex.stack += ex.backtrace;
+        const ln = ex.backtrace.match(/line ([^ ]*) of/);
+        const fn = ex.backtrace.match(/ of ([^:]*): /);
+        if (ln && ln[1]) {
+            ex.lineNumber = ln[1];
+        }
+        if (fn && fn[1]) {
+            ex.fileName = fn[1];
+            if (ex.fileName.length > 30) {
+                ex.fileName = '...' + ex.fileName.substr(ex.fileName.length - 27);
             }
-
-            Y.use('moodle-core-notification-exception', function() {
-                var modal = new M.core.exception(ex);
-
-                modal.show();
-
-                pendingPromise.resolve();
-            });
         }
-    };
+    }
 
-    return /** @alias module:core/notification */{
-        init: function(contextid, notifications) {
-            notificationModule.contextid = contextid;
+    if (typeof ex.name === 'undefined' && ex.errorcode) {
+        ex.name = ex.errorcode;
+    }
 
-            // Setup the message target region if it isn't setup already
-            notificationModule.setupTargetRegion();
+    const Y = await import('core/yui');
+    Y.use('moodle-core-notification-exception', function() {
+        var modal = new M.core.exception(ex);
 
-            // Add provided notifications.
-            notificationModule.addNotifications(notifications);
+        modal.show();
 
-            // Poll for any new notifications.
-            notificationModule.fetchNotifications();
-        },
+        pendingPromise.resolve();
+    });
+};
 
-        /**
-         * Poll the server for any new notifications.
-         *
-         * @method fetchNotifications
-         */
-        fetchNotifications: notificationModule.fetchNotifications,
-
-        /**
-         * Add a notification to the page.
-         *
-         * Note: This does not cause the notification to be added to the session.
-         *
-         * @method addNotification
-         * @param {Object}  notification                The notification to add.
-         * @param {string}  notification.message        The body of the notification
-         * @param {string}  notification.type           The type of notification to add (error, warning, info, success).
-         * @param {Boolean} notification.closebutton    Whether to show the close button.
-         * @param {Boolean} notification.announce       Whether to announce to screen readers.
-         */
-        addNotification: notificationModule.addNotification,
-
-        /**
-         * Wrap M.core.alert.
-         *
-         * @method alert
-         * @param {string} title
-         * @param {string} message
-         * @param {string} yesLabel
-         */
-        alert: notificationModule.alert,
-
-        /**
-         * Wrap M.core.confirm.
-         *
-         * @method confirm
-         * @param {string} title
-         * @param {string} question
-         * @param {string} yesLabel
-         * @param {string} noLabel
-         * @param {function} yesCallback
-         * @param {function} noCallback Optional parameter to be called if the user presses cancel.
-         */
-        confirm: notificationModule.confirm,
-
-        /**
-         * Wrap M.core.exception.
-         *
-         * @method exception
-         * @param {Error} ex
-         */
-        exception: notificationModule.exception
-    };
-});
+/**
+ * Initialise the page for the suppled context, and displaying the supplied notifications.
+ *
+ * @param {Number} contextId
+ * @param {Array} notificationList
+ */
+export const init = (contextId, notificationList) => {
+    currentContextId = contextId;
+
+    // Setup the message target region if it isn't setup already
+    setupTargetRegion();
+
+    // Add provided notifications.
+    addNotifications(notificationList);
+
+    // Perform an initial poll for any new notifications.
+    fetchNotifications();
+};
+
+// To maintain backwards compatability we export default here.
+export default {
+    init,
+    fetchNotifications,
+    addNotification,
+    alert,
+    confirm,
+    saveCancel,
+    exception,
+};