Client-side rendering of aspect dropdown for hovercards
This commit is contained in:
parent
923fb8a763
commit
82ac611396
11 changed files with 133 additions and 96 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
spec/presenters/profile_presenter_spec.rb
Normal file
13
spec/presenters/profile_presenter_spec.rb
Normal 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
|
||||
Loading…
Reference in a new issue