Skip to content
This repository was archived by the owner on May 16, 2019. It is now read-only.

Commit 70e8500

Browse files
committed
Merge pull request #1114 from rmisio/paginate-chat-messages
Paginate chat messages
2 parents 43d0ff8 + 9e0bd91 commit 70e8500

File tree

8 files changed

+232
-82
lines changed

8 files changed

+232
-82
lines changed

css/obBase.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2065,7 +2065,7 @@ div.chatSearchOut .chatSearchIcon {
20652065
}
20662066

20672067
.chatConversationMenu {
2068-
width: 100px;
2068+
width: 110px;
20692069
border: solid 1px #565656;
20702070
border-top: 0;
20712071
padding: 10px;

js/collections/chatConversationsCl.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,5 @@ module.exports = Backbone.Collection.extend({
1111

1212
comparator: function(convo) {
1313
return -convo.get('timestamp');
14-
},
15-
16-
initialize: function(options) {
1714
}
1815
});

js/languages/en-US.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
"Listing": "Listing",
155155
"Listings": "Listings",
156156
"ViewPage": "View page",
157+
"ClearConvo": "Clear conversation",
157158
"Pages": "Pages",
158159
"Page": "Page",
159160
"Language": "Language",

js/models/chatConversationMd.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ module.exports = window.Backbone.Model.extend({
66
unread: 0,
77
avatar_hash: "",
88
guid: ""
9-
}
9+
},
10+
11+
idAttribute: 'guid'
1012
});

js/templates/chatConversation.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="chatConversationHeader">
2-
<a class="chatConversationAvatar thumbnail-small marginRight5 floatLeft js-username clickable" style="background-image: <% if (ob.avatar_hash) {%>url(<%= ob.serverUrl %>/get_image?hash=<%= ob.avatar_hash %>&guid=<%= ob.guid %>),<% } %> url(imgs/defaultUser.png);" href="#userPage/<%= ob.guid %>/store"></a>
2+
<a class="chatConversationAvatar thumbnail-small marginRight5 floatLeft js-username clickable" style="background-image: <%= ob.cssImageUrl(ob.avatar_hash, ob.guid, 'imgs/defaultUser.png') %>;" href="#userPage/<%= ob.guid %>/store"></a>
33
<div class="chatConversationLabel floatLeft js-username"><%= ob.handle || ob.guid %></div>
44
<div class="conversationHeader-btn floatRight marginTop4 textOpacity75">
55
<a class="ion-close-round js-closeConversation"></a>
@@ -16,10 +16,13 @@
1616
<div>
1717
<a class="js-blockUser"><%= polyglot.t('Block') %></a>
1818
</div>
19+
<div>
20+
<a class="js-clearConvo"><%= polyglot.t('ClearConvo') %></a>
21+
</div>
1922
</div>
2023

2124
<div class="chatConversationContent">
22-
<div class="padding10 width100 alignCenter js-loadingSpinner spinnerWrapper">
25+
<div class="padding10 width100 alignCenter js-loadingSpinner spinnerWrapper <% !ob.isFetching && print('hide') %>">
2326
<i class="ion-android-sync spinner textSize24px textOpacity50"></i>
2427
</div>
2528
<div class="js-messagesContainer"></div>

js/views/chatConversationVw.js

