Client-side rendering of aspect dropdown for hovercards

This commit is contained in:
cmrd Senya 2016-07-16 13:37:46 +03:00
parent 923fb8a763
commit 82ac611396
No known key found for this signature in database
GPG key ID: 5FCC5BA680E67BFE
11 changed files with 133 additions and 96 deletions

View file

@ -28,6 +28,7 @@ app.Router = Backbone.Router.extend({
"tags/:name": "followed_tags",
"people/:id/photos": "photos",
"people/:id/contacts": "profile",
"people": "_loadAspects",
"people/:id": "profile",
"u/:name": "profile"
@ -106,6 +107,7 @@ app.Router = Backbone.Router.extend({
},
stream : function() {
this._loadAspects();
app.stream = new app.models.Stream();
app.stream.fetch();
this._initializeStreamView();

View file

@ -4,13 +4,15 @@ app.views.Hovercard = app.views.Base.extend({
templateName: 'hovercard',
id: 'hovercard_container',
subviews: {
"#hovercard_dropdown_container": "aspectMembershipDropdown"
},
events: {
'mouseleave': '_mouseleaveHandler'
},
initialize: function() {
this.render();
$(document)
.on('mouseenter', '.hovercardable', _.bind(this._mouseenterHandler, this))
.on('mouseleave', '.hovercardable', _.bind(this._mouseleaveHandler, this));
@ -18,18 +20,18 @@ app.views.Hovercard = app.views.Base.extend({
this.showMe = false;
this.parent = null; // current 'hovercardable' element that caused HC to appear
// cache some element references
this.avatar = this.$('.avatar');
this.avatarLink = this.$("a.person_avatar");
this.dropdown_container = this.$('#hovercard_dropdown_container');
this.hashtags = this.$('.hashtags');
this.person_link = this.$('a.person');
this.person_handle = this.$('div.handle');
this.active = true;
},
postRenderTemplate: function() {
this.$el.appendTo($('body'));
this.$el.appendTo($("body"));
// cache some element references
this.avatar = this.$(".avatar");
this.avatarLink = this.$("a.person_avatar");
this.hashtags = this.$(".hashtags");
this.personLink = this.$("a.person");
this.personID = this.$("div.handle");
},
deactivate: function() {
@ -73,7 +75,6 @@ app.views.Hovercard = app.views.Base.extend({
this.$el.hide();
}
this.dropdown_container.unbind().empty();
return false;
},
@ -102,6 +103,12 @@ app.views.Hovercard = app.views.Base.extend({
throw new Error("received data is not a person object");
}
if (app.currentUser.authenticated()) {
self.aspectMembershipDropdown = new app.views.AspectMembership({person: new app.models.Person(person)});
}
self.render();
self._populateHovercardWith(person);
if( !self.showMe ) {
// mouse has left element
@ -112,29 +119,20 @@ app.views.Hovercard = app.views.Base.extend({
},
_populateHovercardWith: function(person) {
var self = this;
this.avatarLink.attr("href", this.href());
this.personLink.attr("href", this.href());
this.personLink.text(person.name);
this.personID.text(person.diaspora_id);
this.avatar.attr('src', person.avatar);
this.avatarLink.attr("href", person.url);
this.person_link.attr('href', person.url);
this.person_link.text(person.name);
this.person_handle.text(person.handle);
if (person.profile) {
this.avatar.attr("src", person.profile.avatar);
// set hashtags
this.hashtags.empty();
this.hashtags.html( $(_.map(person.tags, function(tag){
return $('<a/>',{href: "/tags/"+tag.substring(1)}).text(tag)[0] ;
})) );
if(!app.currentUser.authenticated()){ return; }
// set aspect dropdown
// TODO render me client side!!!
var href = this.href();
href += "/aspect_membership_button";
$.ajax(href, {preventGlobalErrorHandling: true}).done(function(response){
self.dropdown_container.html(response);
});
new app.views.AspectMembership({el: self.dropdown_container});
// set hashtags
this.hashtags.empty();
this.hashtags.html($(_.map(person.profile.tags, function(tag) {
return $("<a/>", {href: "/tags/" + tag.substring(1)}).text(tag)[0];
})));
}
},
_positionHovercard: function() {

View file

@ -108,7 +108,7 @@ class PeopleController < ApplicationController
end
format.json do
render :json => HovercardPresenter.new(@person)
render json: PersonPresenter.new(@person, current_user).hovercard
end
end
end

View file

@ -4,9 +4,21 @@ class AvatarPresenter < BasePresenter
def base_hash
{
small: image_url(:thumb_small) || DEFAULT_IMAGE,
medium: image_url(:thumb_medium) || DEFAULT_IMAGE,
large: image_url || DEFAULT_IMAGE
small: small,
medium: medium,
large: large
}
end
def small
image_url(:thumb_small) || DEFAULT_IMAGE
end
def medium
image_url(:thumb_medium) || DEFAULT_IMAGE
end
def large
image_url || DEFAULT_IMAGE
end
end

View file

@ -1,39 +0,0 @@
class HovercardPresenter
attr_accessor :person
# initialize the presenter with the given Person object
def initialize(person)
raise ArgumentError, "the given object is not a Person" unless person.class == Person
self.person = person
end
# returns the json representation of the Person object for use with the
# hovercard UI
def to_json(options={})
{ :id => person.id,
:avatar => avatar('medium'),
:url => profile_url,
:name => person.name,
:handle => person.diaspora_handle,
:tags => person.tags.map { |t| "#"+t.name }
}.to_json(options)
end
# get the image url of the profile avatar for the given size
# possible sizes: 'small', 'medium', 'large'
def avatar(size="medium")
if !["small", "medium", "large"].include?(size)
raise ArgumentError, "the given parameter is not a valid size"
end
person.image_url("thumb_#{size}".to_sym)
end
# return the (relative) url to the user profile page.
# uses the 'person_path' url helper from the rails routes
def profile_url
Rails.application.routes.url_helpers.person_path(person)
end
end

View file

@ -9,10 +9,9 @@ class PersonPresenter < BasePresenter
end
def full_hash
base_hash.merge(
base_hash_with_contact.merge(
relationship: relationship,
block: is_blocked? ? BlockPresenter.new(current_user_person_block).base_hash : false,
contact: (!own_profile? && has_contact?) ? contact_hash : false,
is_own_profile: own_profile?,
show_profile_info: public_details? || own_profile? || person_is_following_current_user
)
@ -22,6 +21,10 @@ class PersonPresenter < BasePresenter
full_hash_with_profile
end
def hovercard
base_hash_with_contact.merge(profile: ProfilePresenter.new(profile).for_hovercard)
end
protected
def own_profile?
@ -46,6 +49,12 @@ class PersonPresenter < BasePresenter
contact && contact.sharing?
end
def base_hash_with_contact
base_hash.merge(
contact: (!own_profile? && has_contact?) ? contact_hash : false
)
end
def full_hash_with_profile
attrs = full_hash

View file

@ -15,6 +15,13 @@ class ProfilePresenter < BasePresenter
)
end
def for_hovercard
{
avatar: AvatarPresenter.new(@presentable).medium,
tags: tags.pluck(:name)
}
end
def private_hash
public_hash.merge(
bio: bio_message.plain_text_for_json,

View file

@ -453,7 +453,14 @@ describe PeopleController, :type => :controller do
it 'returns json with profile stuff' do
get :hovercard, :person_id => @hover_test.guid, :format => 'json'
expect(JSON.parse( response.body )['handle']).to eq(@hover_test.diaspora_handle)
expect(JSON.parse(response.body)["diaspora_id"]).to eq(@hover_test.diaspora_handle)
end
it "returns contact when sharing" do
alice.share_with(@hover_test, alice.aspects.first)
expect(@controller).to receive(:current_user).at_least(:once).and_return(alice)
get :hovercard, person_id: @hover_test.guid, format: "json"
expect(JSON.parse(response.body)["contact"]).not_to be_falsy
end
end

View file

@ -1,16 +1,24 @@
describe("app.views.Hovercard", function() {
afterEach(function() {
$("body #hovercard_container").remove();
});
context("user not signed in", function() {
beforeEach(function() {
logout();
this.view = new app.views.Hovercard();
});
describe("_populateHovercardWith", function() {
it("doesn't fetch the aspect dropdown", function() {
spyOn(jQuery, "ajax").and.callThrough();
describe("_populateHovercard", function() {
it("doesn't create the aspect dropdown", function() {
this.view.parent = spec.content();
this.view._populateHovercardWith({});
expect(jQuery.ajax).not.toHaveBeenCalled();
this.view._populateHovercard();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
responseText: JSON.stringify({id: 1337})
});
expect(this.view.aspectMembershipDropdown).toEqual(undefined);
});
});
});
@ -40,17 +48,15 @@ describe("app.views.Hovercard", function() {
this.view._populateHovercard();
expect(jQuery.ajax).toHaveBeenCalledWith("undefined/hovercard.json", {preventGlobalErrorHandling: true});
});
});
describe("_populateHovercardWith", function() {
it("prevents global error handling for the ajax call", function() {
spyOn(jQuery, "ajax").and.callThrough();
it("creates the aspect dropdown", function() {
this.view.parent = spec.content();
this.view._populateHovercardWith({});
expect(jQuery.ajax).toHaveBeenCalledWith(
"undefined/aspect_membership_button",
{preventGlobalErrorHandling: true}
);
this.view._populateHovercard();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
responseText: JSON.stringify({id: 1337})
});
expect(this.view.aspectMembershipDropdown).not.toEqual(undefined);
});
});
});

View file

@ -4,10 +4,18 @@ describe PersonPresenter do
let(:profile_user) { FactoryGirl.create(:user_with_aspect) }
let(:person) { profile_user.person }
let(:mutual_contact) { double(id: 1, mutual?: true, sharing?: true, receiving?: true) }
let(:receiving_contact) { double(id: 1, mutual?: false, sharing?: false, receiving?: true) }
let(:sharing_contact) { double(id: 1, mutual?: false, sharing?: true, receiving?: false) }
let(:non_contact) { double(id: 1, mutual?: false, sharing?: false, receiving?: false) }
let(:mutual_contact) {
FactoryGirl.create(:contact, user: current_user, person: person, sharing: true, receiving: true)
}
let(:receiving_contact) {
FactoryGirl.create(:contact, user: current_user, person: person, sharing: false, receiving: true)
}
let(:sharing_contact) {
FactoryGirl.create(:contact, user: current_user, person: person, sharing: true, receiving: false)
}
let(:non_contact) {
FactoryGirl.create(:contact, user: current_user, person: person, sharing: false, receiving: false)
}
describe "#as_json" do
context "with no current_user" do
@ -113,4 +121,18 @@ describe PersonPresenter do
end
end
end
describe "#hovercard" do
let(:current_user) { FactoryGirl.create(:user) }
let(:presenter) { PersonPresenter.new(person, current_user) }
it "contains data required for hovercard" do
mutual_contact
expect(presenter.hovercard).to have_key(:profile)
expect(presenter.hovercard[:profile]).to have_key(:avatar)
expect(presenter.hovercard[:profile]).to have_key(:tags)
expect(presenter.hovercard).to have_key(:contact)
expect(presenter.hovercard[:contact]).to have_key(:aspect_memberships)
end
end
end

View file

@ -0,0 +1,13 @@
require "spec_helper"
describe ProfilePresenter do
let(:profile) { FactoryGirl.create(:profile_with_image_url, person: alice.person) }
describe "#for_hovercard" do
it "contains tags and avatar" do
hash = ProfilePresenter.new(profile).for_hovercard
expect(hash[:avatar]).to eq(profile.image_url_medium)
expect(hash[:tags]).to match_array(%w(one two))
end
end
end