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
This commit is contained in:
parent
abf402b8c5
commit
2861fa26a7
7 changed files with 75 additions and 33 deletions
|
@ -357,18 +357,27 @@
|
|||
},
|
||||
|
||||
fetchConversation: function(conversationId) {
|
||||
var options = {remove: false};
|
||||
options.index = {
|
||||
// 'conversation' index on [conversationId, received_at]
|
||||
name : 'conversation',
|
||||
lower : [conversationId],
|
||||
upper : [conversationId, Number.MAX_VALUE]
|
||||
// SELECT messages WHERE conversationId = this.id ORDER
|
||||
// received_at DESC
|
||||
};
|
||||
// TODO pagination/infinite scroll
|
||||
// limit: 10, offset: page*10,
|
||||
return this.fetch(options);
|
||||
return new Promise(function(resolve) {
|
||||
var upper;
|
||||
if (this.length === 0) {
|
||||
// fetch the most recent messages first
|
||||
upper = Number.MAX_VALUE;
|
||||
} else {
|
||||
// not our first rodeo, fetch older messages.
|
||||
upper = this.at(0).get('received_at');
|
||||
}
|
||||
var options = {remove: false, limit: 100};
|
||||
options.index = {
|
||||
// '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() {
|
||||
|
|
|
@ -64,9 +64,7 @@
|
|||
this.remove();
|
||||
}.bind(this));
|
||||
|
||||
setTimeout(function() {
|
||||
this.view.scrollToBottom();
|
||||
}.bind(this), 10);
|
||||
this.fetchMessages();
|
||||
},
|
||||
|
||||
events: {
|
||||
|
@ -84,12 +82,23 @@
|
|||
'click' : 'onClick',
|
||||
'select .message-list .entry': 'messageDetail',
|
||||
'force-resize': 'forceUpdateMessageFieldSize',
|
||||
'click .choose-file': 'focusMessageField'
|
||||
'click .choose-file': 'focusMessageField',
|
||||
'loadMore .message-list': 'fetchMessages'
|
||||
},
|
||||
focusMessageField: function() {
|
||||
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) {
|
||||
this.model.messageCollection.add(message, {merge: true});
|
||||
},
|
||||
|
|
|
@ -54,18 +54,10 @@
|
|||
appWindow: this.model.appWindow
|
||||
});
|
||||
$el = view.$el;
|
||||
if (conversation.messageCollection.length === 0) {
|
||||
$el.find('.message-list').addClass('loading');
|
||||
}
|
||||
}
|
||||
$el.prependTo(this.el);
|
||||
$el.find('.message-list').trigger('reset-scroll');
|
||||
$el.trigger('force-resize');
|
||||
conversation.fetchContacts().then(function() {
|
||||
conversation.fetchMessages().then(function() {
|
||||
$el.find('.message-list').removeClass('loading');
|
||||
});
|
||||
});
|
||||
conversation.markRead();
|
||||
conversation.trigger('opened');
|
||||
}
|
||||
|
|
|
@ -10,14 +10,15 @@
|
|||
className: 'message-list',
|
||||
itemView: Whisper.MessageView,
|
||||
events: {
|
||||
'add': 'onAdd',
|
||||
'update *': 'scrollToBottom',
|
||||
'scroll': 'measureScrollPosition',
|
||||
'update *': 'scrollToBottomIfNeeded',
|
||||
'scroll': 'onScroll',
|
||||
'reset-scroll': 'resetScrollPosition'
|
||||
},
|
||||
onAdd: function() {
|
||||
this.$el.removeClass('loading');
|
||||
this.scrollToBottom();
|
||||
onScroll: function() {
|
||||
this.measureScrollPosition();
|
||||
if (this.$el.scrollTop() === 0) {
|
||||
this.$el.trigger('loadMore');
|
||||
}
|
||||
},
|
||||
measureScrollPosition: function() {
|
||||
if (this.el.scrollHeight === 0) { // hidden
|
||||
|
@ -47,6 +48,22 @@
|
|||
addAll: function() {
|
||||
Whisper.ListView.prototype.addAll.apply(this, arguments); // super()
|
||||
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 {
|
||||
position: relative;
|
||||
&::before {
|
||||
display: block;
|
||||
margin: $header-height auto;
|
||||
content: " ";
|
||||
height: $header-height;
|
||||
width: $header-height;
|
||||
}
|
||||
margin: 0;
|
||||
padding: 1em 0;
|
||||
overflow-y: auto;
|
||||
|
|
|
@ -396,7 +396,7 @@ $avatar-size: 44px;
|
|||
|
||||
.loading {
|
||||
position: relative;
|
||||
&::after {
|
||||
&::before {
|
||||
display: block;
|
||||
margin: $header-height auto;
|
||||
content: " ";
|
||||
|
|
|
@ -315,7 +315,7 @@ img.emoji {
|
|||
|
||||
.loading {
|
||||
position: relative; }
|
||||
.loading::after {
|
||||
.loading::before {
|
||||
display: block;
|
||||
margin: 36px auto;
|
||||
content: " ";
|
||||
|
@ -666,9 +666,16 @@ input.search {
|
|||
opacity: 1; }
|
||||
|
||||
.message-list {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 1em 0;
|
||||
overflow-y: auto; }
|
||||
.message-list::before {
|
||||
display: block;
|
||||
margin: 36px auto;
|
||||
content: " ";
|
||||
height: 36px;
|
||||
width: 36px; }
|
||||
.message-list .timestamp {
|
||||
cursor: pointer; }
|
||||
.message-list .timestamp:hover {
|
||||
|
|
Loading…
Reference in a new issue