first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,11 @@
/**
* Controls the content area of the notification area on the
* notification page.
*
* @module message_popup/notification_area_content_area
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("message_popup/notification_area_content_area",["jquery","core/templates","core/notification","core/custom_interaction_events","message_popup/notification_repository","message_popup/notification_area_events"],(function($,Templates,DebugNotification,CustomEvents,NotificationRepo,NotificationAreaEvents){var SELECTORS={CONTAINER:'[data-region="notification-area"]',CONTENT:'[data-region="content"]',HEADER:'[data-region="header"]',FOOTER:'[data-region="footer"]',TOGGLE_MODE:'[data-action="toggle-mode"]'},TEMPLATES_HEADER="message_popup/notification_area_content_area_header",TEMPLATES_CONTENT="message_popup/notification_area_content_area_content",TEMPLATES_FOOTER="message_popup/notification_area_content_area_footer",ContentArea=function(root,userId){this.root=$(root),this.container=this.root.closest(SELECTORS.CONTAINER),this.userId=userId,this.header=this.root.find(SELECTORS.HEADER),this.content=this.root.find(SELECTORS.CONTENT),this.footer=this.root.find(SELECTORS.FOOTER),this.registerEventListeners()};return ContentArea.prototype.getRoot=function(){return this.root},ContentArea.prototype.getContainer=function(){return this.container},ContentArea.prototype.getUserId=function(){return this.userId},ContentArea.prototype.getHeader=function(){return this.header},ContentArea.prototype.getContent=function(){return this.content},ContentArea.prototype.getFooter=function(){return this.footer},ContentArea.prototype.show=function(){this.getContainer().addClass("show-content-area")},ContentArea.prototype.hide=function(){this.getContainer().removeClass("show-content-area")},ContentArea.prototype.setHeaderHTML=function(html){this.getHeader().empty().html(html)},ContentArea.prototype.setContentHTML=function(html){this.getContent().empty().html(html)},ContentArea.prototype.setFooterHTML=function(html){this.getFooter().empty().html(html)},ContentArea.prototype.showNotification=function(notification){var headerPromise=Templates.render(TEMPLATES_HEADER,notification).done(function(html){this.setHeaderHTML(html)}.bind(this)),contentPromise=Templates.render(TEMPLATES_CONTENT,notification).done(function(html){this.setContentHTML(html)}.bind(this)),footerPromise=Templates.render(TEMPLATES_FOOTER,notification).done(function(html){this.setFooterHTML(html)}.bind(this));return $.when(headerPromise,contentPromise,footerPromise).done(function(){this.show(),this.getContainer().trigger(NotificationAreaEvents.notificationShown,[notification])}.bind(this))},ContentArea.prototype.registerEventListeners=function(){CustomEvents.define(this.getRoot(),[CustomEvents.events.activate]),this.getRoot().on(CustomEvents.events.activate,SELECTORS.VIEW_TOGGLE,function(){this.hide()}.bind(this)),this.getContainer().on(NotificationAreaEvents.showNotification,function(e,notification){this.showNotification(notification)}.bind(this))},ContentArea}));
//# sourceMappingURL=notification_area_content_area.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
define("message_popup/notification_area_events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={showNotification:"notification-area-events:showNotification",notificationShown:"notification-area-events:notificationShown"},_exports.default}));
//# sourceMappingURL=notification_area_events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"notification_area_events.min.js","sources":["../src/notification_area_events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the events that can be fired in the notification area on\n * the notifications page.\n *\n * @module message_popup/notification_area_events\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n showNotification: 'notification-area-events:showNotification',\n notificationShown: 'notification-area-events:notificationShown',\n};\n"],"names":["showNotification","notificationShown"],"mappings":"wLAuBe,CACXA,iBAAkB,4CAClBC,kBAAmB"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
/**
* Retrieves notifications from the server.
*
* @module message_popup/notification_repository
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("message_popup/notification_repository",["core/ajax","core/notification"],(function(Ajax,Notification){return{query:function(args){void 0===args.limit&&(args.limit=20),void 0===args.offset&&(args.offset=0);var request={methodname:"message_popup_get_popup_notifications",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},countUnread:function(args){var request={methodname:"message_popup_get_unread_popup_notification_count",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},markAllAsRead:function(args){var request={methodname:"core_message_mark_all_notifications_as_read",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},markAsRead:function(id,timeread){var args={notificationid:id};timeread&&(args.timeread=timeread);var request={methodname:"core_message_mark_notification_read",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise}}}));
//# sourceMappingURL=notification_repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"notification_repository.min.js","sources":["../src/notification_repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Retrieves notifications from the server.\n *\n * @module message_popup/notification_repository\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/ajax', 'core/notification'], function(Ajax, Notification) {\n /**\n * Retrieve a list of notifications from the server.\n *\n * @param {object} args The request arguments\n * @return {object} jQuery promise\n */\n var query = function(args) {\n if (typeof args.limit === 'undefined') {\n args.limit = 20;\n }\n\n if (typeof args.offset === 'undefined') {\n args.offset = 0;\n }\n\n var request = {\n methodname: 'message_popup_get_popup_notifications',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Get the number of unread notifications from the server.\n *\n * @param {object} args The request arguments\n * @return {object} jQuery promise\n */\n var countUnread = function(args) {\n var request = {\n methodname: 'message_popup_get_unread_popup_notification_count',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Mark all notifications for the given user as read.\n *\n * @param {object} args The request arguments:\n * @return {object} jQuery promise\n */\n var markAllAsRead = function(args) {\n var request = {\n methodname: 'core_message_mark_all_notifications_as_read',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Mark a specific notification as read.\n *\n * @param {int} id The notification id\n * @param {int} timeread The read timestamp (optional)\n * @return {object} jQuery promise\n */\n var markAsRead = function(id, timeread) {\n var args = {\n notificationid: id,\n };\n\n if (timeread) {\n args.timeread = timeread;\n }\n\n var request = {\n methodname: 'core_message_mark_notification_read',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n return {\n query: query,\n countUnread: countUnread,\n markAllAsRead: markAllAsRead,\n markAsRead: markAsRead,\n };\n});\n"],"names":["define","Ajax","Notification","query","args","limit","offset","request","methodname","promise","call","fail","exception","countUnread","markAllAsRead","markAsRead","id","timeread","notificationid"],"mappings":";;;;;;;AAsBAA,+CAAO,CAAC,YAAa,sBAAsB,SAASC,KAAMC,oBA8F/C,CACHC,MAxFQ,SAASC,WACS,IAAfA,KAAKC,QACZD,KAAKC,MAAQ,SAGU,IAAhBD,KAAKE,SACZF,KAAKE,OAAS,OAGdC,QAAU,CACVC,WAAY,wCACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH,SAuEPI,YA9Dc,SAAST,UACnBG,QAAU,CACVC,WAAY,oDACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH,SAqDPK,cA5CgB,SAASV,UACrBG,QAAU,CACVC,WAAY,8CACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH,SAmCPM,WAzBa,SAASC,GAAIC,cACtBb,KAAO,CACPc,eAAgBF,IAGhBC,WACAb,KAAKa,SAAWA,cAGhBV,QAAU,CACVC,WAAY,sCACZJ,KAAMA,MAGNK,QAAUR,KAAKS,KAAK,CAACH,UAAU,UAEnCE,QAAQE,KAAKT,aAAaU,WAEnBH"}
@@ -0,0 +1,216 @@
// 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/>.
/**
* Controls the content area of the notification area on the
* notification page.
*
* @module message_popup/notification_area_content_area
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/notification', 'core/custom_interaction_events',
'message_popup/notification_repository', 'message_popup/notification_area_events'],
function($, Templates, DebugNotification, CustomEvents, NotificationRepo, NotificationAreaEvents) {
var SELECTORS = {
CONTAINER: '[data-region="notification-area"]',
CONTENT: '[data-region="content"]',
HEADER: '[data-region="header"]',
FOOTER: '[data-region="footer"]',
TOGGLE_MODE: '[data-action="toggle-mode"]',
};
var TEMPLATES = {
HEADER: 'message_popup/notification_area_content_area_header',
CONTENT: 'message_popup/notification_area_content_area_content',
FOOTER: 'message_popup/notification_area_content_area_footer',
};
/**
* Constructor for the ContentArea
*
* @class
* @param {object} root The root element for the content area
* @param {int} userId The user id of the current user
*/
var ContentArea = function(root, userId) {
this.root = $(root);
this.container = this.root.closest(SELECTORS.CONTAINER);
this.userId = userId;
this.header = this.root.find(SELECTORS.HEADER);
this.content = this.root.find(SELECTORS.CONTENT);
this.footer = this.root.find(SELECTORS.FOOTER);
this.registerEventListeners();
};
/**
* Get the root element.
*
* @method getRoot
* @return {object} jQuery element
*/
ContentArea.prototype.getRoot = function() {
return this.root;
};
/**
* Get the container element (which the content area is within).
*
* @method getContainer
* @return {object} jQuery element
*/
ContentArea.prototype.getContainer = function() {
return this.container;
};
/**
* Get the user id.
*
* @method getUserId
* @return {int}
*/
ContentArea.prototype.getUserId = function() {
return this.userId;
};
/**
* Get the content area header element.
*
* @method getHeader
* @return {object} jQuery element
*/
ContentArea.prototype.getHeader = function() {
return this.header;
};
/**
* Get the content area content element.
*
* @method getContent
* @return {object} jQuery element
*/
ContentArea.prototype.getContent = function() {
return this.content;
};
/**
* Get the content area footer element.
*
* @method getFooter
* @return {object} jQuery element
*/
ContentArea.prototype.getFooter = function() {
return this.footer;
};
/**
* Display the content area. Typically used with responsive
* styling on smaller screens.
*
* @method show
*/
ContentArea.prototype.show = function() {
this.getContainer().addClass('show-content-area');
};
/**
* Hide the content area. Typically used with responsive
* styling on smaller screens.
*
* @method hide
*/
ContentArea.prototype.hide = function() {
this.getContainer().removeClass('show-content-area');
};
/**
* Change the HTML in the content area header element.
*
* @method setHeaderHTML
* @param {string} html The HTML to be set
*/
ContentArea.prototype.setHeaderHTML = function(html) {
this.getHeader().empty().html(html);
};
/**
* Change the HTML in the content area content element.
*
* @method setContentHMTL
* @param {string} html The HTML to be set.
*/
ContentArea.prototype.setContentHTML = function(html) {
this.getContent().empty().html(html);
};
/**
* Change the HTML in the content area footer element.
*
* @method setFooterHTML
* @param {string} html The HTML to be set.
*/
ContentArea.prototype.setFooterHTML = function(html) {
this.getFooter().empty().html(html);
};
/**
* Render the given notification context in the content area.
*
* @method showNotification
* @param {object} notification The notification context (from a webservice)
* @return {object} jQuery promise
*/
ContentArea.prototype.showNotification = function(notification) {
var headerPromise = Templates.render(TEMPLATES.HEADER, notification).done(function(html) {
this.setHeaderHTML(html);
}.bind(this));
var contentPromise = Templates.render(TEMPLATES.CONTENT, notification).done(function(html) {
this.setContentHTML(html);
}.bind(this));
var footerPromise = Templates.render(TEMPLATES.FOOTER, notification).done(function(html) {
this.setFooterHTML(html);
}.bind(this));
return $.when(headerPromise, contentPromise, footerPromise).done(function() {
this.show();
this.getContainer().trigger(NotificationAreaEvents.notificationShown, [notification]);
}.bind(this));
};
/**
* Create the event listeners for the content area.
*
* @method registerEventListeners
*/
ContentArea.prototype.registerEventListeners = function() {
CustomEvents.define(this.getRoot(), [
CustomEvents.events.activate
]);
this.getRoot().on(CustomEvents.events.activate, SELECTORS.VIEW_TOGGLE, function() {
this.hide();
}.bind(this));
this.getContainer().on(NotificationAreaEvents.showNotification, function(e, notification) {
this.showNotification(notification);
}.bind(this));
};
return ContentArea;
});
@@ -0,0 +1,443 @@
// 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/>.
/**
* Controls the notification area on the notification page.
*
* @module message_popup/notification_area_control_area
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/templates', 'core/notification', 'core/custom_interaction_events',
'message_popup/notification_repository', 'message_popup/notification_area_events'],
function($, Templates, DebugNotification, CustomEvents, NotificationRepo, NotificationAreaEvents) {
var SELECTORS = {
CONTAINER: '[data-region="notification-area"]',
CONTENT: '[data-region="content"]',
NOTIFICATION: '[data-region="notification-content-item-container"]',
CAN_RECEIVE_FOCUS: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
};
var TEMPLATES = {
NOTIFICATION: 'message_popup/notification_content_item',
};
/**
* Constructor for ControlArea
*
* @class
* @param {object} root The root element for the content area
* @param {int} userId The user id of the current user
*/
var ControlArea = function(root, userId) {
this.root = $(root);
this.container = this.root.closest(SELECTORS.CONTAINER);
this.userId = userId;
this.content = this.root.find(SELECTORS.CONTENT);
this.offset = 0;
this.limit = 20;
this.initialLoad = false;
this.isLoading = false;
this.loadedAll = false;
this.notifications = {};
this.registerEventListeners();
};
/**
* Get the root element.
*
* @method getRoot
* @return {object} jQuery element
*/
ControlArea.prototype.getRoot = function() {
return this.root;
};
/**
* Get the container element (which the control area is within).
*
* @method getContainer
* @return {object} jQuery element
*/
ControlArea.prototype.getContainer = function() {
return this.container;
};
/**
* Get the user id.
*
* @method getUserId
* @return {int}
*/
ControlArea.prototype.getUserId = function() {
return this.userId;
};
/**
* Get the control area content element.
*
* @method getContent
* @return {object} jQuery element
*/
ControlArea.prototype.getContent = function() {
return this.content;
};
/**
* Get the offset value for paginated loading of the
* notifications.
*
* @method getOffset
* @return {int}
*/
ControlArea.prototype.getOffset = function() {
return this.offset;
};
/**
* Get the limit value for the paginated loading of the
* notifications.
*
* @method getLimit
* @return {int}
*/
ControlArea.prototype.getLimit = function() {
return this.limit;
};
/**
* Set the offset value for the paginated loading of the
* notifications.
*
* @method setOffset
* @param {int} value The new offset value
*/
ControlArea.prototype.setOffset = function(value) {
this.offset = value;
};
/**
* Set the limit value for the paginated loading of the
* notifications.
*
* @method setLimit
* @param {int} value The new limit value
*/
ControlArea.prototype.setLimit = function(value) {
this.limit = value;
};
/**
* Increment the offset by the limit amount.
*
* @method incrementOffset
*/
ControlArea.prototype.incrementOffset = function() {
this.offset += this.limit;
};
/**
* Flag the control area as loading.
*
* @method startLoading
*/
ControlArea.prototype.startLoading = function() {
this.isLoading = true;
this.getRoot().addClass('loading');
};
/**
* Remove the loading flag from the control area.
*
* @method stopLoading
*/
ControlArea.prototype.stopLoading = function() {
this.isLoading = false;
this.getRoot().removeClass('loading');
};
/**
* Check if the first load of notifications has been triggered.
*
* @method hasDoneInitialLoad
* @return {bool} true if first notification loaded, false otherwise
*/
ControlArea.prototype.hasDoneInitialLoad = function() {
return this.initialLoad;
};
/**
* Check if all of the notifications have been loaded.
*
* @method hasLoadedAllContent
* @return {bool}
*/
ControlArea.prototype.hasLoadedAllContent = function() {
return this.loadedAll;
};
/**
* Set the state of the loaded all content property.
*
* @method setLoadedAllContent
* @param {bool} val True if all content is loaded, false otherwise
*/
ControlArea.prototype.setLoadedAllContent = function(val) {
this.loadedAll = val;
};
/**
* Save a notification in the cache.
*
* @method setCacheNotification
* @param {object} notification A notification returned by a webservice
*/
ControlArea.prototype.setCacheNotification = function(notification) {
this.notifications[notification.id] = notification;
};
/**
* Retrieve a notification from the cache.
*
* @method getCacheNotification
* @param {int} id The id for the notification you wish to retrieve
* @return {object} A notification (as returned by a webservice)
*/
ControlArea.prototype.getCacheNotification = function(id) {
return this.notifications[id];
};
/**
* Find the notification element in the control area for the given id.
*
* @method getNotificationElement
* @param {int} id The notification id
* @return {(object|null)} jQuery element or null
*/
ControlArea.prototype.getNotificationElement = function(id) {
var element = this.getRoot().find(SELECTORS.NOTIFICATION + '[data-id="' + id + '"]');
return element.length == 1 ? element : null;
};
/**
* Scroll the notification element into view within the control area, if it
* isn't already visible.
*
* @method scrollNotificationIntoView
* @param {object} notificationElement The jQuery notification element
*/
ControlArea.prototype.scrollNotificationIntoView = function(notificationElement) {
var position = notificationElement.position();
var container = this.getRoot();
var relativeTop = position.top - container.scrollTop();
// If the element isn't in the view window.
if (relativeTop > container.innerHeight()) {
var height = notificationElement.outerHeight();
// Offset enough to make sure the notification will be in view.
height = height * 4;
var scrollTo = position.top - height;
container.scrollTop(scrollTo);
}
};
/**
* Show the full notification for the given notification element. The notification
* context is retrieved from the cache and send as data with an event to be
* rendered in the content area.
*
* @method showNotification
* @param {(int|object)} notificationElement The notification id or jQuery notification element
*/
ControlArea.prototype.showNotification = function(notificationElement) {
if (typeof notificationElement !== 'object') {
// Assume it's an ID if it's not an object.
notificationElement = this.getNotificationElement(notificationElement);
}
if (notificationElement && notificationElement.length) {
this.getRoot().find(SELECTORS.NOTIFICATION).removeClass('selected');
notificationElement.addClass('selected').find(SELECTORS.CAN_RECEIVE_FOCUS).focus();
var notificationId = notificationElement.attr('data-id');
var notification = this.getCacheNotification(notificationId);
this.scrollNotificationIntoView(notificationElement);
// Create a new version of the notification to send with the notification so
// this copy isn't modified.
this.getContainer().trigger(NotificationAreaEvents.showNotification, [$.extend({}, notification)]);
}
};
/**
* Send a request to mark the notification as read in the server and remove the unread
* status from the element.
*
* @method markNotificationAsRead
* @param {object} notificationElement The jQuery notification element
* @return {object} jQuery promise
*/
ControlArea.prototype.markNotificationAsRead = function(notificationElement) {
return NotificationRepo.markAsRead(notificationElement.attr('data-id')).done(function() {
notificationElement.removeClass('unread');
});
};
/**
* Render the notification data with the appropriate template and add it to the DOM.
*
* @method renderNotifications
* @param {array} notifications Array of notification data
* @return {object} jQuery promise that is resolved when all notifications have been
* rendered and added to the DOM
*/
ControlArea.prototype.renderNotifications = function(notifications) {
var promises = [];
var container = this.getContent();
$.each(notifications, function(index, notification) {
// Need to remove the contexturl so the item isn't rendered
// as a link.
var contextUrl = notification.contexturl;
delete notification.contexturl;
var promise = Templates.render(TEMPLATES.NOTIFICATION, notification)
.then(function(html, js) {
// Restore it for the cache.
notification.contexturl = contextUrl;
this.setCacheNotification(notification);
// Pass the Rendered content out.
return {html: html, js: js};
}.bind(this));
promises.push(promise);
}.bind(this));
return $.when.apply($, promises).then(function() {
// Each of the promises in the when will pass its results as an argument to the function.
// The order of the arguments will be the order that the promises are passed to when()
// i.e. the first promise's results will be in the first argument.
$.each(arguments, function(index, argument) {
container.append(argument.html);
Templates.runTemplateJS(argument.js);
});
return;
});
};
/**
* Load notifications from the server and render them.
*
* @method loadMoreNotifications
* @return {object} jQuery promise
*/
ControlArea.prototype.loadMoreNotifications = function() {
if (this.isLoading || this.hasLoadedAllContent()) {
return $.Deferred().resolve();
}
this.startLoading();
var request = {
limit: this.getLimit(),
offset: this.getOffset(),
useridto: this.getUserId(),
};
if (!this.initialLoad) {
// If this is the first load we may have been given a non-zero offset,
// in which case we need to load all notifications preceeding that offset
// to make sure the full list is rendered.
request.limit = this.getOffset() + this.getLimit();
request.offset = 0;
}
var promise = NotificationRepo.query(request).then(function(result) {
var notifications = result.notifications;
this.unreadCount = result.unreadcount;
this.setLoadedAllContent(!notifications.length || notifications.length < this.getLimit());
this.initialLoad = true;
if (notifications.length) {
this.incrementOffset();
return this.renderNotifications(notifications);
}
return false;
}.bind(this))
.always(function() {
this.stopLoading();
}.bind(this));
return promise;
};
/**
* Create the event listeners for the control area.
*
* @method registerEventListeners
*/
ControlArea.prototype.registerEventListeners = function() {
CustomEvents.define(this.getRoot(), [
CustomEvents.events.activate,
CustomEvents.events.scrollBottom,
CustomEvents.events.scrollLock,
CustomEvents.events.up,
CustomEvents.events.down,
]);
this.getRoot().on(CustomEvents.events.scrollBottom, function() {
this.loadMoreNotifications();
}.bind(this));
this.getRoot().on(CustomEvents.events.activate, SELECTORS.NOTIFICATION, function(e) {
var notificationElement = $(e.target).closest(SELECTORS.NOTIFICATION);
this.showNotification(notificationElement);
}.bind(this));
// Show the previous notification in the list.
this.getRoot().on(CustomEvents.events.up, SELECTORS.NOTIFICATION, function(e, data) {
var notificationElement = $(e.target).closest(SELECTORS.NOTIFICATION);
this.showNotification(notificationElement.prev());
data.originalEvent.preventDefault();
}.bind(this));
// Show the next notification in the list.
this.getRoot().on(CustomEvents.events.down, SELECTORS.NOTIFICATION, function(e, data) {
var notificationElement = $(e.target).closest(SELECTORS.NOTIFICATION);
this.showNotification(notificationElement.next());
data.originalEvent.preventDefault();
}.bind(this));
this.getContainer().on(NotificationAreaEvents.notificationShown, function(e, notification) {
if (!notification.read) {
var element = this.getNotificationElement(notification.id);
if (element) {
this.markNotificationAsRead(element);
}
var cachedNotification = this.getCacheNotification(notification.id);
if (cachedNotification) {
cachedNotification.read = true;
}
}
}.bind(this));
};
return ControlArea;
});
@@ -0,0 +1,27 @@
// 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/>.
/**
* Contain the events that can be fired in the notification area on
* the notifications page.
*
* @module message_popup/notification_area_events
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
showNotification: 'notification-area-events:showNotification',
notificationShown: 'notification-area-events:notificationShown',
};
@@ -0,0 +1,408 @@
// 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/>.
/**
* Controls the notification popover in the nav bar.
*
* See template: message_popup/notification_popover
*
* @module message_popup/notification_popover_controller
* @class notification_popover_controller
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
'core/notification', 'core/custom_interaction_events', 'core/popover_region_controller',
'message_popup/notification_repository', 'message_popup/notification_area_events'],
function($, Ajax, Templates, Str, URL, DebugNotification, CustomEvents,
PopoverController, NotificationRepo, NotificationAreaEvents) {
var SELECTORS = {
MARK_ALL_READ_BUTTON: '[data-action="mark-all-read"]',
ALL_NOTIFICATIONS_CONTAINER: '[data-region="all-notifications"]',
NOTIFICATION: '[data-region="notification-content-item-container"]',
UNREAD_NOTIFICATION: '[data-region="notification-content-item-container"].unread',
NOTIFICATION_LINK: '[data-action="content-item-link"]',
EMPTY_MESSAGE: '[data-region="empty-message"]',
COUNT_CONTAINER: '[data-region="count-container"]',
};
/**
* Constructor for the NotificationPopoverController.
* Extends PopoverRegionController.
*
* @param {object} element jQuery object root element of the popover
*/
var NotificationPopoverController = function(element) {
// Initialise base class.
PopoverController.call(this, element);
this.markAllReadButton = this.root.find(SELECTORS.MARK_ALL_READ_BUTTON);
this.unreadCount = 0;
this.lastQueried = 0;
this.userId = this.root.attr('data-userid');
this.container = this.root.find(SELECTORS.ALL_NOTIFICATIONS_CONTAINER);
this.limit = 20;
this.offset = 0;
this.loadedAll = false;
this.initialLoad = false;
// Let's find out how many unread notifications there are.
this.unreadCount = this.root.find(SELECTORS.COUNT_CONTAINER).html();
};
/**
* Clone the parent prototype.
*/
NotificationPopoverController.prototype = Object.create(PopoverController.prototype);
/**
* Make sure the constructor is set correctly.
*/
NotificationPopoverController.prototype.constructor = NotificationPopoverController;
/**
* Set the correct aria label on the menu toggle button to be read out by screen
* readers. The message will indicate the state of the unread notifications.
*
* @method updateButtonAriaLabel
*/
NotificationPopoverController.prototype.updateButtonAriaLabel = function() {
if (this.isMenuOpen()) {
Str.get_string('hidenotificationwindow', 'message').done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
} else {
if (this.unreadCount) {
Str.get_string('shownotificationwindowwithcount', 'message', this.unreadCount).done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
} else {
Str.get_string('shownotificationwindownonew', 'message').done(function(string) {
this.menuToggle.attr('aria-label', string);
}.bind(this));
}
}
};
/**
* Return the jQuery element with the content. This will return either
* the unread notification container or the all notification container
* depending on which is currently visible.
*
* @method getContent
* @return {object} jQuery object currently visible content contianer
*/
NotificationPopoverController.prototype.getContent = function() {
return this.container;
};
/**
* Get the offset value for the current state of the popover in order
* to sent to the backend to correctly paginate the notifications.
*
* @method getOffset
* @return {int} current offset
*/
NotificationPopoverController.prototype.getOffset = function() {
return this.offset;
};
/**
* Increment the offset for the current state, if required.
*
* @method incrementOffset
*/
NotificationPopoverController.prototype.incrementOffset = function() {
this.offset += this.limit;
};
/**
* Check if the first load of notification has been triggered for the current
* state of the popover.
*
* @method hasDoneInitialLoad
* @return {bool} true if first notification loaded, false otherwise
*/
NotificationPopoverController.prototype.hasDoneInitialLoad = function() {
return this.initialLoad;
};
/**
* Check if we've loaded all of the notifications for the current popover
* state.
*
* @method hasLoadedAllContent
* @return {bool} true if all notifications loaded, false otherwise
*/
NotificationPopoverController.prototype.hasLoadedAllContent = function() {
return this.loadedAll;
};
/**
* Set the state of the loaded all content property for the current state
* of the popover.
*
* @method setLoadedAllContent
* @param {bool} val True if all content is loaded, false otherwise
*/
NotificationPopoverController.prototype.setLoadedAllContent = function(val) {
this.loadedAll = val;
};
/**
* Show the unread notification count badge on the menu toggle if there
* are unread notifications, otherwise hide it.
*
* @method renderUnreadCount
*/
NotificationPopoverController.prototype.renderUnreadCount = function() {
var element = this.root.find(SELECTORS.COUNT_CONTAINER);
if (this.unreadCount) {
element.text(this.unreadCount);
element.removeClass('hidden');
} else {
element.addClass('hidden');
}
};
/**
* Hide the unread notification count badge on the menu toggle.
*
* @method hideUnreadCount
*/
NotificationPopoverController.prototype.hideUnreadCount = function() {
this.root.find(SELECTORS.COUNT_CONTAINER).addClass('hidden');
};
/**
* Find the notification element for the given id.
*
* @param {int} id
* @method getNotificationElement
* @return {object|null} The notification element
*/
NotificationPopoverController.prototype.getNotificationElement = function(id) {
var element = this.root.find(SELECTORS.NOTIFICATION + '[data-id="' + id + '"]');
return element.length == 1 ? element : null;
};
/**
* Render the notification data with the appropriate template and add it to the DOM.
*
* @method renderNotifications
* @param {array} notifications Notification data
* @param {object} container jQuery object the container to append the rendered notifications
* @return {object} jQuery promise that is resolved when all notifications have been
* rendered and added to the DOM
*/
NotificationPopoverController.prototype.renderNotifications = function(notifications, container) {
var promises = [];
$.each(notifications, function(index, notification) {
// Determine what the offset was when loading this notification.
var offset = this.getOffset() - this.limit;
// Update the view more url to contain the offset to allow the notifications
// page to load to the correct position in the list of notifications.
notification.viewmoreurl = URL.relativeUrl('/message/output/popup/notifications.php', {
notificationid: notification.id,
offset: offset,
});
// Link to mark read page before loading the actual link.
var notificationurlparams = {
notificationid: notification.id
};
notification.contexturl = URL.relativeUrl('message/output/popup/mark_notification_read.php', notificationurlparams);
var promise = Templates.render('message_popup/notification_content_item', notification)
.then(function(html, js) {
return {html: html, js: js};
});
promises.push(promise);
}.bind(this));
return $.when.apply($, promises).then(function() {
// Each of the promises in the when will pass its results as an argument to the function.
// The order of the arguments will be the order that the promises are passed to when()
// i.e. the first promise's results will be in the first argument.
$.each(arguments, function(index, argument) {
container.append(argument.html);
Templates.runTemplateJS(argument.js);
});
return;
});
};
/**
* Send a request for more notifications from the server, if we aren't already
* loading some and haven't already loaded all of them.
*
* Takes into account the current mode of the popover and will request only
* unread notifications if required.
*
* All notifications are marked as read by the server when they are returned.
*
* @method loadMoreNotifications
* @return {object} jQuery promise that is resolved when notifications have been
* retrieved and added to the DOM
*/
NotificationPopoverController.prototype.loadMoreNotifications = function() {
if (this.isLoading || this.hasLoadedAllContent()) {
return $.Deferred().resolve();
}
this.startLoading();
var request = {
limit: this.limit,
offset: this.getOffset(),
useridto: this.userId,
};
var container = this.getContent();
return NotificationRepo.query(request).then(function(result) {
var notifications = result.notifications;
this.unreadCount = result.unreadcount;
this.lastQueried = Math.floor(new Date().getTime() / 1000);
this.setLoadedAllContent(!notifications.length || notifications.length < this.limit);
this.initialLoad = true;
this.updateButtonAriaLabel();
if (notifications.length) {
this.incrementOffset();
return this.renderNotifications(notifications, container);
}
return false;
}.bind(this))
.always(function() {
this.stopLoading();
}.bind(this));
};
/**
* Send a request to the server to mark all unread notifications as read and update
* the unread count and unread notification elements appropriately.
*
* @return {Promise}
* @method markAllAsRead
*/
NotificationPopoverController.prototype.markAllAsRead = function() {
this.markAllReadButton.addClass('loading');
var request = {
useridto: this.userId,
timecreatedto: this.lastQueried,
};
return NotificationRepo.markAllAsRead(request)
.then(function() {
this.unreadCount = 0;
this.root.find(SELECTORS.UNREAD_NOTIFICATION).removeClass('unread');
}.bind(this))
.always(function() {
this.markAllReadButton.removeClass('loading');
}.bind(this));
};
/**
* Add all of the required event listeners for this notification popover.
*
* @method registerEventListeners
*/
NotificationPopoverController.prototype.registerEventListeners = function() {
CustomEvents.define(this.root, [
CustomEvents.events.activate,
]);
// Mark all notifications read if the user activates the mark all as read button.
this.root.on(CustomEvents.events.activate, SELECTORS.MARK_ALL_READ_BUTTON, function(e, data) {
this.markAllAsRead();
e.stopPropagation();
data.originalEvent.preventDefault();
}.bind(this));
// Mark individual notification read if the user activates it.
this.root.on(CustomEvents.events.activate, SELECTORS.NOTIFICATION_LINK, function(e) {
var element = $(e.target).closest(SELECTORS.NOTIFICATION);
if (element.hasClass('unread')) {
this.unreadCount--;
element.removeClass('unread');
}
e.stopPropagation();
}.bind(this));
// Update the notification information when the menu is opened.
this.root.on(this.events().menuOpened, function() {
this.hideUnreadCount();
this.updateButtonAriaLabel();
if (!this.hasDoneInitialLoad()) {
this.loadMoreNotifications();
}
}.bind(this));
// Update the unread notification count when the menu is closed.
this.root.on(this.events().menuClosed, function() {
this.renderUnreadCount();
this.updateButtonAriaLabel();
}.bind(this));
// Set aria attributes when popover is loading.
this.root.on(this.events().startLoading, function() {
this.getContent().attr('aria-busy', 'true');
}.bind(this));
// Set aria attributes when popover is finished loading.
this.root.on(this.events().stopLoading, function() {
this.getContent().attr('aria-busy', 'false');
}.bind(this));
// Load more notifications if the user has scrolled to the end of content
// item list.
this.getContentContainer().on(CustomEvents.events.scrollBottom, function() {
if (!this.isLoading && !this.hasLoadedAllContent()) {
this.loadMoreNotifications();
}
}.bind(this));
// Stop mouse scroll from propagating to the window element and
// scrolling the page.
CustomEvents.define(this.getContentContainer(), [
CustomEvents.events.scrollLock
]);
// Listen for when a notification is shown in the notifications page and mark
// it as read, if it's unread.
$(document).on(NotificationAreaEvents.notificationShown, function(e, notification) {
if (!notification.read) {
var element = this.getNotificationElement(notification.id);
if (element) {
element.removeClass('unread');
}
this.unreadCount--;
this.renderUnreadCount();
}
}.bind(this));
};
return NotificationPopoverController;
});
@@ -0,0 +1,123 @@
// 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/>.
/**
* Retrieves notifications from the server.
*
* @module message_popup/notification_repository
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/ajax', 'core/notification'], function(Ajax, Notification) {
/**
* Retrieve a list of notifications from the server.
*
* @param {object} args The request arguments
* @return {object} jQuery promise
*/
var query = function(args) {
if (typeof args.limit === 'undefined') {
args.limit = 20;
}
if (typeof args.offset === 'undefined') {
args.offset = 0;
}
var request = {
methodname: 'message_popup_get_popup_notifications',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Get the number of unread notifications from the server.
*
* @param {object} args The request arguments
* @return {object} jQuery promise
*/
var countUnread = function(args) {
var request = {
methodname: 'message_popup_get_unread_popup_notification_count',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Mark all notifications for the given user as read.
*
* @param {object} args The request arguments:
* @return {object} jQuery promise
*/
var markAllAsRead = function(args) {
var request = {
methodname: 'core_message_mark_all_notifications_as_read',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Mark a specific notification as read.
*
* @param {int} id The notification id
* @param {int} timeread The read timestamp (optional)
* @return {object} jQuery promise
*/
var markAsRead = function(id, timeread) {
var args = {
notificationid: id,
};
if (timeread) {
args.timeread = timeread;
}
var request = {
methodname: 'core_message_mark_notification_read',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
return {
query: query,
countUnread: countUnread,
markAllAsRead: markAllAsRead,
markAsRead: markAsRead,
};
});