diff --git a/app/assets/javascripts/app/app.js b/app/assets/javascripts/app/app.js index f1762da84..3e7e3748b 100644 --- a/app/assets/javascripts/app/app.js +++ b/app/assets/javascripts/app/app.js @@ -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")}); diff --git a/app/assets/javascripts/app/collections/aspect_memberships.js b/app/assets/javascripts/app/collections/aspect_memberships.js index dc3c0410b..5c53b6ead 100644 --- a/app/assets/javascripts/app/collections/aspect_memberships.js +++ b/app/assets/javascripts/app/collections/aspect_memberships.js @@ -1,6 +1,10 @@ // @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 diff --git a/app/assets/javascripts/app/collections/aspects.js b/app/assets/javascripts/app/collections/aspects.js new file mode 100644 index 000000000..4cecea647 --- /dev/null +++ b/app/assets/javascripts/app/collections/aspects.js @@ -0,0 +1,7 @@ +// @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 diff --git a/app/assets/javascripts/app/models/aspect_membership.js b/app/assets/javascripts/app/models/aspect_membership.js index a4bd99b97..bfae08b50 100644 --- a/app/assets/javascripts/app/models/aspect_membership.js +++ b/app/assets/javascripts/app/models/aspect_membership.js @@ -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 diff --git a/app/assets/javascripts/app/models/contact.js b/app/assets/javascripts/app/models/contact.js index 22f26804b..2c72ae41d 100644 --- a/app/assets/javascripts/app/models/contact.js +++ b/app/assets/javascripts/app/models/contact.js @@ -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 diff --git a/app/assets/javascripts/app/models/person.js b/app/assets/javascripts/app/models/person.js index 8c9a7241a..4d70fada4 100644 --- a/app/assets/javascripts/app/models/person.js +++ b/app/assets/javascripts/app/models/person.js @@ -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() { diff --git a/app/assets/javascripts/app/pages/getting_started.js b/app/assets/javascripts/app/pages/getting_started.js new file mode 100644 index 000000000..4cf3044c4 --- /dev/null +++ b/app/assets/javascripts/app/pages/getting_started.js @@ -0,0 +1,20 @@ +// @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 diff --git a/app/assets/javascripts/app/router.js b/app/assets/javascripts/app/router.js index f5c42f13b..16008b07e 100644 --- a/app/assets/javascripts/app/router.js +++ b/app/assets/javascripts/app/router.js @@ -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(); diff --git a/app/assets/javascripts/app/views/aspect_create_view.js b/app/assets/javascripts/app/views/aspect_create_view.js index ad40616a6..8c7d030f9 100644 --- a/app/assets/javascripts/app/views/aspect_create_view.js +++ b/app/assets/javascripts/app/views/aspect_create_view.js @@ -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(); }); + }, - var self = this; - aspect.on("sync", function(response) { - var aspectId = response.get("id"), - aspectName = response.get("name"); + createAspect: function() { + this._eventsCounter = 0; - self.modal.modal("hide"); - app.events.trigger("aspect:create", aspectId); + this.$(".modal").modal("hide"); + + 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"); }); - return aspect.save(); + + app.aspects.create({ + "person_id": this._personId || null, + "name": this._name(), + "contacts_visible": this._contactsVisible() + }); + }, + + // 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 diff --git a/app/assets/javascripts/app/views/aspect_membership_view.js b/app/assets/javascripts/app/views/aspect_membership_view.js index 7b9516534..30529837e 100644 --- a/app/assets/javascripts/app/views/aspect_membership_view.js +++ b/app/assets/javascripts/app/views/aspect_membership_view.js @@ -1,7 +1,5 @@ // @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(); + } + + this.listenToOnce(this.person.contact.aspectMemberships, "sync", this._successSaveCb); + this.listenToOnce(this.person.contact.aspectMemberships, "error", function() { + this._displayError('aspect_dropdown.error'); }); - aspect_membership.on('sync', this._successSaveCb, this); - aspect_membership.on('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 diff --git a/app/assets/javascripts/app/views/contact_view.js b/app/assets/javascripts/app/views/contact_view.js index d48ce6499..1a0a676d6 100644 --- a/app/assets/javascripts/app/views/contact_view.js +++ b/app/assets/javascripts/app/views/contact_view.js @@ -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? diff --git a/app/assets/javascripts/app/views/profile_header_view.js b/app/assets/javascripts/app/views/profile_header_view.js index 37595d580..3b63be3ae 100644 --- a/app/assets/javascripts/app/views/profile_header_view.js +++ b/app/assets/javascripts/app/views/profile_header_view.js @@ -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 diff --git a/app/assets/stylesheets/_mixins.scss b/app/assets/stylesheets/_mixins.scss index 0b01ca6bd..2b4c02f90 100644 --- a/app/assets/stylesheets/_mixins.scss +++ b/app/assets/stylesheets/_mixins.scss @@ -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;} + } +} diff --git a/app/assets/stylesheets/aspects.scss b/app/assets/stylesheets/aspects.scss index 1295f6cb5..115e2bcfa 100644 --- a/app/assets/stylesheets/aspects.scss +++ b/app/assets/stylesheets/aspects.scss @@ -1,23 +1,14 @@ .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; diff --git a/app/assets/templates/aspect_create_modal_tpl.jst.hbs b/app/assets/templates/aspect_create_modal_tpl.jst.hbs index ad131f838..00cd08d22 100644 --- a/app/assets/templates/aspect_create_modal_tpl.jst.hbs +++ b/app/assets/templates/aspect_create_modal_tpl.jst.hbs @@ -11,7 +11,7 @@