Commit ac643ae6 authored by JC Brand's avatar JC Brand

Refactor views to use Backbone.OrderedListView

parent 102f39ed
...@@ -73,21 +73,25 @@ ...@@ -73,21 +73,25 @@
spyOn(_converse.chatboxviews, 'trimChats'); spyOn(_converse.chatboxviews, 'trimChats');
expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat'); test_utils.waitUntil(function () {
expect(online_contacts.length).toBe(15); return _converse.rosterview.$el.find('.roster-group li').length;
for (i=0; i<online_contacts.length; i++) { }, 700).then(function () {
$el = $(online_contacts[i]); var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat');
jid = $el.text().trim().replace(/ /g,'.').toLowerCase() + '@localhost'; expect(online_contacts.length).toBe(15);
$el.click(); for (i=0; i<online_contacts.length; i++) {
chatboxview = _converse.chatboxviews.get(jid); $el = $(online_contacts[i]);
expect(_converse.chatboxes.length).toEqual(i+2); jid = $el.text().trim().replace(/ /g,'.').toLowerCase() + '@localhost';
expect(_converse.chatboxviews.trimChats).toHaveBeenCalled(); $el.click();
// Check that new chat boxes are created to the left of the chatboxview = _converse.chatboxviews.get(jid);
// controlbox (but to the right of all existing chat boxes) expect(_converse.chatboxes.length).toEqual(i+2);
expect($("#conversejs .chatbox").length).toBe(i+2); expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
expect($("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id')); // Check that new chat boxes are created to the left of the
} // controlbox (but to the right of all existing chat boxes)
done(); expect($("#conversejs .chatbox").length).toBe(i+2);
expect($("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id'));
}
done();
});
})); }));
it("can be trimmed to conserve space", it("can be trimmed to conserve space",
...@@ -109,10 +113,10 @@ ...@@ -109,10 +113,10 @@
spyOn(trimmed_chatboxes, 'removeChat').and.callThrough(); spyOn(trimmed_chatboxes, 'removeChat').and.callThrough();
expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
_converse.rosterview.update(); // XXX: Hack to make sure $roster element is attaced. _converse.rosterview.update(); // XXX: Hack to make sure $roster element is attached.
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
return _converse.rosterview.$el.find('.roster-group').length; return _converse.rosterview.$el.find('.roster-group li').length;
}, 300).then(function () { }, 700).then(function () {
// Test that they can be maximized again // Test that they can be maximized again
var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat'); var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(15); expect(online_contacts.length).toBe(15);
...@@ -836,10 +840,9 @@ ...@@ -836,10 +840,9 @@
}).c('body').t('Message: '+i).up() }).c('body').t('Message: '+i).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
} }
test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return chatboxview.$content.scrollTop(); return chatboxview.$content.scrollTop();
}, 1000) }, 1000).then(function () {
.then(function () {
return test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return !chatboxview.model.get('auto_scrolled'); return !chatboxview.model.get('auto_scrolled');
}, 500); }, 500);
...@@ -872,7 +875,7 @@ ...@@ -872,7 +875,7 @@
chatboxview.$content.scrollTop(chatboxview.$content[0].scrollHeight); chatboxview.$content.scrollTop(chatboxview.$content[0].scrollHeight);
return test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return !chatboxview.$('.new-msgs-indicator').is(':visible'); return !chatboxview.$('.new-msgs-indicator').is(':visible');
}, 500); }, 700);
}).then(done); }).then(done);
})); }));
...@@ -1541,7 +1544,7 @@ ...@@ -1541,7 +1544,7 @@
view.model.maximize(); view.model.maximize();
return test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return view.model.get('chat_state') === 'active'; return view.model.get('chat_state') === 'active';
}, 500); }, 700);
}).then(function () { }).then(function () {
expect(_converse.connection.send).toHaveBeenCalled(); expect(_converse.connection.send).toHaveBeenCalled();
var calls = _.filter(_converse.connection.send.calls.all(), function (call) { var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
...@@ -1693,9 +1696,8 @@ ...@@ -1693,9 +1696,8 @@
test_utils.openControlBox(); test_utils.openControlBox();
test_utils.openContactsPanel(_converse); test_utils.openContactsPanel(_converse);
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
return _converse.rosterview.$el.find('.roster-group').length; return _converse.rosterview.$el.find('.roster-group li').length;
}, 300) }, 700).then(function () {
.then(function () {
_converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test _converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
...@@ -1715,7 +1717,7 @@ ...@@ -1715,7 +1717,7 @@
return test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return view.model.get('chat_state') === 'paused'; return view.model.get('chat_state') === 'paused';
}, 500); }, 500);
}).then(function () { }).then(function () {
expect(_converse.connection.send).toHaveBeenCalled(); expect(_converse.connection.send).toHaveBeenCalled();
var calls = _.filter(_converse.connection.send.calls.all(), function (call) { var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
return call.args[0] instanceof Strophe.Builder; return call.args[0] instanceof Strophe.Builder;
......
This diff is collapsed.
...@@ -228,11 +228,11 @@ ...@@ -228,11 +228,11 @@
expect(_converse.roster.updateContact).toHaveBeenCalled(); expect(_converse.roster.updateContact).toHaveBeenCalled();
// Check that the user is now properly shown as a pending // Check that the user is now properly shown as a pending
// contact in the roster. // contact in the roster.
var $header = $('a:contains("Pending contacts")');
return test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return $('a:contains("Pending contacts")').length && $header.is(":visible"); var $header = $('a:contains("Pending contacts")');
}, 300); var $contacts = $header.parent().find('li');
return $contacts.length;
}, 600);
}).then(function () { }).then(function () {
var $header = $('a:contains("Pending contacts")'); var $header = $('a:contains("Pending contacts")');
var $contacts = $header.parent().find('li'); var $contacts = $header.parent().find('li');
...@@ -297,10 +297,16 @@ ...@@ -297,10 +297,16 @@
// The contact should now be visible as an existing // The contact should now be visible as an existing
// contact (but still offline). // contact (but still offline).
$header = $('a:contains("My contacts")'); return test_utils.waitUntil(function () {
var $header = $('a:contains("My contacts")');
var $contacts = $header.parent().find('li');
return $contacts.length;
}, 600);
}).then(function () {
var $header = $('a:contains("My contacts")');
expect($header.length).toBe(1); expect($header.length).toBe(1);
expect($header.is(":visible")).toBeTruthy(); expect($header.is(":visible")).toBeTruthy();
$contacts = $header.parent().find('li'); var $contacts = $header.parent().find('li');
expect($contacts.length).toBe(1); expect($contacts.length).toBe(1);
// Check that it has the right classes and text // Check that it has the right classes and text
expect($contacts.hasClass('to')).toBeTruthy(); expect($contacts.hasClass('to')).toBeTruthy();
...@@ -482,10 +488,12 @@ ...@@ -482,10 +488,12 @@
sent_IQ = iq; sent_IQ = iq;
IQ_id = sendIQ.bind(this)(iq, callback, errback); IQ_id = sendIQ.bind(this)(iq, callback, errback);
}); });
return test_utils.waitUntil(function () {
var $header = $('a:contains("My contacts")');
var $contacts = $header.parent().find('li');
return $contacts.length;
}, 600).then(function () {
test_utils.waitUntil(function () {
return $('a:contains("My contacts")').length;
}).then(function () {
var $header = $('a:contains("My contacts")'); var $header = $('a:contains("My contacts")');
// remove the first user // remove the first user
$($header.parent().find('li .remove-xmpp-contact').get(0)).click(); $($header.parent().find('li .remove-xmpp-contact').get(0)).click();
...@@ -547,9 +555,11 @@ ...@@ -547,9 +555,11 @@
'xmlns': Strophe.NS.NICK, 'xmlns': Strophe.NS.NICK,
}).t('Clint Contact'); }).t('Clint Contact');
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () { return test_utils.waitUntil(function () {
return $('a:contains("Contact requests")').length; var $header = $('a:contains("Contact requests")');
}).then(function () { var $contacts = $header.parent().find('li');
return $contacts.length;
}, 600).then(function () {
expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object)); expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
var $header = $('a:contains("Contact requests")'); var $header = $('a:contains("Contact requests")');
expect($header.length).toBe(1); expect($header.length).toBe(1);
......
...@@ -22,6 +22,7 @@ require.config({ ...@@ -22,6 +22,7 @@ require.config({
"backbone.browserStorage": "node_modules/backbone.browserStorage/backbone.browserStorage", "backbone.browserStorage": "node_modules/backbone.browserStorage/backbone.browserStorage",
"backbone.noconflict": "src/backbone.noconflict", "backbone.noconflict": "src/backbone.noconflict",
"backbone.overview": "node_modules/backbone.overview/backbone.overview", "backbone.overview": "node_modules/backbone.overview/backbone.overview",
"backbone.orderedlistview": "node_modules/backbone.overview/backbone.orderedlistview",
"backbone.vdomview": "node_modules/backbone.vdomview/backbone.vdomview", "backbone.vdomview": "node_modules/backbone.vdomview/backbone.vdomview",
"emojione": "node_modules/emojione/lib/js/emojione", "emojione": "node_modules/emojione/lib/js/emojione",
"es6-promise": "node_modules/es6-promise/dist/es6-promise.auto", "es6-promise": "node_modules/es6-promise/dist/es6-promise.auto",
......
...@@ -16,8 +16,7 @@ ...@@ -16,8 +16,7 @@
"strophe", "strophe",
"pluggable", "pluggable",
"backbone.noconflict", "backbone.noconflict",
"backbone.browserStorage", "backbone.browserStorage"
"backbone.overview",
], factory); ], factory);
}(this, function (sizzle, Promise, _, polyfill, i18n, utils, moment, Strophe, pluggable, Backbone) { }(this, function (sizzle, Promise, _, polyfill, i18n, utils, moment, Strophe, pluggable, Backbone) {
......
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
"awesomplete", "awesomplete",
"converse-chatview", "converse-chatview",
"converse-disco", "converse-disco",
"backbone.overview",
"backbone.orderedlistview",
"backbone.vdomview" "backbone.vdomview"
], factory); ], factory);
}(this, function ( }(this, function (
...@@ -2213,16 +2215,17 @@ ...@@ -2213,16 +2215,17 @@
}, },
}); });
_converse.ChatRoomOccupantsView = Backbone.Overview.extend({ _converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({
tagName: 'div', tagName: 'div',
className: 'occupants', className: 'occupants',
listItems: 'model',
sortEvent: 'change:role',
listSelector: '.occupant-list',
ItemView: _converse.ChatRoomOccupantView,
initialize () { initialize () {
this.model.on("add", this.onOccupantAdded, this); Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.model.on("change:role", (occupant) => {
this.model.sort();
this.positionOccupant(occupant);
});
this.chatroomview = this.model.chatroomview; this.chatroomview = this.model.chatroomview;
this.chatroomview.model.on('change:open', this.renderInviteWidget, this); this.chatroomview.model.on('change:open', this.renderInviteWidget, this);
...@@ -2247,9 +2250,7 @@ ...@@ -2247,9 +2250,7 @@
this.model.fetch({ this.model.fetch({
'add': true, 'add': true,
'silent': true, 'silent': true,
'success': () => { 'success': this.sortAndPositionAllItems.bind(this)
this.model.each(this.onOccupantAdded.bind(this));
}
}); });
}, },
...@@ -2353,57 +2354,12 @@ ...@@ -2353,57 +2354,12 @@
this.debouncedRenderRoomFeatures(); this.debouncedRenderRoomFeatures();
}, },
setOccupantsHeight () { setOccupantsHeight () {
const el = this.el.querySelector('.chatroom-features'); const el = this.el.querySelector('.chatroom-features');
this.el.querySelector('.occupant-list').style.cssText = this.el.querySelector('.occupant-list').style.cssText =
`height: calc(100% - ${el.offsetHeight}px - 5em);`; `height: calc(100% - ${el.offsetHeight}px - 5em);`;
}, },
positionOccupant (occupant) {
/* Positions an occupant correctly in the list of
* occupants.
*
* IMPORTANT: there's an important implicit assumption being
* made here. And that is that initially this method gets called
* for each occupant in the right positional order.
*
* In other words, it gets called for the 0th, then the
* 1st, then the 2nd, 3rd and so on.
*
* That's why we call it in the "success" handler after
* fetching the occupants, so that we know we have ALL of
* them and that they're sorted.
*/
const view = this.get(occupant.get('id'));
view.render();
const list = this.el.querySelector('.occupant-list');
const index = this.model.indexOf(view.model);
if (index === 0) {
list.insertAdjacentElement('afterbegin', view.el);
} else if (index === (this.model.length-1)) {
list.insertAdjacentElement('beforeend', view.el);
} else {
const neighbour_el = list.querySelector('li:nth-child('+index+')');
neighbour_el.insertAdjacentElement('afterend', view.el);
}
return view;
},
onOccupantAdded (item) {
let view = this.get(item.get('id'));
if (!view) {
view = this.add(
item.get('id'),
new _converse.ChatRoomOccupantView({model: item})
);
} else {
view.model = item;
view.initialize();
}
this.positionOccupant(item);
},
parsePresence (pres) { parsePresence (pres) {
const id = Strophe.getResourceFromJid(pres.getAttribute("from")); const id = Strophe.getResourceFromJid(pres.getAttribute("from"));
const data = { const data = {
......
...@@ -285,8 +285,12 @@ ...@@ -285,8 +285,12 @@
this.model.on("reset", this.reset, this); this.model.on("reset", this.reset, this);
_converse.on('rosterGroupsFetched', this.positionFetchedGroups, this); _converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
_converse.on('rosterContactsFetched', () => { _converse.on('rosterContactsFetched', () => {
_converse.roster.each(this.onContactAdded.bind(this)); _converse.roster.each((contact) => {
this.addRosterContact(contact, {'silent': true});
});
this.update(); this.update();
this.updateFilter();
this.trigger('rosterContactsFetchedAndProcessed');
}); });
this.createRosterFilter(); this.createRosterFilter();
}, },
...@@ -474,11 +478,11 @@ ...@@ -474,11 +478,11 @@
return this.model.create({name, id: b64_sha1(name)}); return this.model.create({name, id: b64_sha1(name)});
}, },
addContactToGroup (contact, name) { addContactToGroup (contact, name, options) {
this.getGroup(name).contacts.add(contact); this.getGroup(name).contacts.add(contact, options);
}, },
addExistingContact (contact) { addExistingContact (contact, options) {
let groups; let groups;
if (_converse.roster_groups) { if (_converse.roster_groups) {
groups = contact.get('groups'); groups = contact.get('groups');
...@@ -488,17 +492,17 @@ ...@@ -488,17 +492,17 @@
} else { } else {
groups = [HEADER_CURRENT_CONTACTS]; groups = [HEADER_CURRENT_CONTACTS];
} }
_.each(groups, _.bind(this.addContactToGroup, this, contact)); _.each(groups, _.bind(this.addContactToGroup, this, contact, _, options));
}, },
addRosterContact (contact) { addRosterContact (contact, options) {
if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to') { if (contact.get('subscription') === 'both' || contact.get('subscription') === 'to') {
this.addExistingContact(contact); this.addExistingContact(contact, options);
} else { } else {
if ((contact.get('ask') === 'subscribe') || (contact.get('subscription') === 'from')) { if ((contact.get('ask') === 'subscribe') || (contact.get('subscription') === 'from')) {
this.addContactToGroup(contact, HEADER_PENDING_CONTACTS); this.addContactToGroup(contact, HEADER_PENDING_CONTACTS, options);
} else if (contact.get('requesting') === true) { } else if (contact.get('requesting') === true) {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS); this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS, options);
} }
} }
return this; return this;
...@@ -670,22 +674,34 @@ ...@@ -670,22 +674,34 @@
}); });
_converse.RosterGroupView = Backbone.Overview.extend({ _converse.RosterGroupView = Backbone.OrderedListView.extend({
tagName: 'div', tagName: 'div',
className: 'roster-group', className: 'roster-group',
events: { events: {
"click a.group-toggle": "toggle" "click a.group-toggle": "toggle"
}, },
listItems: 'model.contacts',
sortEvent: 'change:chat_status',
listSelector: '.roster-group-contacts',
ItemView: _converse.RosterContactView,
initialize () { initialize () {
this.sortEventually = _.debounce(this.sortAndPositionAll, 500); Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.model.contacts.on("add", this.onContactAdded, this);
this.model.contacts.on("change:subscription", this.onContactSubscriptionChange, this); this.model.contacts.on("change:subscription", this.onContactSubscriptionChange, this);
this.model.contacts.on("change:requesting", this.onContactRequestChange, this); this.model.contacts.on("change:requesting", this.onContactRequestChange, this);
this.model.contacts.on("change:chat_status", this.sortEventually, this);
this.model.contacts.on("destroy", this.onRemove, this); this.model.contacts.on("destroy", this.onRemove, this);
this.model.contacts.on("remove", this.onRemove, this); this.model.contacts.on("remove", this.onRemove, this);
_converse.roster.on('change:groups', this.onContactGroupChange, this); _converse.roster.on('change:groups', this.onContactGroupChange, this);
// This event gets triggered once *all* contacts (i.e. not
// just this group's) have been fetched from browser
// storage or the XMPP server and once they've been
// assigned to their various groups.
_converse.rosterview.on(
'rosterContactsFetchedAndProcessed',
this.sortAndPositionAllItems.bind(this)
);
}, },
render () { render () {
...@@ -700,49 +716,19 @@ ...@@ -700,49 +716,19 @@
return this; return this;
}, },
createContactView (contact) { createItemView (contact) {
const contact_view = new _converse.RosterContactView({model: contact}); const contact_view =
this.add(contact.get('id'), contact_view); Backbone.OrderedListView.prototype.createItemView.apply(this, arguments);
contact_view.render();
return contact_view;
},
onContactAdded (contact) {
const contact_view = this.positionContact(contact);
if (contact_view.mayBeShown()) { if (contact_view.mayBeShown()) {
if (this.model.get('state') === _converse.CLOSED) { if (this.model.get('state') === _converse.CLOSED) {
u.hideElement(contact_view.el); u.hideElement(contact_view.el);
u.showElement(this.el);
} else { } else {
u.showElement(contact_view.el); u.showElement(contact_view.el);
u.showElement(this.el);
} }
u.showElement(this.el);
} }
}, },
positionContact (contact) {
/* Place the contact's DOM element in the correct alphabetical
* position amongst the other contacts in this group.
*/
const view = this.get(contact.get('id')) || this.createContactView(contact);
const list = this.contacts_el;
const index = this.model.contacts.indexOf(contact);
if (index === 0) {
list.insertAdjacentElement('afterbegin', view.el);
} else if (index === (this.model.contacts.length-1)) {
list.insertAdjacentElement('beforeend', view.el);
} else {
const neighbour_el = list.querySelector('li:nth-child('+index+')');
neighbour_el.insertAdjacentElement('afterend', view.el);
}
return view;
},
sortAndPositionAll () {
this.model.contacts.sort();
this.model.contacts.each(this.positionContact.bind(this));
},
show () { show () {
u.showElement(this.el); u.showElement(this.el);
_.each(this.getAll(), (contact_view) => { _.each(this.getAll(), (contact_view) => {
...@@ -851,7 +837,7 @@ ...@@ -851,7 +837,7 @@
if (in_this_group && !in_this_overview) { if (in_this_group && !in_this_overview) {
this.model.contacts.remove(cid); this.model.contacts.remove(cid);
} else if (!in_this_group && in_this_overview) { } else if (!in_this_group && in_this_overview) {
this.onContactAdded(contact); this.items.trigger('add', contact);
} }
}, },
......
...@@ -106,6 +106,7 @@ ...@@ -106,6 +106,7 @@
'bosh_service_url': 'localhost', 'bosh_service_url': 'localhost',
'connection': connection, 'connection': connection,
'animate': false, 'animate': false,
'use_emojione': false,
'no_trimming': true, 'no_trimming': true,
'auto_login': true, 'auto_login': true,
'jid': 'dummy@localhost', 'jid': 'dummy@localhost',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment