Implement infinite scrolling message lists

Only load the most recent messages when initially rendering a
conversation. Scrolling to the top of a message list loads older
messages.

This required some slight refactoring of how we insert message elements
into the dom. If the message is added to the end of the collection,
append it at the end. Otherwise, assume it is an older message and
prepend it.

When adding elements to the top, reset the scrollPosition to its
previous distance from scrollHeight. This keeps the current set of
elements fixed in the viewport.

// FREEBIE
pull/749/head
lilia 10 years ago
parent abf402b8c5
commit 2861fa26a7

@ -357,18 +357,27 @@
}, },
fetchConversation: function(conversationId) { fetchConversation: function(conversationId) {
var options = {remove: false}; return new Promise(function(resolve) {
options.index = { var upper;
// 'conversation' index on [conversationId, received_at] if (this.length === 0) {
name : 'conversation', // fetch the most recent messages first
lower : [conversationId], upper = Number.MAX_VALUE;
upper : [conversationId, Number.MAX_VALUE] } else {
// SELECT messages WHERE conversationId = this.id ORDER // not our first rodeo, fetch older messages.
// received_at DESC upper = this.at(0).get('received_at');
}; }
// TODO pagination/infinite scroll var options = {remove: false, limit: 100};
// limit: 10, offset: page*10, options.index = {
return this.fetch(options); // 'conversation' index on [conversationId, received_at]
name : 'conversation',
lower : [conversationId],
upper : [conversationId, upper],
order : 'desc'
// SELECT messages WHERE conversationId = this.id ORDER
// received_at DESC
};
this.fetch(options).then(resolve);
}.bind(this));
}, },
hasKeyConflicts: function() { hasKeyConflicts: function() {

@ -64,9 +64,7 @@
this.remove(); this.remove();
}.bind(this)); }.bind(this));
setTimeout(function() { this.fetchMessages();
this.view.scrollToBottom();
}.bind(this), 10);
}, },
events: { events: {
@ -84,12 +82,23 @@
'click' : 'onClick', 'click' : 'onClick',
'select .message-list .entry': 'messageDetail', 'select .message-list .entry': 'messageDetail',
'force-resize': 'forceUpdateMessageFieldSize', 'force-resize': 'forceUpdateMessageFieldSize',
'click .choose-file': 'focusMessageField' 'click .choose-file': 'focusMessageField',
'loadMore .message-list': 'fetchMessages'
}, },
focusMessageField: function() { focusMessageField: function() {
this.$messageField.focus(); this.$messageField.focus();
}, },
fetchMessages: function() {
this.$('.message-list').addClass('loading');
return this.model.fetchContacts().then(function() {
return this.model.fetchMessages().then(function() {
this.$('.message-list').removeClass('loading');
}.bind(this));
}.bind(this));
// TODO catch?
},
addMessage: function(message) { addMessage: function(message) {
this.model.messageCollection.add(message, {merge: true}); this.model.messageCollection.add(message, {merge: true});
}, },

@ -54,18 +54,10 @@
appWindow: this.model.appWindow appWindow: this.model.appWindow
}); });
$el = view.$el; $el = view.$el;
if (conversation.messageCollection.length === 0) {
$el.find('.message-list').addClass('loading');
}
} }
$el.prependTo(this.el); $el.prependTo(this.el);
$el.find('.message-list').trigger('reset-scroll'); $el.find('.message-list').trigger('reset-scroll');
$el.trigger('force-resize'); $el.trigger('force-resize');
conversation.fetchContacts().then(function() {
conversation.fetchMessages().then(function() {
$el.find('.message-list').removeClass('loading');
});
});
conversation.markRead(); conversation.markRead();
conversation.trigger('opened'); conversation.trigger('opened');
} }

@ -10,14 +10,15 @@
className: 'message-list', className: 'message-list',
itemView: Whisper.MessageView, itemView: Whisper.MessageView,
events: { events: {
'add': 'onAdd', 'update *': 'scrollToBottomIfNeeded',
'update *': 'scrollToBottom', 'scroll': 'onScroll',
'scroll': 'measureScrollPosition',
'reset-scroll': 'resetScrollPosition' 'reset-scroll': 'resetScrollPosition'
}, },
onAdd: function() { onScroll: function() {
this.$el.removeClass('loading'); this.measureScrollPosition();
this.scrollToBottom(); if (this.$el.scrollTop() === 0) {
this.$el.trigger('loadMore');
}
}, },
measureScrollPosition: function() { measureScrollPosition: function() {
if (this.el.scrollHeight === 0) { // hidden if (this.el.scrollHeight === 0) { // hidden
@ -47,6 +48,22 @@
addAll: function() { addAll: function() {
Whisper.ListView.prototype.addAll.apply(this, arguments); // super() Whisper.ListView.prototype.addAll.apply(this, arguments); // super()
this.scrollToBottom(); this.scrollToBottom();
} },
addOne: function(model) {
if (this.itemView) {
var view = new this.itemView({model: model}).render();
if (this.collection.indexOf(model) === this.collection.length - 1) {
// add to the bottom.
this.$el.append(view.el);
this.scrollToBottom();
} else {
// add to the top.
var offset = this.el.scrollHeight - this.$el.scrollTop();
this.$el.prepend(view.el);
this.$el.scrollTop(this.el.scrollHeight - offset);
}
}
this.$el.removeClass('loading');
},
}); });
})(); })();

@ -219,6 +219,14 @@
} }
.message-list { .message-list {
position: relative;
&::before {
display: block;
margin: $header-height auto;
content: " ";
height: $header-height;
width: $header-height;
}
margin: 0; margin: 0;
padding: 1em 0; padding: 1em 0;
overflow-y: auto; overflow-y: auto;

@ -396,7 +396,7 @@ $avatar-size: 44px;
.loading { .loading {
position: relative; position: relative;
&::after { &::before {
display: block; display: block;
margin: $header-height auto; margin: $header-height auto;
content: " "; content: " ";

@ -315,7 +315,7 @@ img.emoji {
.loading { .loading {
position: relative; } position: relative; }
.loading::after { .loading::before {
display: block; display: block;
margin: 36px auto; margin: 36px auto;
content: " "; content: " ";
@ -666,9 +666,16 @@ input.search {
opacity: 1; } opacity: 1; }
.message-list { .message-list {
position: relative;
margin: 0; margin: 0;
padding: 1em 0; padding: 1em 0;
overflow-y: auto; } overflow-y: auto; }
.message-list::before {
display: block;
margin: 36px auto;
content: " ";
height: 36px;
width: 36px; }
.message-list .timestamp { .message-list .timestamp {
cursor: pointer; } cursor: pointer; }
.message-list .timestamp:hover { .message-list .timestamp:hover {

Loading…
Cancel
Save