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

Dynamic updating of event interface on event admin change. #425

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/unhangout-sockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,13 @@ _.extend(UnhangoutSocketManager.prototype, events.EventEmitter.prototype, {
value: message
});
});
this.db.events.on("change:admins", function(event, admins) {
mgr.sync(event.getRoomId(), "state", {
path: ["event", "admins"],
op: "set",
value: admins
});
});

//
// Hangout-on-air events
Expand Down
69 changes: 55 additions & 14 deletions public/js/event-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ $(document).ready(function() {
});

curEvent.on("change:open", function(model, open, options) {
if (IS_ADMIN) {
if (app.currentUser.isAdminOf(curEvent)) {
app.chatView.chatInputView.onRender();
} else if (!open) {
window.location.reload();
Expand Down Expand Up @@ -121,32 +121,66 @@ $(document).ready(function() {
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

this.currentUser = new models.User(USER);
this.currentUserIsAdmin = (function() {
return this.currentUser.isAdminOf(curEvent);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The this value here isn't enclosed -- won't this.currentUser be undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because this.currentUserIsAdmin() is only called in the context of the initializer. I've tested it, it works. :) Did you have some other approach in mind?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you that it works. I'm concerned about the code style of going 3 levels deep in the call stack with this and having it work -- it seems brittle; any variation in how those methods get invoked and the binding is lost.

We could either explicitly bind "this" in the function definitions (e.g. (function() {}).bind(this)), or enclose a reference to this which isn't dynamic, e.g.:

var that = this
that.currentUserIsAdmin = function() {
    return that.currentUser.isAdminOf(curEvent);
};

Just concerned about maintainability of that code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've addressed your concern by binding 'this' to the function.

}).bind(this);

// create all the basic views
this.sessionListView = new eventViews.SessionListView({
collection: curEvent.get("sessions"),
currentUser: this.currentUser,
event: curEvent,
transport: trans
});
this.topicListView = new eventViews.TopicListView({
collection: curEvent.get("sessions"),
currentUser: this.currentUser,
event: curEvent,
transport: trans
});
this.chatView = new eventViews.ChatLayout({
currentUser: this.currentUser,
messages: messages,
users: curEvent.get("connectedUsers"),
event: curEvent,
transport: trans
});
this.youtubeEmbedView = new eventViews.VideoEmbedView({
model: curEvent, transport: trans
model: curEvent, transport: trans,
currentUser: this.currentUser
});
this.dialogView = new eventViews.DialogView({
event: curEvent, transport: trans
});

this.aboutView = new eventViews.AboutEventView({model: curEvent});

// Always include this in the DOM for callback support, even if it's
// not inserted.
this.adminButtonView = new eventViews.AdminButtonView({
event: curEvent, transport: trans
});

this.adminChanged = function() {
var previousAdmins = _.pluck(curEvent.previous("admins"), "id");
var currentAdmins = _.pluck(curEvent.get("admins"), "id");
// This user's interface needs to be refreshed if:
// 1. The user is in either the previous or current list.
// 2. The user isn't in both lists.
// Refresh all views that are impacted by admin status.
var inPrevious = previousAdmins.indexOf(USER.id) !== -1;
var inCurrent = currentAdmins.indexOf(USER.id) !== -1;
if ((inPrevious || inCurrent) && !(inPrevious && inCurrent)) {
this.setupAdminControls();
this.youtubeEmbedView.render();
this.sessionListView.render();
this.topicListView.render();
this.chatView.whiteboardView.render();
this.chatView.chatInputView.render();
}
}

// present the views in their respective regions
this.right.show(this.chatView);
this.topLeft.show(this.youtubeEmbedView);
Expand Down Expand Up @@ -179,6 +213,10 @@ $(document).ready(function() {
$("#topic-list").hide();
}

curEvent.on("change:admins", _.bind(function() {
this.adminChanged();
}, this));

curEvent.on("change:adminProposedSessions change:sessionsOpen change:open", _.bind(function() {
this.adminButtonView.render();
}, this));
Expand Down Expand Up @@ -325,21 +363,24 @@ $(document).ready(function() {
}
}

// obviously this is not secure, but any admin requests are re-authenticated on
// the server. Showing the admin UI is harmless if a non-admin messes with it.
if(IS_ADMIN) {
if (NUM_HANGOUT_URLS_WARNING > 0 && NUM_HANGOUT_URLS_AVAILABLE < NUM_HANGOUT_URLS_WARNING) {
console.error("Too few hangout URLS available!", NUM_HANGOUT_URLS_AVAILABLE);
}
this.adminButtonView = new eventViews.AdminButtonView({
event: curEvent, transport: trans
});
this.hotkeys = this.initHotkeys();

this.admin.show(this.adminButtonView);
this.hotkeys = this.initHotkeys();
this.hotkeys.activate($(document));
this.setupAdminControls = function() {
if(this.currentUserIsAdmin()) {
this.admin.show(this.adminButtonView);
this.hotkeys.activate($(document));
}
else {
// Marionette requires a view, and there's no need to destroy
// the existing admin view, so use a dummy view to hide it.
var dummyView = new Backbone.Marionette.ItemView({template: '#dummy-template'});
this.admin.show(dummyView);
this.hotkeys.deactivate($(document));
}
}

this.setupAdminControls();

var maybeMute = function() {
var hoa = curEvent.get("hoa");
if (hoa && _.findWhere(hoa.get("connectedParticipants"), {id: auth.USER_ID})) {
Expand Down
52 changes: 39 additions & 13 deletions public/js/event-views.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ views.SessionView = Backbone.Marionette.ItemView.extend({
this.listenTo(this.model, 'change:title', this.render, this);
},

serializeData: function() {
return _.extend(this.model.toJSON(), {
eventAdmin: this.options.currentUser.isAdminOf(this.options.event)
});
},

onRender: function() {
$('.tooltip').hide();
var start = new Date().getTime();
Expand All @@ -87,7 +93,7 @@ views.SessionView = Backbone.Marionette.ItemView.extend({
}

//Show delete button only for admins
if(IS_ADMIN) {
if(this.options.currentUser.isAdminOf(this.options.event)) {
this.ui.deleteButton.show();
} else {
this.ui.deleteButton.hide();
Expand All @@ -109,7 +115,7 @@ views.SessionView = Backbone.Marionette.ItemView.extend({
this.ui.unapprove.hide();
this.ui.proposeeDetails.hide();

if(IS_ADMIN) {
if(this.options.currentUser.isAdminOf(this.options.event)) {
this.ui.deleteButton.removeClass("top-margin");
}

Expand Down Expand Up @@ -350,6 +356,12 @@ views.TopicView = Backbone.Marionette.ItemView.extend({
this.listenTo(this.model, 'change:title', this.render, this);
},

serializeData: function() {
return _.extend(this.model.toJSON(), {
eventAdmin: this.options.currentUser.isAdminOf(this.options.event)
});
},

onRender: function() {
$('.tooltip').hide();

Expand Down Expand Up @@ -382,7 +394,7 @@ views.TopicView = Backbone.Marionette.ItemView.extend({
//, hide approve button from the topic template and position
// delete button

if(!IS_ADMIN && (this.model.get("proposedBy") &&
if(!this.options.currentUser.isAdminOf(this.options.event) && (this.model.get("proposedBy") &&
USER.id === this.model.get("proposedBy").id)) {
this.ui.approve.hide();
this.ui.deleteButton.addClass("pos-admin-delete");
Expand Down Expand Up @@ -491,7 +503,9 @@ views.SessionListView = Backbone.Marionette.CollectionView.extend({

itemViewOptions: function() {
return {
event: this.options.event, transport: this.options.transport
event: this.options.event,
currentUser: this.options.currentUser,
transport: this.options.transport
};
},

Expand Down Expand Up @@ -524,7 +538,9 @@ views.TopicListView = Backbone.Marionette.CollectionView.extend({

itemViewOptions: function() {
return {
event: this.options.event, transport: this.options.transport
event: this.options.event,
currentUser: this.options.currentUser,
transport: this.options.transport
};
},

Expand Down Expand Up @@ -1132,6 +1148,7 @@ views.ChatLayout = Backbone.Marionette.Layout.extend({
Backbone.Marionette.View.prototype.initialize.call(this, options);
this.whiteboardView = new views.WhiteboardView({
model: this.options.event,
currentUser: this.options.currentUser,
transport: this.options.transport,
messages: this.options.messages
});
Expand All @@ -1155,6 +1172,7 @@ views.ChatLayout = Backbone.Marionette.Layout.extend({

this.chatInputView = new views.ChatInputView({
event: this.options.event,
currentUser: this.options.currentUser,
transport: this.options.transport
});

Expand Down Expand Up @@ -1210,7 +1228,7 @@ views.WhiteboardView = Backbone.Marionette.ItemView.extend({

// Function to toggle the view of the form only if the user is an admin
toggleForm: function(){
if(IS_ADMIN){
if(this.options.currentUser.isAdminOf(this.model)){
this.ui.form.toggle();
this.ui.buttons.toggle();
this.ui.message.toggle();
Expand All @@ -1234,7 +1252,7 @@ views.WhiteboardView = Backbone.Marionette.ItemView.extend({
this.ui.message.html(utils.linkify(_.escape(whiteboard.message)));
} else {
// If not an admin, we hide the whole whiteboard, else we show an empty whiteboard for admins
if(IS_ADMIN){
if(this.options.currentUser.isAdminOf(this.model)){
this.ui.message.html('')
} else {
this.ui.message.hide();
Expand All @@ -1249,7 +1267,8 @@ views.WhiteboardView = Backbone.Marionette.ItemView.extend({
chatArchiveUrl = this.model.getChatArchiveUrl();
}
return _.extend(this.model.toJSON(), {
chatArchiveUrl: this.model.getChatArchiveUrl()
chatArchiveUrl: this.model.getChatArchiveUrl(),
eventAdmin: this.options.currentUser.isAdminOf(this.model)
});
}
});
Expand All @@ -1271,9 +1290,15 @@ views.ChatInputView = Backbone.Marionette.ItemView.extend({
Backbone.Marionette.View.prototype.initialize.call(this, options);
},

serializeData: function() {
return {
eventAdmin: this.options.currentUser.isAdminOf(this.options.event)
};
},

chat: function(e) {
var msg = this.ui.chatInput.val();
var postAsAdmin = IS_ADMIN && this.ui.asAdmin.is(":checked");
var postAsAdmin = this.options.currentUser.isAdminOf(this.options.event) && this.ui.asAdmin.is(":checked");

if(msg.length>0) {

Expand Down Expand Up @@ -1598,6 +1623,7 @@ views.VideoEmbedView = Backbone.Marionette.ItemView.extend({
if (this.model.get("hoa")) {
context.hoa = this.model.get("hoa").toJSON();
}
context.eventAdmin = this.options.currentUser.isAdminOf(this.model);
return context;
},
setVideo: function(jqevt) {
Expand Down Expand Up @@ -1672,9 +1698,9 @@ views.VideoEmbedView = Backbone.Marionette.ItemView.extend({
this.ui.player.toggle(visible);
// Show a placeholder ("video goes here") if video is not visible and
// the user is an admin. Non-admins get nothing.
this.ui.placeholder.toggle(!visible && IS_ADMIN);
this.ui.placeholder.toggle(!visible && this.options.currentUser.isAdminOf(this.model));
// Always show controls if the user is an admin.
this.ui.controls.toggle(IS_ADMIN);
this.ui.controls.toggle(this.options.currentUser.isAdminOf(this.model));
},
renderControls: function() {
var hoa = this.model.get("hoa");
Expand Down Expand Up @@ -1708,10 +1734,10 @@ views.VideoEmbedView = Backbone.Marionette.ItemView.extend({
onRender: function() {
this.yt = new video.YoutubeVideo({
ytID: this.model.get("youtubeEmbed"),
permitGroupControl: IS_ADMIN,
permitGroupControl: this.options.currentUser.isAdminOf(this.model),
showGroupControls: false // We're doing our own controls on event pages.
});
if (IS_ADMIN) {
if (this.options.currentUser.isAdminOf(this.model)) {
this.yt.on("renderControls", _.bind(function() {
this.renderControls();
}, this));
Expand Down
20 changes: 11 additions & 9 deletions views/event.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

<script>
// Initial data
var IS_ADMIN = <%= event && user.isAdminOf(event) %>;
var IS_SUPERUSER = <%= event && user.isSuperuser() %>;
var USER = <%- JSON.stringify(user) %>;
var NUM_HANGOUT_URLS_AVAILABLE = <%= numHangoutUrlsAvailable %>;
Expand Down Expand Up @@ -70,7 +69,7 @@
<script type="text/template" id="session-template">
<div class="col-xs-12 session-area">

{{ if(IS_ADMIN) { }}
{{ if(eventAdmin) { }}
<div class="col-xs-8 session-padding">

<div class="col-xs-12 session-padding session-title-container">
Expand Down Expand Up @@ -145,7 +144,7 @@

</div>

{{ if(IS_ADMIN) { }}
{{ if(eventAdmin) { }}
<div class="col-xs-1 session-padding left-padding">

<div class="col-xs-12 session-padding btn-admin-unapprove-session">
Expand All @@ -166,15 +165,15 @@
<script type="text/template" id="topic-template">
<div class="col-xs-12 session-area">

{{ if(IS_ADMIN || (proposedBy && proposedBy.id == USER.id)) { }}
{{ if(eventAdmin || (proposedBy && proposedBy.id == USER.id)) { }}

<div class="col-xs-8 topic-padding topic-title-container">
<div class="col-xs-1 topic-padding">
<button class="btn btn-primary btn-edit-topic"><i class="fa fa-pencil"></i></button>
</div>

<div class="col-xs-11 topic-title-padding">
<h3 class="topic-title">{{- title }} </h3>
<h3 class="topic-title">{{- title }} </h3>
</div>
</div>

Expand Down Expand Up @@ -214,12 +213,12 @@

<div class="col-xs-12 topic-padding vote-session">
<button class="btn btn-primary btn-vote" data-toggle="tooltip" data-placement="bottom" data-container="body" title="" data-original-title="Vote for this topic">
VOTE | {{- votes }}
VOTE | {{- votes }}
</button>
</div>
</div>

{{ if(IS_ADMIN || (proposedBy && proposedBy.id == USER.id)) { }}
{{ if(eventAdmin || (proposedBy && proposedBy.id == USER.id)) { }}

<div class="col-xs-1 topic-padding left-padding">

Expand Down Expand Up @@ -374,7 +373,7 @@
<script type="text/template" id="chat-whiteboard-template">
<div id="whiteboard-message"></div>

{{ if(IS_ADMIN) { }}
{{ if(eventAdmin) { }}
<div id="whiteboard-form">
<textarea placeholder="whiteboard message here..." rows="1"></textarea>
<button class='update-whiteboard btn btn-success'>
Expand All @@ -400,7 +399,7 @@
<script type="text/template" id="chat-input-template">
<form>
<input type="text" id="chat-input" class="form-control" autocomplete="off" placeholder='Type your message here'></input>
{{ if (IS_ADMIN) { }}
{{ if (eventAdmin) { }}
<label class='admin'
title='Check to highlight your message.'
data-role='tooltip'
Expand Down Expand Up @@ -1167,6 +1166,9 @@
</div>
</script>

<script type="text/template" id="dummy-template">
</script>

<% include _video-templates.ejs %>

<%- requireScripts("/public/js/event-app.js") %>
Expand Down