Skip to content
This repository has been archived by the owner on Nov 7, 2020. It is now read-only.

Commit

Permalink
Preserve list position on leaving/joining session
Browse files Browse the repository at this point in the history
  • Loading branch information
yourcelf committed Apr 14, 2014
1 parent f6020dc commit 3bd1e7a
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 23 deletions.
81 changes: 60 additions & 21 deletions public/js/event-views.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ views.SessionView = Backbone.Marionette.ItemView.extend({
// re-render to show them.
this.listenTo(this.model, 'change:connectedParticipants', this.render, this);
this.listenTo(this.model, 'change:joiningParticipants', this.render, this);
// Maintain a list of slots and user preferences for them, so that we
// can render people in consistent-ish places in the list.
// The idea is that each user gets a "slotPreference", which is either
// the last slot they were rendered in. If their preferred slot is occupied,
// they get the next unused slot, and their "preference" is updated.
this.userSlotPreference = {};
this.userSlots = {};
},

onRender: function() {
Expand Down Expand Up @@ -78,9 +85,29 @@ views.SessionView = Backbone.Marionette.ItemView.extend({
// the hangout-users div, and populate it with users.
this.$el.addClass("hangout-connected");

//
// Build the list of user views.
//

var fragment = document.createDocumentFragment();
var drawUser = function (udata, joining) {

// clear out slots for users that are no longer connected, and
// construct an array of any slots that are available.
var connectedAndJoining = _.pluck(this.model.get("connectedParticipants"), "id")
.concat(_.pluck(this.model.get("joiningParticipants"), "id"));
var available = [];
for (var i = 0; i < this.model.MAX_ATTENDEES; i++) {
if (this.userSlots[i]) {
if (_.contains(connectedAndJoining, this.userSlots[i].id)) {
continue;
}
delete this.userSlots[i];
}
available.push(i);
}

var drawUser = _.bind(function (udata, joining) {
// Get the user view.
var userView;
if (udata.id in userViewCache) {
userView = userViewCache[udata.id];
Expand All @@ -97,18 +124,41 @@ views.SessionView = Backbone.Marionette.ItemView.extend({
if (joining) {
el.className += " joining";
}
fragment.appendChild(el);
}
// Add connected users

// Determine where it goes.
var slot = this.userSlots[this.userSlotPreference[udata.id]];
if (slot && slot.id === udata.id) {
slot.el = el;
} else {
var pos = null;
var pref = this.userSlotPreference[udata.id];
if (pref && _.contains(available, pref)) {
pos = pref;
available = _.without(available, pref);
} else if (available.length > 0) {
pos = available.shift();
}
if (pos !== null) {
this.userSlotPreference[udata.id] = pos;
slot = {id: udata.id, el: el}
this.userSlots[pos] = slot;
}
}
}, this);

// build slots for connected users
_.each(this.model.get("connectedParticipants"), function(udata) { drawUser(udata); });
// Add joining users
// ... and joining users
_.each(this.model.get("joiningParticipants"), function(udata) { drawUser(udata, true); });
var emptyli;
for(var i = 0; i < this.model.MAX_ATTENDEES - numAttendees; i++) {
//this.ui.hangoutUsers.append($("<li class='empty'></li>"));
emptyli = document.createElement("li");
emptyli.className = "empty";
fragment.appendChild(emptyli);
for (var i = 0; i < this.model.MAX_ATTENDEES; i++) {
if (this.userSlots[i]) {
fragment.appendChild(this.userSlots[i].el);
} else {
emptyli = document.createElement("li");
emptyli.className = "empty";
fragment.appendChild(emptyli);
}
}

// Now add the fragment to the layout and display it
Expand Down Expand Up @@ -443,8 +493,6 @@ views.UserColumnLayout = Backbone.Marionette.Layout.extend({
});

// The actual core UserListView that manages displaying each individual user.
// This logic is quite similar to the SessionListView, which also deals with
// pagination in a flexible-height space.
views.UserListView = Backbone.Marionette.CompositeView.extend({
template: '#user-list-template',
itemView: views.UserView,
Expand All @@ -464,15 +512,6 @@ views.UserListView = Backbone.Marionette.CompositeView.extend({
// are removed, but totalUnfilteredRecords does. Could
// be a bug.

// Other side note: be aware that there is some magic in
// marionette around adding to collections. It apparently
// tries to just auto-add the new record to the
// itemViewContainer. This is a little weird when
// combined with the pagination system, which doesn't
// necessarily show all incoming models. Just something
// to keep an eye on. More info here:
// https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.compositeview.md#model-and-collection-rendering

this.$el.find(".header .contents").text(this.collection.length);
}, this);
},
Expand Down
5 changes: 3 additions & 2 deletions test/test.admin-users.selenium.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ describe("ADMIN USERS SELENIUM", function() {
// Wait for modal to fade in...
browser.waitForSelector(".modal-body select");
browser.byCss(".modal-body select").sendKeys(event.get("title"));
browser.byLinkText("Add").click().then(function() {
expect(user.isAdminOf(event)).to.be(true);
browser.byLinkText("Add").click();
browser.wait(function() {
return user.isAdminOf(event) === true;
});

// Ensure the new admin can access the admin page.
Expand Down
68 changes: 68 additions & 0 deletions test/test.session-joining.selenium.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,72 @@ describe("SESSION JOINING PARTICIPANT LISTS", function() {
done();
});
});

it("Preserves session list position", function(done) {
this.timeout(80000);
// We'll use 3 sockets --- s1, s2, s3 in the session.
var s1, s2, s3;
var session = event.get("sessions").at(0);
var u1 = common.server.db.users.findWhere({"sock-key": "regular1"});
var u2 = common.server.db.users.findWhere({"sock-key": "regular2"});
var u3 = common.server.db.users.findWhere({"sock-key": "admin1"});
var observer = common.server.db.users.findWhere({"sock-key": "admin2"});
browser.get("http://localhost:7777/");
browser.mockAuthenticate(observer.get("sock-key"));
browser.get("http://localhost:7777" + event.getEventUrl());
browser.waitForScript("$");
browser.then(function() {
return common.authedSock(
u1.getSockKey(), session.getRoomId()
).then(function(sock) { s1 = sock; });
});
browser.then(function() {
return common.authedSock(
u2.getSockKey(), session.getRoomId()
).then(function(sock) { s2 = sock; });
});
browser.then(function() {
return common.authedSock(
u3.getSockKey(), session.getRoomId()
).then(function(sock) { s3 = sock; });
});
function checkSessionUsers(list) {
var selector ="[data-session-id='" + session.id + "'] ul.hangout-users li";
return browser.wait(function() {
return browser.byCsss(selector).then(function(els) {
return Promise.all(_.map(els, function(el, i) {
return new Promise(function(resolve, reject) {
el.getAttribute("class").then(function(cls) {
if (list[i] === null && cls === "empty") {
return resolve();
} else if (cls === "user focus") {
return resolve();
} else {
return reject("Unexpected class " + cls);
}
}).then(null, function(err) {
return reject("Selenium error");
});
});
})).catch(function() {
return false;
});
});
});
};
checkSessionUsers([u1, u2, u3, null, null, null, null, null, null, null]);
browser.then(function() { return s2.promiseClose(); });
checkSessionUsers([u1, null, u3, null, null, null, null, null, null, null]);
browser.then(function() { return s1.promiseClose(); });
checkSessionUsers([null, null, u3, null, null, null, null, null, null, null]);
browser.then(function() {
return common.authedSock(
u2.getSockKey(), session.getRoomId()
).then(function(sock) { s2 = sock });
});
checkSessionUsers([null, u2, u3, null, null, null, null, null, null, null]);
browser.then(function() { return s3.promiseClose(); })
browser.then(function() { return s2.promiseClose(); });
browser.then(function() { done(); });
});
});

0 comments on commit 3bd1e7a

Please sign in to comment.