Commit ac643ae6 authored by JC Brand's avatar JC Brand

Refactor views to use Backbone.OrderedListView

parent 102f39ed
......@@ -73,21 +73,25 @@
spyOn(_converse.chatboxviews, 'trimChats');
expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(15);
for (i=0; i<online_contacts.length; i++) {
$el = $(online_contacts[i]);
jid = $el.text().trim().replace(/ /g,'.').toLowerCase() + '@localhost';
$el.click();
chatboxview = _converse.chatboxviews.get(jid);
expect(_converse.chatboxes.length).toEqual(i+2);
expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
// Check that new chat boxes are created to the left of the
// controlbox (but to the right of all existing chat boxes)
expect($("#conversejs .chatbox").length).toBe(i+2);
expect($("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id'));
}
done();
test_utils.waitUntil(function () {
return _converse.rosterview.$el.find('.roster-group li').length;
}, 700).then(function () {
var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(15);
for (i=0; i<online_contacts.length; i++) {
$el = $(online_contacts[i]);
jid = $el.text().trim().replace(/ /g,'.').toLowerCase() + '@localhost';
$el.click();
chatboxview = _converse.chatboxviews.get(jid);
expect(_converse.chatboxes.length).toEqual(i+2);
expect(_converse.chatboxviews.trimChats).toHaveBeenCalled();
// Check that new chat boxes are created to the left of the
// controlbox (but to the right of all existing chat boxes)
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",
......@@ -109,10 +113,10 @@
spyOn(trimmed_chatboxes, 'removeChat').and.callThrough();
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 () {
return _converse.rosterview.$el.find('.roster-group').length;
}, 300).then(function () {
return _converse.rosterview.$el.find('.roster-group li').length;
}, 700).then(function () {
// Test that they can be maximized again
var online_contacts = _converse.rosterview.$el.find('.roster-group .current-xmpp-contact a.open-chat');
expect(online_contacts.length).toBe(15);
......@@ -836,10 +840,9 @@
}).c('body').t('Message: '+i).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
}
test_utils.waitUntil(function () {
return chatboxview.$content.scrollTop();
}, 1000)
.then(function () {
return test_utils.waitUntil(function () {
return chatboxview.$content.scrollTop();
}, 1000).then(function () {
return test_utils.waitUntil(function () {
return !chatboxview.model.get('auto_scrolled');
}, 500);
......@@ -872,7 +875,7 @@
chatboxview.$content.scrollTop(chatboxview.$content[0].scrollHeight);
return test_utils.waitUntil(function () {
return !chatboxview.$('.new-msgs-indicator').is(':visible');
}, 500);
}, 700);
}).then(done);
}));
......@@ -1541,7 +1544,7 @@
view.model.maximize();
return test_utils.waitUntil(function () {
return view.model.get('chat_state') === 'active';
}, 500);
}, 700);
}).then(function () {
expect(_converse.connection.send).toHaveBeenCalled();
var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
......@@ -1693,9 +1696,8 @@
test_utils.openControlBox();
test_utils.openContactsPanel(_converse);
test_utils.waitUntil(function () {
return _converse.rosterview.$el.find('.roster-group').length;
}, 300)
.then(function () {
return _converse.rosterview.$el.find('.roster-group li').length;
}, 700).then(function () {
_converse.TIMEOUTS.PAUSED = 200; // Make the timeout shorter so that we can test
contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
......@@ -1715,7 +1717,7 @@
return test_utils.waitUntil(function () {
return view.model.get('chat_state') === 'paused';
}, 500);
}).then(function () {
}).then(function () {
expect(_converse.connection.send).toHaveBeenCalled();
var calls = _.filter(_converse.connection.send.calls.all(), function (call) {
return call.args[0] instanceof Strophe.Builder;
......
This diff is collapsed.
......@@ -228,11 +228,11 @@
expect(_converse.roster.updateContact).toHaveBeenCalled();
// Check that the user is now properly shown as a pending
// contact in the roster.
var $header = $('a:contains("Pending contacts")');
return test_utils.waitUntil(function () {
return $('a:contains("Pending contacts")').length && $header.is(":visible");
}, 300);
var $header = $('a:contains("Pending contacts")');
var $contacts = $header.parent().find('li');
return $contacts.length;
}, 600);
}).then(function () {
var $header = $('a:contains("Pending contacts")');
var $contacts = $header.parent().find('li');
......@@ -297,10 +297,16 @@
// The contact should now be visible as an existing
// 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.is(":visible")).toBeTruthy();
$contacts = $header.parent().find('li');
var $contacts = $header.parent().find('li');
expect($contacts.length).toBe(1);
// Check that it has the right classes and text
expect($contacts.hasClass('to')).toBeTruthy();
......@@ -482,10 +488,12 @@
sent_IQ = iq;
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")');
// remove the first user
$($header.parent().find('li .remove-xmpp-contact').get(0)).click();
......@@ -547,9 +555,11 @@
'xmlns': Strophe.NS.NICK,
}).t('Clint Contact');
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(function () {
return $('a:contains("Contact requests")').length;
}).then(function () {
return test_utils.waitUntil(function () {
var $header = $('a:contains("Contact requests")');
var $contacts = $header.parent().find('li');
return $contacts.length;
}, 600).then(function () {
expect(_converse.emit).toHaveBeenCalledWith('contactRequest', jasmine.any(Object));
var $header = $('a:contains("Contact requests")');
expect($header.length).toBe(1);
......
......@@ -22,6 +22,7 @@ require.config({
"backbone.browserStorage": "node_modules/backbone.browserStorage/backbone.browserStorage",
"backbone.noconflict": "src/backbone.noconflict",
"backbone.overview": "node_modules/backbone.overview/backbone.overview",
"backbone.orderedlistview": "node_modules/backbone.overview/backbone.orderedlistview",
"backbone.vdomview": "node_modules/backbone.vdomview/backbone.vdomview",
"emojione": "node_modules/emojione/lib/js/emojione",
"es6-promise": "node_modules/es6-promise/dist/es6-promise.auto",
......
......@@ -16,8 +16,7 @@
"strophe",
"pluggable",
"backbone.noconflict",
"backbone.browserStorage",
"backbone.overview",
"backbone.browserStorage"
], factory);
}(this, function (sizzle, Promise, _, polyfill, i18n, utils, moment, Strophe, pluggable, Backbone) {
......
......@@ -37,6 +37,8 @@
"awesomplete",
"converse-chatview",
"converse-disco",
"backbone.overview",
"backbone.orderedlistview",
"backbone.vdomview"
], factory);
}(this, function (
......@@ -2213,16 +2215,17 @@
},
});
_converse.ChatRoomOccupantsView = Backbone.Overview.extend({
_converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({
tagName: 'div',
className: 'occupants',
listItems: 'model',
sortEvent: 'change:role',
listSelector: '.occupant-list',
ItemView: _converse.ChatRoomOccupantView,
initialize () {
this.model.on("add", this.onOccupantAdded, this);
this.model.on("change:role", (occupant) => {
this.model.sort();
this.positionOccupant(occupant);
});
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.chatroomview = this.model.chatroomview;
this.chatroomview.model.on('change:open', this.renderInviteWidget, this);
......@@ -2247,9 +2250,7 @@
this.model.fetch({
'add': true,
'silent': true,
'success': () => {
this.model.each(this.onOccupantAdded.bind(this));
}
'success': this.sortAndPositionAllItems.bind(this)
});
},
......@@ -2353,57 +2354,12 @@
this.debouncedRenderRoomFeatures();
},
setOccupantsHeight () {
const el = this.el.querySelector('.chatroom-features');
this.el.querySelector('.occupant-list').style.cssText =
`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) {
const id = Strophe.getResourceFromJid(pres.getAttribute("from"));
const data = {
......
......@@ -285,8 +285,12 @@
this.model.on("reset", this.reset, this);
_converse.on('rosterGroupsFetched', this.positionFetchedGroups, this);
_converse.on('rosterContactsFetched', () => {
_converse.roster.each(this.onContactAdded.bind(this));
_converse.roster.each((contact) => {
this.addRosterContact(contact, {'silent': true});
});
this.update();
this.updateFilter();
this.trigger('rosterContactsFetchedAndProcessed');
});
this.createRosterFilter();
},
......@@ -474,11 +478,11 @@
return this.model.create({name, id: b64_sha1(name)});
},
addContactToGroup (contact, name) {
this.getGroup(name).contacts.add(contact);
addContactToGroup (contact, name, options) {
this.getGroup(name).contacts.add(contact, options);
},
addExistingContact (contact) {
addExistingContact (contact, options) {
let groups;
if (_converse.roster_groups) {
groups = contact.get('groups');
......@@ -488,17 +492,17 @@
} else {
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') {
this.addExistingContact(contact);
this.addExistingContact(contact, options);
} else {
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) {
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS);
this.addContactToGroup(contact, HEADER_REQUESTING_CONTACTS, options);
}
}
return this;
......@@ -670,22 +674,34 @@
});
_converse.RosterGroupView = Backbone.Overview.extend({
_converse.RosterGroupView = Backbone.OrderedListView.extend({
tagName: 'div',
className: 'roster-group',
events: {
"click a.group-toggle": "toggle"
},
listItems: 'model.contacts',
sortEvent: 'change:chat_status',
listSelector: '.roster-group-contacts',
ItemView: _converse.RosterContactView,
initialize () {
this.sortEventually = _.debounce(this.sortAndPositionAll, 500);
this.model.contacts.on("add", this.onContactAdded, this);
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
this.model.contacts.on("change:subscription", this.onContactSubscriptionChange, 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("remove", this.onRemove, 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 () {
......@@ -700,49 +716,19 @@
return this;
},
createContactView (contact) {
const contact_view = new _converse.RosterContactView({model: contact});
this.add(contact.get('id'), contact_view);
contact_view.render();
return contact_view;
},
onContactAdded (contact) {
const contact_view = this.positionContact(contact);
createItemView (contact) {
const contact_view =
Backbone.OrderedListView.prototype.createItemView.apply(this, arguments);
if (contact_view.mayBeShown()) {
if (this.model.get('state') === _converse.CLOSED) {
u.hideElement(contact_view.el);
u.showElement(this.el);
} else {
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 () {
u.showElement(this.el);
_.each(this.getAll(), (contact_view) => {
......@@ -851,7 +837,7 @@
if (in_this_group && !in_this_overview) {
this.model.contacts.remove(cid);
} else if (!in_this_group && in_this_overview) {
this.onContactAdded(contact);
this.items.trigger('add', contact);
}
},
......
......@@ -106,6 +106,7 @@
'bosh_service_url': 'localhost',
'connection': connection,
'animate': false,
'use_emojione': false,
'no_trimming': true,
'auto_login': true,
'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