MDL-68409 js: Convert core/notification to ES6

This commit is contained in:
Andrew Nicols 2020-04-14 09:08:06 +08:00
parent b810c84f2b
commit e8df743b3b
3 changed files with 271 additions and 262 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,283 +13,292 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A system for displaying notifications to users from the session.
*
* 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
*/
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',
},
import Pending from 'core/pending';
import Log from 'core/log';
fieldName: 'user-notifications',
let currentContextId = M.cfg.contextid;
fetchNotifications: function() {
var pendingPromise = new Pending('core/notification:fetchNotifications');
const notificationTypes = {
success: 'core/notification_success',
info: 'core/notification_info',
warning: 'core/notification_warning',
error: 'core/notification_error',
};
require(['core/ajax'], function(ajax) {
var promises = ajax.call([{
methodname: 'core_fetch_notifications',
args: {
contextid: notificationModule.contextid
}
}]);
const notificationRegionId = 'user-notifications';
// This currently fails when not logged in.
// eslint-disable-next-line promise/catch-or-return
promises[0]
.then(notificationModule.addNotifications)
.always(pendingPromise.resolve);
});
},
const Selectors = {
notificationRegion: `#${notificationRegionId}`,
fallbackRegionParents: [
'#region-main',
'[role="main"]',
'body',
],
};
addNotifications: function(notifications) {
var pendingPromise = new Pending('core/notification:addNotifications');
const setupTargetRegion = () => {
let targetRegion = getNotificationRegion();
if (targetRegion) {
return false;
}
if (!notifications) {
notifications = [];
}
const newRegion = document.createElement('span');
newRegion.id = notificationRegionId;
$.each(notifications, function(i, notification) {
notificationModule.renderNotification(notification.template, notification.variables);
});
return Selectors.fallbackRegionParents.some(selector => {
const targetRegion = document.querySelector(selector);
pendingPromise.resolve();
},
setupTargetRegion: function() {
var targetRegion = $('#' + notificationModule.fieldName);
if (targetRegion.length) {
return false;
}
var newRegion = $('<span>').attr('id', notificationModule.fieldName);
targetRegion = $('#region-main');
if (targetRegion.length) {
return targetRegion.prepend(newRegion);
}
targetRegion = $('[role="main"]');
if (targetRegion.length) {
return targetRegion.prepend(newRegion);
}
targetRegion = $('body');
return targetRegion.prepend(newRegion);
},
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;
}
pendingPromise.resolve();
return notificationModule.renderNotification(template, notification);
},
renderNotification: function(template, variables) {
if (typeof variables.message === 'undefined' || !variables.message) {
log.debug('Notification received without content. Skipping.');
return;
}
var pendingPromise = new Pending('core/notification:renderNotification');
require(['core/templates'], function(templates) {
templates.render(template, variables)
.then(function(html, js) {
$('#' + notificationModule.fieldName).prepend(html);
templates.runTemplateJS(js);
return;
})
.always(pendingPromise.resolve)
.catch(notificationModule.exception);
});
},
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
});
alert.show();
pendingPromise.resolve();
});
},
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();
});
},
exception: function(ex) {
var pendingPromise = new Pending('core/notification:addNotifications');
// 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;
}
Y.use('moodle-core-notification-exception', function() {
var modal = new M.core.exception(ex);
modal.show();
pendingPromise.resolve();
});
if (targetRegion) {
targetRegion.prepend(newRegion);
return true;
}
return false;
});
};
/**
* Poll the server for any new notifications.
*
* @returns {Promise}
*/
export const fetchNotifications = async() => {
const Ajax = await import('core/ajax');
return Ajax.call([{
methodname: 'core_fetch_notifications',
args: {
contextid: currentContextId
}
}])[0]
.then(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();
}
const pendingPromise = new Pending('core/notification:addNotifications');
notifications.forEach(notification => renderNotification(notification.template, notification.variables));
return 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');
let template = notificationTypes.error;
notification = {
closebutton: true,
announce: true,
type: 'error',
...notification,
};
return /** @alias module:core/notification */{
init: function(contextid, notifications) {
notificationModule.contextid = contextid;
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;
}
// Setup the message target region if it isn't setup already
notificationModule.setupTargetRegion();
return renderNotification(template, notification)
.then(pendingPromise.resolve);
};
// Add provided notifications.
notificationModule.addNotifications(notifications);
const renderNotification = async(template, variables) => {
if (typeof variables.message === 'undefined' || !variables.message) {
Log.debug('Notification received without content. Skipping.');
return;
}
// Poll for any new notifications.
notificationModule.fetchNotifications();
const pendingPromise = new Pending('core/notification:renderNotification');
const Templates = await import('core/templates');
Templates.renderForPromise(template, variables)
.then(({html, js = ''}) => {
Templates.prependNodeContents(getNotificationRegion(), html, js);
return;
})
.then(pendingPromise.resolve)
.catch(exception);
};
const getNotificationRegion = () => document.querySelector(Selectors.notificationRegion);
/**
* 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');
const ModalFactory = await import('core/modal_factory');
return ModalFactory.create({
type: ModalFactory.types.ALERT,
body: message,
title: title,
buttons: {
cancel: cancelText,
},
removeOnClose: true,
})
.then(function(modal) {
modal.show();
/**
* Poll the server for any new notifications.
*
* @method fetchNotifications
*/
fetchNotifications: notificationModule.fetchNotifications,
pendingPromise.resolve();
return modal;
});
};
/**
* 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,
/**
* 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');
/**
* Wrap M.core.alert.
*
* @method alert
* @param {string} title
* @param {string} message
* @param {string} yesLabel
*/
alert: notificationModule.alert,
const [
ModalFactory,
ModalEvents,
] = await Promise.all([
import('core/modal_factory'),
import('core/modal_events'),
]);
/**
* 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,
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();
/**
* Wrap M.core.exception.
*
* @method exception
* @param {Error} ex
*/
exception: notificationModule.exception
};
});
modal.getRoot().on(ModalEvents.save, saveCallback);
modal.getRoot().on(ModalEvents.cancel, cancelCallback);
pendingPromise.resolve();
return modal;
});
};
/**
* 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);
}
}
}
if (typeof ex.name === 'undefined' && ex.errorcode) {
ex.name = ex.errorcode;
}
const Y = await import('core/yui');
Y.use('moodle-core-notification-exception', function() {
var modal = new M.core.exception(ex);
modal.show();
pendingPromise.resolve();
});
};
/**
* 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,
};