From 8ec78c48faafe20b1b0d45cc1fa4fdc71c284701 Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Wed, 27 Jul 2016 17:45:14 +0800 Subject: [PATCH] MDL-54687 core_message: added ajax scrolling for messages --- message/amd/src/message_area_messages.js | 142 +++++++++++++++--- message/classes/api.php | 5 +- message/classes/helper.php | 7 + message/externallib.php | 16 +- message/templates/message_area.mustache | 2 +- .../templates/message_area_messages.mustache | 16 +- .../message_area_messages_area.mustache | 11 ++ theme/bootstrapbase/less/moodle/message.less | 12 +- theme/bootstrapbase/style/moodle.css | 10 +- 9 files changed, 173 insertions(+), 48 deletions(-) create mode 100644 message/templates/message_area_messages_area.mustache diff --git a/message/amd/src/message_area_messages.js b/message/amd/src/message_area_messages.js index 2728fb0cc8d..ba7da22445e 100644 --- a/message/amd/src/message_area_messages.js +++ b/message/amd/src/message_area_messages.js @@ -21,8 +21,8 @@ * @copyright 2016 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/ajax', 'core/templates', 'core/notification'], - function($, ajax, templates, notification) { +define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/custom_interaction_events'], + function($, ajax, templates, notification, customEvents) { /** * Messages class. @@ -34,6 +34,15 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification'], this._init(); } + /** @type {Boolean} checks if we are currently loading messages */ + Messages.prototype._isLoadingMessages = false; + + /** @type {int} the number of messagess displayed */ + Messages.prototype._numMessagesDisplayed = 0; + + /** @type {int} the number of messages to retrieve */ + Messages.prototype._numMessagesToRetrieve = 20; + /** @type {Messagearea} The messaging area object. */ Messages.prototype.messageArea = null; @@ -44,8 +53,8 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification'], */ Messages.prototype._init = function() { this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONDELETED, this._handleConversationDeleted.bind(this)); - this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONSELECTED, this._loadMessages.bind(this)); - this.messageArea.onCustomEvent(this.messageArea.EVENTS.SENDMESSAGE, this._loadMessages.bind(this)); + this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONSELECTED, this._viewMessages.bind(this)); + this.messageArea.onCustomEvent(this.messageArea.EVENTS.SENDMESSAGE, this._viewMessages.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.CHOOSEMESSAGESTODELETE, this._chooseMessagesToDelete.bind(this)); this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.SENDMESSAGE, this._sendMessage.bind(this)); this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.DELETEMESSAGES, this._deleteMessages.bind(this)); @@ -54,35 +63,121 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification'], }; /** - * Loads messages for a specific user. + * View the message panel. * * @param {Event} event * @param {int} userid * @returns {Promise} The promise resolved when the messages have been loaded. * @private */ - Messages.prototype._loadMessages = function(event, userid) { - // Show loading template. - templates.render('core/loading', {}).done(function(html, js) { - templates.replaceNodeContents(this.messageArea.SELECTORS.MESSAGESAREA, html, js); - }.bind(this)); + Messages.prototype._viewMessages = function(event, userid) { + // We are viewing another user, or re-loading the panel, so set number of messages displayed to 0. + this._numMessagesDisplayed = 0; + // Keep track of the number of messages received. + var numberreceived = 0; + // Show loading template. + return templates.render('core/loading', {}).then(function(html, js) { + templates.replaceNodeContents(this.messageArea.SELECTORS.MESSAGESAREA, html, js); + return this._getMessages(userid); + }.bind(this)).then(function(data) { + numberreceived = data.messages.length; + // We have the data - lets render the template with it. + return templates.render('core_message/message_area_messages_area', data); + }).then(function(html, js) { + templates.replaceNodeContents(this.messageArea.SELECTORS.MESSAGESAREA, html, js); + // Scroll to the bottom. + this._scrollBottom(); + // Only increment if data was returned. + if (numberreceived > 0) { + // Set the number of messages displayed. + this._numMessagesDisplayed = numberreceived; + } + // Now enable the ability to infinitely scroll through messages. + customEvents.define(this.messageArea.SELECTORS.MESSAGES, [ + customEvents.events.scrollTop + ]); + // Assign the event for scrolling. + this.messageArea.onCustomEvent(customEvents.events.scrollTop, this._loadMessages.bind(this)); + }.bind(this)).fail(notification.exception); + }; + + /** + * Loads messages while scrolling. + * + * @returns {Promise} The promise resolved when the messages have been loaded. + * @private + */ + Messages.prototype._loadMessages = function() { + if (this._isLoadingMessages) { + return; + } + + this._isLoadingMessages = true; + + // Keep track of the number of messages received. + var numberreceived = 0; + // Show loading template. + return templates.render('core/loading', {}).then(function(html, js) { + templates.prependNodeContents(this.messageArea.SELECTORS.MESSAGES, + "
" + html + "
", js); + return this._getMessages(this._getUserId()); + }.bind(this)).then(function(data) { + numberreceived = data.messages.length; + // We have the data - lets render the template with it. + return templates.render('core_message/message_area_messages', data); + }).then(function(html, js) { + // Remove the loading icon. + this.messageArea.find(this.messageArea.SELECTORS.MESSAGES + " " + + this.messageArea.SELECTORS.LOADINGICON).remove(); + // Check if we got something to do. + if (numberreceived > 0) { + // Let's check if we can remove the block time. + // First, get the block time that is currently being displayed. + var blocktime = this.messageArea.node.find(this.messageArea.SELECTORS.BLOCKTIME + ":first"); + var newblocktime = $(html).find(this.messageArea.SELECTORS.BLOCKTIME + ":first").addBack(); + if (blocktime.html() == newblocktime.html()) { + // Remove the block time as it's present above. + blocktime.remove(); + } + // Get height before we add the messages. + var oldheight = this.messageArea.find(this.messageArea.SELECTORS.MESSAGES)[0].scrollHeight; + // Show the new content. + templates.prependNodeContents(this.messageArea.SELECTORS.MESSAGES, html, js); + // Get height after we add the messages. + var newheight = this.messageArea.find(this.messageArea.SELECTORS.MESSAGES)[0].scrollHeight; + // Make sure scroll bar is at the location before we loaded more messages. + this.messageArea.find(this.messageArea.SELECTORS.MESSAGES).scrollTop(newheight - oldheight); + // Increment the number of messages displayed. + this._numMessagesDisplayed += numberreceived; + } + // Mark that we are no longer busy loading data. + this._isLoadingMessages = false; + }.bind(this)).fail(notification.exception); + }; + + /** + * Handles returning a list of messages to display. + * + * @param {int} userid + * @returns {Promise} The promise resolved when the contact area has been rendered + * @private + */ + Messages.prototype._getMessages = function(userid) { // Call the web service to get our data. var promises = ajax.call([{ methodname: 'core_message_data_for_messagearea_messages', args: { currentuserid: this.messageArea.getCurrentUserId(), - otheruserid: userid + otheruserid: userid, + limitfrom: this._numMessagesDisplayed, + limitnum: this._numMessagesToRetrieve, + newest: true } }]); // Do stuff when we get data back. - return promises[0].then(function(data) { - // We have the data - lets re-render the template with it. - return templates.render('core_message/message_area_messages', data); - }).then(function(html, js) { - templates.replaceNodeContents(this.messageArea.SELECTORS.MESSAGESAREA, html, js); - }.bind(this)).fail(notification.exception); + return promises[0]; }; /** @@ -264,6 +359,8 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification'], templates.appendNodeContents(this.messageArea.SELECTORS.MESSAGES, html, js); // Empty the response text area. this.messageArea.find(this.messageArea.SELECTORS.SENDMESSAGETEXT).val(''); + // Scroll down. + this._scrollBottom(); }.bind(this)).fail(notification.exception); }; @@ -277,6 +374,17 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification'], return this.messageArea.find(this.messageArea.SELECTORS.MESSAGES).data('userid'); }; + /** + * Scrolls to the bottom of the messages. + * + * @private + */ + Messages.prototype._scrollBottom = function() { + // Scroll to the bottom. + var messages = this.messageArea.find(this.messageArea.SELECTORS.MESSAGES); + messages.scrollTop(messages[0].scrollHeight); + }; + return Messages; } ); \ No newline at end of file diff --git a/message/classes/api.php b/message/classes/api.php index fa413f18d09..db135fe1972 100644 --- a/message/classes/api.php +++ b/message/classes/api.php @@ -91,11 +91,12 @@ class api { * @param int $otheruserid the other user * @param int $limitfrom * @param int $limitnum + * @param string $sort * @return \core_message\output\messages */ - public static function get_messages($userid, $otheruserid, $limitfrom = 0, $limitnum = 0) { + public static function get_messages($userid, $otheruserid, $limitfrom = 0, $limitnum = 0, $sort = 'timecreated ASC') { $arrmessages = array(); - if ($messages = \core_message\helper::get_messages($userid, $otheruserid, 0, $limitfrom, $limitnum)) { + if ($messages = \core_message\helper::get_messages($userid, $otheruserid, 0, $limitfrom, $limitnum, $sort)) { $arrmessages = \core_message\helper::create_messages($userid, $messages); } diff --git a/message/classes/helper.php b/message/classes/helper.php index 767f13800a6..07584864e0a 100644 --- a/message/classes/helper.php +++ b/message/classes/helper.php @@ -81,6 +81,13 @@ class helper { // Store the messages. $arrmessages = array(); + // We always view messages from oldest to newest, ensure we have it in that order. + $lastmessage = end($messages); + $firstmessage = reset($messages); + if ($lastmessage->timecreated < $firstmessage->timecreated) { + $messages = array_reverse($messages); + } + // Keeps track of the last day, month and year combo we were viewing. $day = ''; $month = ''; diff --git a/message/externallib.php b/message/externallib.php index 35e9fb2ce8a..ade34493510 100644 --- a/message/externallib.php +++ b/message/externallib.php @@ -554,7 +554,8 @@ class core_message_external extends external_api { 'currentuserid' => new external_value(PARAM_INT, 'The current user\'s id'), 'otheruserid' => new external_value(PARAM_INT, 'The other user\'s id'), 'limitfrom' => new external_value(PARAM_INT, 'Limit from', VALUE_DEFAULT, 0), - 'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 0) + 'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 0), + 'newest' => new external_value(PARAM_BOOL, 'Newest first?', VALUE_DEFAULT, false), ) ); } @@ -566,9 +567,10 @@ class core_message_external extends external_api { * @param int $otheruserid The other user's id * @param int $limitfrom * @param int $limitnum + * @param boolean $newest * @return external_description */ - public static function data_for_messagearea_messages($currentuserid, $otheruserid, $limitfrom = 0, $limitnum = 0) { + public static function data_for_messagearea_messages($currentuserid, $otheruserid, $limitfrom = 0, $limitnum = 0, $newest = false) { global $CFG, $PAGE; // Check if messaging is enabled. @@ -580,13 +582,19 @@ class core_message_external extends external_api { 'currentuserid' => $currentuserid, 'otheruserid' => $otheruserid, 'limitfrom' => $limitfrom, - 'limitnum' => $limitnum + 'limitnum' => $limitnum, + 'newest' => $newest ); self::validate_parameters(self::data_for_messagearea_messages_parameters(), $params); self::validate_context(context_user::instance($currentuserid)); - $messages = \core_message\api::get_messages($currentuserid, $otheruserid, $limitfrom, $limitnum); + if ($newest) { + $sort = 'timecreated DESC'; + } else { + $sort = 'timecreated ASC'; + } + $messages = \core_message\api::get_messages($currentuserid, $otheruserid, $limitfrom, $limitnum, $sort); $renderer = $PAGE->get_renderer('core_message'); return $messages->export_for_template($renderer); diff --git a/message/templates/message_area.mustache b/message/templates/message_area.mustache index 64998c585d4..d012e120d42 100644 --- a/message/templates/message_area.mustache +++ b/message/templates/message_area.mustache @@ -22,7 +22,7 @@
{{#messages}} - {{> core_message/message_area_messages }} + {{> core_message/message_area_messages_area }} {{/messages}}
diff --git a/message/templates/message_area_messages.mustache b/message/templates/message_area_messages.mustache index b09d1693b6c..3e15128dab8 100644 --- a/message/templates/message_area_messages.mustache +++ b/message/templates/message_area_messages.mustache @@ -1,13 +1,3 @@ -
-
- {{otheruserfullname}} -
- {{#messages}} - {{> core_message/message_area_message }} - {{/messages}} -
-
- {{#iscurrentuser}} - {{> core_message/message_area_response }} - {{/iscurrentuser}} -
+{{#messages}} + {{> core_message/message_area_message }} +{{/messages}} \ No newline at end of file diff --git a/message/templates/message_area_messages_area.mustache b/message/templates/message_area_messages_area.mustache new file mode 100644 index 00000000000..accfe3ad5df --- /dev/null +++ b/message/templates/message_area_messages_area.mustache @@ -0,0 +1,11 @@ +
+ {{otheruserfullname}} +
+
+ {{> core_message/message_area_messages }} +
+
+ {{#iscurrentuser}} + {{> core_message/message_area_response }} + {{/iscurrentuser}} +
diff --git a/theme/bootstrapbase/less/moodle/message.less b/theme/bootstrapbase/less/moodle/message.less index f9ee9712f72..39d400f82f9 100644 --- a/theme/bootstrapbase/less/moodle/message.less +++ b/theme/bootstrapbase/less/moodle/message.less @@ -156,13 +156,13 @@ } } - .messages { - height: 520px; - overflow-y: auto; + .name { + font-weight: bold; + } - .name { - font-weight: bold; - } + .messages { + height: 500px; + overflow-y: auto; .blocktime { clear: both; diff --git a/theme/bootstrapbase/style/moodle.css b/theme/bootstrapbase/style/moodle.css index df1f0662530..4a1e35f8019 100644 --- a/theme/bootstrapbase/style/moodle.css +++ b/theme/bootstrapbase/style/moodle.css @@ -5879,13 +5879,13 @@ a.ygtvspacer:hover { .messaging-area .messages-area .profile .actions { padding-top: 10px; } -.messaging-area .messages-area .messages { - height: 520px; - overflow-y: auto; -} -.messaging-area .messages-area .messages .name { +.messaging-area .messages-area .name { font-weight: bold; } +.messaging-area .messages-area .messages { + height: 500px; + overflow-y: auto; +} .messaging-area .messages-area .messages .blocktime { clear: both; font-weight: bold;