MDL-54687 core_message: added ajax scrolling for messages

This commit is contained in:
Mark Nelson 2016-07-27 17:45:14 +08:00
parent 90525930e5
commit 8ec78c48fa
9 changed files with 173 additions and 48 deletions

View File

@ -21,8 +21,8 @@
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @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,
"<div style='text-align:center'>" + html + "</div>", 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;
}
);

View File

@ -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);
}

View File

@ -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 = '';

View File

@ -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);

View File

@ -22,7 +22,7 @@
</div>
<div class="span9 messages-area" data-region="messages-area">
{{#messages}}
{{> core_message/message_area_messages }}
{{> core_message/message_area_messages_area }}
{{/messages}}
</div>
</div>

View File

@ -1,13 +1,3 @@
<div class="messages" data-region="messages" data-userid="{{otheruserid}}">
<div class="name">
{{otheruserfullname}}
</div>
{{#messages}}
{{> core_message/message_area_message }}
{{/messages}}
</div>
<div class="response" data-region="response">
{{#iscurrentuser}}
{{> core_message/message_area_response }}
{{/iscurrentuser}}
</div>
{{#messages}}
{{> core_message/message_area_message }}
{{/messages}}

View File

@ -0,0 +1,11 @@
<div class="name">
{{otheruserfullname}}
</div>
<div class="messages" data-region="messages" data-userid="{{otheruserid}}">
{{> core_message/message_area_messages }}
</div>
<div class="response" data-region="response">
{{#iscurrentuser}}
{{> core_message/message_area_response }}
{{/iscurrentuser}}
</div>

View File

@ -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;

View File

@ -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;