Commit 923fb8a7 authored by cmrd Senya's avatar cmrd Senya

Refactor app.views.AspectMembership

in order to support adding new aspect to a dropdown without full
page reload
parent 15e0f887
......@@ -120,9 +120,6 @@ var app = {
setupGlobalViews: function() {
app.hovercard = new app.views.Hovercard();
$('.aspect_membership_dropdown').each(function(){
new app.views.AspectMembership({el: this});
});
app.sidebar = new app.views.Sidebar();
app.backToTop = new app.views.BackToTop({el: $(document)});
app.flashMessages = new app.views.FlashMessages({el: $("#flash-container")});
......
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.collections.AspectMemberships = Backbone.Collection.extend({
model: app.models.AspectMembership
model: app.models.AspectMembership,
findByAspectId: function(id) {
return this.find(function(membership) { return membership.belongsToAspect(id); });
}
});
// @license-end
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.collections.Aspects = Backbone.Collection.extend({
model: app.models.Aspect,
url: "/aspects"
});
// @license-end
......@@ -5,6 +5,11 @@
* (only valid for the context of the current user)
*/
app.models.AspectMembership = Backbone.Model.extend({
urlRoot: "/aspect_memberships"
urlRoot: "/aspect_memberships",
belongsToAspect: function(aspectId) {
var aspect = this.get("aspect");
return aspect && aspect.id === aspectId;
}
});
// @license-end
......@@ -3,11 +3,14 @@
app.models.Contact = Backbone.Model.extend({
initialize : function() {
this.aspectMemberships = new app.collections.AspectMemberships(this.get("aspect_memberships"));
if(this.get("person")) { this.person = new app.models.Person(this.get("person")); }
if (this.get("person")) {
this.person = new app.models.Person(this.get("person"));
this.person.contact = this;
}
},
inAspect : function(id) {
return this.aspectMemberships.any(function(membership){ return membership.get("aspect").id === id; });
return this.aspectMemberships.any(function(membership) { return membership.belongsToAspect(id); });
}
});
// @license-end
......@@ -6,8 +6,13 @@ app.models.Person = Backbone.Model.extend({
},
initialize: function() {
if( this.get('profile') )
this.profile = new app.models.Profile(this.get('profile'));
if (this.get("profile")) {
this.profile = new app.models.Profile(this.get("profile"));
}
if (this.get("contact")) {
this.contact = new app.models.Contact(this.get("contact"));
this.contact.person = this;
}
},
isSharing: function() {
......
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.pages.GettingStarted = app.views.Base.extend({
el: "#hello-there",
templateName: false,
subviews: {
".aspect_membership_dropdown": "aspectMembershipView"
},
initialize: function(opts) {
this.inviter = opts.inviter;
app.events.on("aspect:create", this.render, this);
},
aspectMembershipView: function() {
return new app.views.AspectMembership({person: this.inviter, dropdownMayCreateNewAspect: true});
}
});
// @license-end
......@@ -5,6 +5,7 @@ app.Router = Backbone.Router.extend({
"help/:section": "help",
"help/": "help",
"help": "help",
"getting_started": "gettingStarted",
"contacts": "contacts",
"conversations": "conversations",
"user/edit": "settings",
......@@ -60,6 +61,7 @@ app.Router = Backbone.Router.extend({
contacts: function() {
app.aspect = new app.models.Aspect(gon.preloads.aspect);
app.contacts = new app.collections.Contacts(app.parsePreload("contacts"));
this._loadAspects();
var stream = new app.views.ContactStream({
collection: app.contacts,
......@@ -69,6 +71,13 @@ app.Router = Backbone.Router.extend({
app.page = new app.pages.Contacts({stream: stream});
},
gettingStarted: function() {
this._loadAspects();
this.renderPage(function() {
return new app.pages.GettingStarted({inviter: new app.models.Person(app.parsePreload("inviter"))});
});
},
conversations: function() {
app.conversations = new app.views.Conversations();
},
......@@ -134,6 +143,7 @@ app.Router = Backbone.Router.extend({
},
aspects: function() {
this._loadAspects();
app.aspectSelections = app.aspectSelections ||
new app.collections.AspectSelections(app.currentUser.get("aspects"));
this.aspectsList = this.aspectsList || new app.views.AspectsList({collection: app.aspectSelections});
......@@ -157,6 +167,7 @@ app.Router = Backbone.Router.extend({
},
profile: function() {
this._loadAspects();
this.renderPage(function() {
return new app.pages.Profile({
el: $("body > #profile_container")
......@@ -164,6 +175,10 @@ app.Router = Backbone.Router.extend({
});
},
_loadAspects: function() {
app.aspects = new app.collections.Aspects(app.currentUser.get("aspects"));
},
_hideInactiveStreamLists: function() {
if(this.aspectsList && Backbone.history.fragment !== "aspects") {
this.aspectsList.hideAspectsList();
......
......@@ -9,20 +9,18 @@ app.views.AspectCreate = app.views.Base.extend({
},
initialize: function(opts) {
this._personId = _.has(opts, "personId") ? opts.personId : null;
if (opts && opts.person) {
this.person = opts.person;
this._personId = opts.person.id;
}
},
presenter: function() {
return _.extend(this.defaultPresenter(), {
addPersonId: this._personId !== null,
personId : this._personId
});
},
postRenderTemplate: function() {
this.modal = this.$(".modal");
},
_contactsVisible: function() {
return this.$("#aspect_contacts_visible").is(":checked");
},
......@@ -38,28 +36,52 @@ app.views.AspectCreate = app.views.Base.extend({
}
},
createAspect: function() {
var aspect = new app.models.Aspect({
"person_id": this._personId,
"name": this._name(),
"contacts_visible": this._contactsVisible()
postRenderTemplate: function() {
this.$(".modal").on("hidden.bs.modal", null, this, function(e) {
e.data.ensureEventsOrder();
});
},
createAspect: function() {
this._eventsCounter = 0;
var self = this;
aspect.on("sync", function(response) {
var aspectId = response.get("id"),
aspectName = response.get("name");
this.$(".modal").modal("hide");
self.modal.modal("hide");
app.events.trigger("aspect:create", aspectId);
this.listenToOnce(app.aspects, "sync", function(response) {
var aspectName = response.get("name"),
membership = response.get("aspect_membership");
this._newAspectId = response.get("id");
if (membership) {
if (!this.person.contact) {
this.person.contact = new app.models.Contact();
}
this.person.contact.aspectMemberships.add([membership]);
}
this.ensureEventsOrder();
app.flashMessages.success(Diaspora.I18n.t("aspects.create.success", {"name": aspectName}));
});
aspect.on("error", function() {
self.modal.modal("hide");
this.listenToOnce(app.aspects, "error", function() {
app.flashMessages.error(Diaspora.I18n.t("aspects.create.failure"));
this.stopListening(app.aspects, "sync");
});
app.aspects.create({
"person_id": this._personId || null,
"name": this._name(),
"contacts_visible": this._contactsVisible()
});
return aspect.save();
},
// ensure that we trigger the aspect:create event only after both hidden.bs.modal and and aspects sync happens
ensureEventsOrder: function() {
this._eventsCounter++;
if (this._eventsCounter > 1) {
app.events.trigger("aspect:create", this._newAspectId);
}
}
});
// @license-end
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
//= require ./aspects_dropdown_view
/**
* this view lets the user (de-)select aspect memberships in the context
* of another users profile or the contact page.
......@@ -9,7 +7,13 @@
* updates to the list of aspects are immediately propagated to the server, and
* the results are dislpayed as flash messages.
*/
app.views.AspectMembership = app.views.AspectsDropdown.extend({
app.views.AspectMembership = app.views.Base.extend({
templateName: "aspect_membership_dropdown",
className: "btn-group aspect_dropdown aspect_membership_dropdown",
subviews: {
".newAspectContainer": "aspectCreateView"
},
events: {
"click ul.aspect_membership.dropdown-menu > li.aspect_selector"
......@@ -18,70 +22,89 @@ app.views.AspectMembership = app.views.AspectsDropdown.extend({
: "_clickHandler"
},
initialize: function() {
initialize: function(opts) {
_.extend(this, opts);
this.list_item = null;
this.dropdown = null;
if (this.$(".newAspectContainer").length > 0) {
this.aspectCreateView = new app.views.AspectCreate({
el: this.$(".newAspectContainer"),
personId: this.$("ul.dropdown-menu").data("person_id")
});
this.aspectCreateView.render();
}
},
presenter: function() {
var aspectMembershipsLength = this.person.contact ? this.person.contact.aspectMemberships.length : 0;
return _.extend(this.defaultPresenter(), {
aspects: this.aspectsPresenter(),
dropdownMayCreateNewAspect: this.dropdownMayCreateNewAspect
}, aspectMembershipsLength === 0 ? {
extraButtonClass: "btn-default",
noAspectIsSelected: true
} : { // this.contact.aspectMemberships.length > 0
aspectMembershipsLength: aspectMembershipsLength,
allAspectsAreSelected: aspectMembershipsLength === app.aspects.length,
onlyOneAspectIsSelected: aspectMembershipsLength === 1,
firstMembershipName: this.person.contact.aspectMemberships.at(0).get("aspect").name,
extraButtonClass: "btn-success"
});
},
aspectsPresenter: function() {
return _.map(app.aspects.models, function(aspect) {
return _.extend(
this.person.contact ?
{membership: this.person.contact.aspectMemberships.findByAspectId(aspect.attributes.id)} : {},
aspect.attributes // id, name
);
}, this);
},
aspectCreateView: function() {
return new app.views.AspectCreate({
person: this.person
});
},
// decide what to do when clicked
// -> addMembership
// -> removeMembership
_clickHandler: function(evt) {
var promise = null;
this.list_item = $(evt.target).closest('li.aspect_selector');
this.dropdown = this.list_item.parent();
this.list_item.addClass('loading');
if( this.list_item.is('.selected') ) {
var membership_id = this.list_item.data('membership_id');
promise = this.removeMembership(membership_id);
if (this.list_item.is(".selected")) {
this.removeMembership(this.list_item.data("membership_id"));
} else {
var aspect_id = this.list_item.data('aspect_id');
var person_id = this.dropdown.data('person_id');
promise = this.addMembership(person_id, aspect_id);
this.addMembership(this.list_item.data("aspect_id"));
}
promise && promise.always(function() {
// trigger a global event
app.events.trigger('aspect_membership:update');
});
return false; // stop the event
},
// return the (short) name of the person associated with the current dropdown
_name: function() {
return this.dropdown.data('person-short-name');
return this.person.name || this.person.get("name");
},
_personId: function() {
return this.person.id;
},
// create a membership for the given person in the given aspect
addMembership: function(person_id, aspect_id) {
var aspect_membership = new app.models.AspectMembership({
'person_id': person_id,
'aspect_id': aspect_id
});
addMembership: function(aspectId) {
if (!this.person.contact) {
this.person.contact = new app.models.Contact();
}
aspect_membership.on('sync', this._successSaveCb, this);
aspect_membership.on('error', function() {
this.listenToOnce(this.person.contact.aspectMemberships, "sync", this._successSaveCb);
this.listenToOnce(this.person.contact.aspectMemberships, "error", function() {
this._displayError('aspect_dropdown.error');
}, this);
});
return aspect_membership.save();
return this.person.contact.aspectMemberships.create({"aspect_id": aspectId, "person_id": this._personId()});
},
_successSaveCb: function(aspectMembership) {
var aspectId = aspectMembership.get("aspect_id"),
membershipId = aspectMembership.get("id"),
li = this.dropdown.find("li[data-aspect_id='" + aspectId + "']"),
personId = li.closest("ul.dropdown-menu").data("person_id"),
startSharing = false;
// the user didn't have this person in any aspects before, congratulate them
......@@ -93,15 +116,11 @@ app.views.AspectMembership = app.views.AspectsDropdown.extend({
}
app.events.trigger("aspect_membership:create", {
membership: { aspectId: aspectId, personId: personId },
membership: {aspectId: aspectId, personId: this._personId()},
startSharing: startSharing
});
li.attr("data-membership_id", membershipId) // just to be sure...
.data("membership_id", membershipId);
this.updateSummary(li);
this._done();
this.render();
app.events.trigger("aspect_membership:update");
},
// show an error flash msg
......@@ -114,44 +133,35 @@ app.views.AspectMembership = app.views.AspectsDropdown.extend({
},
// remove the membership with the given id
removeMembership: function(membership_id) {
var aspect_membership = new app.models.AspectMembership({
'id': membership_id
removeMembership: function(membershipId) {
var membership = this.person.contact.aspectMemberships.get(membershipId);
this.listenToOnce(membership, "sync", this._successDestroyCb);
this.listenToOnce(membership, "error", function() {
this._displayError("aspect_dropdown.error_remove");
});
aspect_membership.on('sync', this._successDestroyCb, this);
aspect_membership.on('error', function() {
this._displayError('aspect_dropdown.error_remove');
}, this);
return aspect_membership.destroy();
return membership.destroy();
},
_successDestroyCb: function(aspectMembership) {
var membershipId = aspectMembership.get("id"),
li = this.dropdown.find("li[data-membership_id='" + membershipId + "']"),
aspectId = li.data("aspect_id"),
personId = li.closest("ul.dropdown-menu").data("person_id"),
aspectId = aspectMembership.get("aspect").id,
stopSharing = false;
li.removeAttr("data-membership_id")
.removeData("membership_id");
this.updateSummary(li);
this.render();
// we just removed the last aspect, inform the user with a flash message
// that he is no longer sharing with that person
if( this.dropdown.find("li.selected").length === 0 ) {
if (this.$el.find("li.selected").length === 0) {
var msg = Diaspora.I18n.t("aspect_dropdown.stopped_sharing_with", { "name": this._name() });
stopSharing = true;
app.flashMessages.success(msg);
}
app.events.trigger("aspect_membership:destroy", {
membership: { aspectId: aspectId, personId: personId },
membership: {aspectId: aspectId, personId: this._personId()},
stopSharing: stopSharing
});
this._done();
app.events.trigger("aspect_membership:update");
},
// cleanup tasks after aspect selection
......@@ -160,11 +170,5 @@ app.views.AspectMembership = app.views.AspectsDropdown.extend({
this.list_item.removeClass('loading');
}
},
// refresh the button text to reflect the current aspect selection status
updateSummary: function(target) {
this._toggleCheckbox(target);
this._updateButton("btn-success");
}
});
// @license-end
......@@ -3,6 +3,10 @@
app.views.Contact = app.views.Base.extend({
templateName: 'contact',
subviews: {
".aspect_membership_dropdown": "AspectMembershipView"
},
events: {
"click .contact_add-to-aspect" : "addContactToAspect",
"click .contact_remove-from-aspect" : "removeContactFromAspect"
......@@ -10,6 +14,12 @@ app.views.Contact = app.views.Base.extend({
tooltipSelector: '.contact_add-to-aspect, .contact_remove-from-aspect',
initialize: function() {
this.AspectMembershipView = new app.views.AspectMembership(
{person: _.extend(this.model.get("person"), {contact: this.model})}
);
},
presenter: function() {
return _.extend(this.defaultPresenter(), {
person_id : this.model.get('person_id'),
......@@ -18,21 +28,6 @@ app.views.Contact = app.views.Base.extend({
});
},
postRenderTemplate: function() {
var dropdownEl = this.$('.aspect_membership_dropdown.placeholder');
if( dropdownEl.length === 0 ) {
return;
}
// TODO render me client side!!!
var href = this.model.person.url() + '/aspect_membership_button?size=small';
$.get(href, function(resp) {
dropdownEl.html(resp);
new app.views.AspectMembership({el: $('.aspect_dropdown',dropdownEl)});
});
},
addContactToAspect: function(){
var self = this;
// do we create the first aspect membership for this person?
......
......@@ -3,13 +3,16 @@
app.views.ProfileHeader = app.views.Base.extend({
templateName: 'profile_header',
subviews: {
".aspect_membership_dropdown": "aspectMembershipView"
},
events: {
"click #mention_button": "showMentionModal",
"click #message_button": "showMessageModal"
},
initialize: function(opts) {
app.events.on('aspect:create', this.postRenderTemplate, this);
this.photos = _.has(opts, 'photos') ? opts.photos : null;
this.contacts = _.has(opts, 'contacts') ? opts.contacts : null;
},
......@@ -29,6 +32,10 @@ app.views.ProfileHeader = app.views.Base.extend({
});
},
aspectMembershipView: function() {
return new app.views.AspectMembership({person: this.model, dropdownMayCreateNewAspect: true});
},
_hasTags: function() {
return (this.model.get('profile')['tags'].length > 0);
},
......@@ -52,21 +59,6 @@ app.views.ProfileHeader = app.views.Base.extend({
showMessageModal: function(){
app.helpers.showModal("#conversationModal");
},
postRenderTemplate: function() {
var dropdownEl = this.$('.aspect_membership_dropdown.placeholder');
if( dropdownEl.length === 0 ) {
return;
}
// TODO render me client side!!!
var href = this.model.url() + '/aspect_membership_button?create=true&size=normal';
$.get(href, function(resp) {
dropdownEl.html(resp);
new app.views.AspectMembership({el: $('.aspect_dropdown',dropdownEl)});
});
}
});
// @license-end
......@@ -120,3 +120,21 @@ $default-border-radius: 3px;
}
}
}
@mixin selectable-list() {
.glyphicon-ok,
.glyphicon-refresh {
display: none;
padding-right: 5px;
}
&.selected {
.glyphicon-ok { display: inline-block;}
.glyphicon-refresh { display: none;}
}
&.loading {
.glyphicon-refresh { display: inline-block;}
.glyphicon-ok { display: none;}
}
}
.aspect_dropdown {
li {
@include selectable-list;
.status_indicator {
width: 19px;
height: 14px;
display: inline-block;
}
.glyphicon-ok, .icon-refresh {
padding-right: 5px;
display: none;
}
&.selected {
.glyphicon-ok { display: inline-block;}
.icon-refresh { display: none;}
}
&.loading {
.icon-refresh { display: inline-block;}
.glyphicon-ok { display: none;}
}
a {
.text {
color: #333333;
......
......@@ -11,7 +11,7 @@
<div class="modal-body">
<form>
<fieldset>
{{#if addPersonId}}
{{#if personId}}
<input id="aspect_person_id" type="hidden" value="{{ personId }}">
{{/if}}
......
<button class="btn dropdown-toggle {{extraButtonClass}}" data-toggle="dropdown" tabindex="0">
<span class="text">
{{#if allAspectsAreSelected }}
{{ t "aspect_dropdown.all_aspects" }}
{{else if onlyOneAspectIsSelected}}
{{ firstMembershipName }}
{{else if noAspectIsSelected}}
{{ t "aspect_dropdown.add_to_aspect"}}
{{else}}
{{ t "aspect_dropdown.toggle" count=aspectMembershipsLength }}
{{/if}}
</span>
<span class="caret" />
</button>
<ul class="dropdown-menu aspect_membership pull-right" unselectable="on">
{{#each aspects}}
<li
{{#if membership}}
class="aspect_selector selected"
{{else}}
class="aspect_selector"
{{/if}}
data-aspect_id="{{id}}"
{{#if membership}}
data-membership_id="{{membership.id}}"
{{/if}}
>
<a>
<span class="status_indicator">
<i class="glyphicon glyphicon-ok" />
<i class="glyphicon glyphicon-refresh" />
</span>
<span class="text">
{{name}}
</span>
</a>
</li>
{{/each}}
{{#if dropdownMayCreateNewAspect}}