Lines changed: 145 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ module.exports = baseVw.extend({
1515
'keyup textarea': 'onKeyupMessage',
1616
'click .js-conversationSettings': 'toggleConvoSettings',
1717
'click .chatConversationMenu': 'closeConvoSettings',
18-
'click .js-blockUser': 'onBlockClick'
18+
'click .js-blockUser': 'onBlockClick',
19+
'click .js-clearConvo': 'onClearConvoClick'
1920
},
2021

22+
// This is ignoered by the server. There is an issue with SQLite and
23+
// it's using a hard-coded value of 20.
24+
MESSAGES_PER_FETCH: 20,
25+
2126
initialize: function(options) {
2227
this.options = options || {};
2328

@@ -34,35 +39,77 @@ module.exports = baseVw.extend({
3439
}
3540

3641
this.user = this.options.user;
42+
this.fetch = this.options.initialFetch;
3743

38-
this.collection.fetch({
39-
data: {
40-
guid: this.model.get('guid')
41-
},
42-
reset: true
43-
});
44+
this.listenTo(this.collection, 'reset', this.render);
4445

45-
this.listenTo(this.collection, 'reset', () => {
46-
this.renderMessages();
47-
});
46+
this.listenTo(this.collection, 'request', (cl, xhr, options) => {
47+
var clLen = cl.length;
4848

49-
this.listenTo(this.collection, 'request', () => {
50-
this.$messagesContainer.empty();
49+
this.fetch = xhr;
5150
this.$loadingSpinner.removeClass('hide');
51+
52+
xhr.done(() => this.$loadingSpinner.addClass('hide'));
53+
});
54+
55+
this.listenTo(this.collection, 'update', (cl, options) => {
56+
var $msgPage = $('<div />'),
57+
md;
58+
59+
if (!cl.at(0).viewCreated) {
60+
// new page of messages
61+
cl.every((md) => {
62+
var processed = md.viewCreated;
63+
64+
!processed && $msgPage.append(this.createMsg(md).render().el);
65+
66+
return !processed;
67+
});
68+
69+
this.addMessagesToDom($msgPage, true);
70+
} else {
71+
// new socket message or via text area
72+
__.filter(cl.models, (md) => {
73+
return !md.viewCreated;
74+
}).forEach((md) => {
75+
this.addMessagesToDom(
76+
this.createMsg(md).render().el
77+
);
78+
});
79+
}
5280
});
5381

54-
this.listenTo(this.collection, 'add', (md) => {
55-
var el = this.$messagesScrollContainer[0],
56-
scolledToBot = el.scrollTop >= (el.scrollHeight - el.offsetHeight) - 5;
57-
58-
this.$msgWrap.append(
59-
this.createMsg(md).render().el
60-
);
82+
this.scrollHandler = __.bind(
83+
__.throttle(this.onScroll, 100), this
84+
);
85+
},
6186

62-
if (scolledToBot) {
63-
el.scrollTop = el.scrollHeight;
64-
};
65-
});
87+
onScroll: function(e) {
88+
var startId;
89+
90+
if (!this.collection.length) return;
91+
92+
startId = this.collection.at(0).id;
93+
94+
if (
95+
!this.fetchedAll &&
96+
!(this.fetch && this.fetch.state() === 'pending') &&
97+
this.$messagesScrollContainer[0].scrollTop === 0
98+
) {
99+
this.collection.fetch({
100+
remove: false,
101+
data: {
102+
guid: this.model.get('guid'),
103+
start: startId,
104+
// backend is hard-coding limit at 20 for now (SQLite issue)
105+
limit: typeof this.options.messagesPerFetch === 'undefined' ? this.MESSAGES_PER_FETCH : this.options.messagesPerFetch
106+
}
107+
}).done(() => {
108+
if (this.collection.at(0).id === startId) {
109+
this.fetchedAll = true;
110+
}
111+
});
112+
}
66113
},
67114

68115
onClickClose: function() {
@@ -96,16 +143,8 @@ module.exports = baseVw.extend({
96143
return this.$msgTextArea;
97144
},
98145

99-
createMsg: function(md) {
100-
var vw = new ChatMessageVw({
101-
model: md,
102-
user: this.user
103-
});
104-
105-
this.msgViews.push(vw);
106-
this.registerChild(vw);
107-
108-
return vw;
146+
getScrollContainer: function() {
147+
return this.$messagesScrollContainer[0];
109148
},
110149

111150
closeConvoSettings: function() {
@@ -120,45 +159,100 @@ module.exports = baseVw.extend({
120159
this.user.blockUser(this.model.get('guid'));
121160
},
122161

123-
renderMessages: function() {
124-
this.$msgWrap = $('<div />');
162+
onClearConvoClick: function() {
163+
var formData = new FormData();
125164

126-
this.$loadingSpinner.addClass('hide');
165+
formData.append('guid', this.model.get('guid'));
127166

128-
if (this.msgViews) {
129-
this.msgViews.forEach((vw, index) => {
130-
vw.remove();
131-
});
132-
}
167+
$.ajax({
168+
url: app.serverConfig.getServerBaseUrl() + '/chat_conversation?guid=' + this.model.get('guid'),
169+
type: 'DELETE'
170+
});
133171

134-
this.msgViews = [];
172+
this.collection.reset();
173+
this.trigger('clear-conversation');
174+
},
135175

136-
this.collection.forEach((md, index) => {
137-
this.$msgWrap.append(
138-
this.createMsg(md).render().el
139-
);
176+
createMsg: function(md) {
177+
var vw = new ChatMessageVw({
178+
model: md,
179+
user: this.user
140180
});
141181

142-
this.$messagesContainer.html(this.$msgWrap);
143-
this.$messagesScrollContainer[0].scrollTop = this.$messagesScrollContainer[0].scrollHeight;
182+
md.viewCreated = true;
183+
this.msgViews.push(vw);
184+
this.registerChild(vw);
185+
186+
return vw;
187+
},
188+
189+
addMessagesToDom: function($messages, prepend, scrollTop) {
190+
var prevScroll = {},
191+
$scroll = this.$messagesScrollContainer;
192+
193+
prevScroll.height = $scroll[0].scrollHeight;
194+
prevScroll.top = $scroll[0].scrollTop;
195+
196+
if (!prepend) {
197+
this.$messagesContainer.append($messages);
198+
199+
if (__.isNumber(scrollTop)) {
200+
$scroll[0].scrollTop = scrollTop;
201+
} else if (prevScroll.top >= prevScroll.height - $scroll[0].clientHeight - 10) {
202+
$scroll[0].scrollTop = $scroll[0].scrollHeight;
203+
}
204+
} else {
205+
this.$messagesContainer.prepend($messages);
206+
$scroll[0].scrollTop = prevScroll.top + ($scroll[0].scrollHeight - prevScroll.height);
207+
}
144208
},
145209

146210
render: function() {
147211
loadTemplate('./js/templates/chatConversation.html', (tmpl) => {
212+
var $msgWrap = $('<div />');
213+
214+
if (this.msgViews) {
215+
this.msgViews.forEach((vw, index) => {
216+
vw.remove();
217+
});
218+
}
219+
220+
this.msgViews = [];
221+
148222
this.$el.html(
149223
tmpl(__.extend(this.model.toJSON(), {
150-
serverUrl: app.serverConfig.getServerBaseUrl(),
151-
moment: moment
224+
isFetching: this.fetch && this.fetch.state() === 'pending',
225+
messages: this.collection.toJSON()
152226
}))
153227
);
154228

155229
this.$('.chatConversationMessage textarea').focus()
156230
this.$messagesScrollContainer = this.$('.chatConversationContent');
231+
this.$messagesScrollContainer.on('scroll', this.scrollHandler);
157232
this.$loadingSpinner = this.$messagesScrollContainer.find('.js-loadingSpinner');
158233
this.$messagesContainer = this.$messagesScrollContainer.find('.js-messagesContainer');
159234
this.$msgTextArea = this.$('textarea');
235+
236+
if (this.collection.length) {
237+
this.collection.forEach((md) => {
238+
$msgWrap.append(
239+
this.createMsg(md).render().el
240+
);
241+
});
242+
243+
setTimeout(() => {
244+
this.addMessagesToDom($msgWrap, null,
245+
__.isNumber(this.options.initialScroll) ? this.options.initialScroll : 9999);
246+
}, 0);
247+
}
160248
});
161249

162250
return this;
163-
}
251+
},
252+
253+
remove: function() {
254+
this.$scrollContainer && this.$scrollContainer.off('scroll', this.scrollHandler);
255+
256+
baseVw.prototype.remove.apply(this, arguments);
257+
}
164258
});

js/views/chatHeadsVw.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@ module.exports = baseVw.extend({
1717
throw new Error('Please provide a parent element');
1818
}
1919

20-
this.listenTo(this.collection, 'add', (md, cl, opts) => {
21-
this.$headContainer.prepend(
22-
this.createChatHead(md).render().el
23-
);
24-
});
25-
20+
this.setCollection(this.collection);
2621
this.$chatHeadsContainer = options.parentEl;
2722
this.showPerScroll = 12;
2823

@@ -34,6 +29,10 @@ module.exports = baseVw.extend({
3429
},
3530

3631
setCollection: function(cl) {
32+
if (this.collection) {
33+
this.stopListening(this.collection);
34+
}
35+
3736
if (cl) {
3837
this.collection = cl;
3938
}

0 commit comments

Comments
 (0)