Merge pull request #4480 from Raven24/publisher_backbone

Publisher - now with even more Backbone
This commit is contained in:
Jonne Haß 2013-09-04 16:51:07 +02:00
commit 72e421b38f
13 changed files with 569 additions and 409 deletions

View file

@ -5,6 +5,7 @@
* Build a color palette to uniform color usage [#4437](https://github.com/diaspora/diaspora/pull/4437) [#4469](https://github.com/diaspora/diaspora/pull/4469) [#4479](https://github.com/diaspora/diaspora/pull/4479)
* Rename bitcoin_wallet_id setting to bitcoin_address [#4485](https://github.com/diaspora/diaspora/pull/4485)
* Batch insert posts into stream collection for a small speedup [#4341](https://github.com/diaspora/diaspora/pull/4351)
* Ported fileuploader to Backbone and refactored publisher views [#4480](https://github.com/diaspora/diaspora/pull/4480)
## Bug fixes
* Highlight down arrow at the user menu on hover [#4441](https://github.com/diaspora/diaspora/pull/4441)

View file

@ -99,7 +99,7 @@ app.Router = Backbone.Router.extend({
app.page = new app.views.Stream({model : app.stream});
app.publisher = app.publisher || new app.views.Publisher({collection : app.stream.items});
app.publisher.updateAspectsSelector(ids);
app.publisher.setSelectedAspects(ids);
var streamFacesView = new app.views.StreamFaces({collection : app.stream.items});

View file

@ -0,0 +1,87 @@
/* Copyright (c) 2010-2012, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
// Aspects view for the publisher.
// Provides the ability to specify the visibility of posted content as public
// or limited to selected aspects
app.views.PublisherAspectSelector = Backbone.View.extend({
events: {
"click .dropdown_list > li": "toggleAspect"
},
// event handler for aspect selection
toggleAspect: function(evt) {
var el = $(evt.target);
var btn = el.parent('.dropdown').find('.button');
// visually toggle the aspect selection
if( el.is('.radio') ) {
AspectsDropdown.toggleRadio(el);
} else {
AspectsDropdown.toggleCheckbox(el);
}
// update the selection summary
this._updateAspectsNumber(el);
this._updateSelectedAspectIds();
},
// select a (list of) aspects in the dropdown selector by the given list of ids
updateAspectsSelector: function(ids){
var el = this.$("ul.dropdown_list");
this.$('.dropdown_list > li').each(function(){
var el = $(this);
var aspectId = el.data('aspect_id');
if (_.contains(ids, aspectId)) {
el.addClass('selected');
}
else {
el.removeClass('selected');
}
});
this._updateAspectsNumber(el);
this._updateSelectedAspectIds();
},
// take care of the form fields that will indicate the selected aspects
_updateSelectedAspectIds: function() {
var self = this;
// remove previous selection
this.options.form.find('input[name="aspect_ids[]"]').remove();
// create fields for current selection
this.$('.dropdown_list li.selected').each(function() {
var el = $(this);
var aspectId = el.data('aspect_id');
self._addHiddenAspectInput(aspectId);
// close the dropdown when a radio item was selected
if( el.is('.radio') ) {
el.closest('.dropdown').removeClass('active');
}
});
},
_updateAspectsNumber: function(el){
AspectsDropdown.updateNumber(
el.closest(".dropdown_list"),
null,
el.parent().find('li.selected').length,
''
);
},
_addHiddenAspectInput: function(id) {
var uid = _.uniqueId('aspect_ids_');
this.options.form.append(
'<input id="'+uid+'" name="aspect_ids[]" type="hidden" value="'+id+'">'
);
}
});

View file

@ -1,83 +0,0 @@
/* Copyright (c) 2010-2012, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
(function(){
// mixin-object, used in conjunction with the publisher to provide the
// functionality for selecting aspects
app.views.PublisherAspectsSelector = {
// event handler for aspect selection
toggleAspect: function(evt) {
var el = $(evt.target);
var btn = el.parent('.dropdown').find('.button');
// visually toggle the aspect selection
if( el.is('.radio') ) {
AspectsDropdown.toggleRadio(el);
} else {
AspectsDropdown.toggleCheckbox(el);
}
// update the selection summary
this._updateAspectsNumber(el);
this._updateSelectedAspectIds();
},
updateAspectsSelector: function(ids){
var el = this.$("ul.dropdown_list");
this.$('.dropdown_list > li').each(function(){
var el = $(this);
var aspectId = el.data('aspect_id');
if (_.contains(ids, aspectId)) {
el.addClass('selected');
}
else {
el.removeClass('selected');
}
});
this._updateAspectsNumber(el);
this._updateSelectedAspectIds();
},
// take care of the form fields that will indicate the selected aspects
_updateSelectedAspectIds: function() {
var self = this;
// remove previous selection
this.$('input[name="aspect_ids[]"]').remove();
// create fields for current selection
this.$('.dropdown .dropdown_list li.selected').each(function() {
var el = $(this);
var aspectId = el.data('aspect_id');
self._addHiddenAspectInput(aspectId);
// close the dropdown when a radio item was selected
if( el.is('.radio') ) {
el.closest('.dropdown').removeClass('active');
}
});
},
_updateAspectsNumber: function(el){
AspectsDropdown.updateNumber(
el.closest(".dropdown_list"),
null,
el.parent().find('li.selected').length,
''
);
},
_addHiddenAspectInput: function(id) {
var uid = _.uniqueId('aspect_ids_');
this.$('.content_creation form').append(
'<input id="'+uid+'" name="aspect_ids[]" type="hidden" value="'+id+'">'
);
}
};
})();

View file

@ -1,65 +0,0 @@
/* Copyright (c) 2010-2012, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
(function(){
// mixin-object, used in conjunction with the publisher to provide the
// functionality for displaying 'getting-started' information
app.views.PublisherGettingStarted = {
// initiate all the popover message boxes
triggerGettingStarted: function() {
this._addPopover(this.el_input, {
trigger: 'manual',
offset: 30,
id: 'first_message_explain',
placement: 'right',
html: true
}, 600);
this._addPopover(this.$('.dropdown'), {
trigger: 'manual',
offset: 10,
id: 'message_visibility_explain',
placement: 'bottom',
html: true
}, 1000);
this._addPopover($('#gs-shim'), {
trigger: 'manual',
offset: -5,
id: 'stream_explain',
placement: 'left',
html: true
}, 1400);
// hide some popovers when a post is created
this.$('.button.creation').click(function() {
this.$('.dropdown').popover('hide');
this.el_input.popover('hide');
});
},
_addPopover: function(el, opts, timeout) {
el.popover(opts);
el.click(function() {
el.popover('hide');
});
// show the popover after the given timeout
setTimeout(function() {
el.popover('show');
// disable 'getting started' when the last popover is closed
var popup = el.data('popover').$tip[0];
var close = $(popup).find('.close');
close.click(function() {
if( $('.popover').length==1 ) {
$.get('/getting_started_completed');
}
el.popover('hide');
});
}, timeout);
}
};
})();

View file

@ -0,0 +1,65 @@
/* Copyright (c) 2010-2012, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
// Getting started view for the publisher.
// Provides "getting started" popups around the elements of the publisher
// for describing their use to new users.
app.views.PublisherGettingStarted = Backbone.View.extend({
// initiate all the popover message boxes
show: function() {
this._addPopover(this.options.el_first_msg, {
trigger: 'manual',
offset: 30,
id: 'first_message_explain',
placement: 'right',
html: true
}, 600);
this._addPopover(this.options.el_visibility, {
trigger: 'manual',
offset: 10,
id: 'message_visibility_explain',
placement: 'bottom',
html: true
}, 1000);
this._addPopover(this.options.el_stream, {
trigger: 'manual',
offset: -5,
id: 'stream_explain',
placement: 'left',
html: true
}, 1400);
// hide some popovers when a post is created
this.$('.button.creation').click(function() {
this.options.el_visibility.popover('hide');
this.options.el_first_msg.popover('hide');
});
},
_addPopover: function(el, opts, timeout) {
el.popover(opts);
el.click(function() {
el.popover('hide');
});
// show the popover after the given timeout
setTimeout(function() {
el.popover('show');
// disable 'getting started' when the last popover is closed
var popup = el.data('popover').$tip[0];
var close = $(popup).find('.close');
close.click(function() {
if( $('.popover').length==1 ) {
$.get('/getting_started_completed');
}
el.popover('hide');
return false;
});
}, timeout);
}
});

View file

@ -1,51 +0,0 @@
/* Copyright (c) 2010-2012, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
(function(){
// mixin-object, used in conjunction with the publisher to provide the
// functionality for selecting services for cross-posting
app.views.PublisherServices = {
// visually toggle the icon and kick-off all other actions for cross-posting
toggleService: function(evt) {
var el = $(evt.target);
var provider = el.attr('id');
el.toggleClass("dim");
this._createCounter();
this._toggleServiceField(provider);
},
// keep track of character count
_createCounter: function() {
// remove obsolete counter
this.$('.counter').remove();
// create new counter
var min = 40000;
var a = this.$('.service_icon:not(.dim)');
if(a.length > 0){
$.each(a, function(index, value){
var num = parseInt($(value).attr('maxchar'));
if (min > num) { min = num; }
});
this.el_input.charCount({allowed: min, warning: min/10 });
}
},
// add or remove the input containing the selected service
_toggleServiceField: function(provider) {
var hidden_field = this.$('input[name="services[]"][value="'+provider+'"]');
if(hidden_field.length > 0){
hidden_field.remove();
} else {
var uid = _.uniqueId('services_');
this.$(".content_creation form").append(
'<input id="'+uid+'" name="services[]" type="hidden" value="'+provider+'">');
}
}
};
})();

View file

@ -0,0 +1,60 @@
/* Copyright (c) 2010-2012, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
// Services view for the publisher.
// Provides the ability for selecting services for cross-posting
app.views.PublisherServices = Backbone.View.extend({
events: {
'click .service_icon': 'toggleService'
},
tooltipSelector: '.service_icon',
initialize: function() {
// init tooltip plugin
this.$(this.tooltipSelector).tooltip();
},
// visually toggle the icon and handle all other actions for cross-posting
toggleService: function(evt) {
var el = $(evt.target);
var provider = el.attr('id');
el.toggleClass("dim");
this._createCounter();
this._toggleServiceField(provider);
},
// keep track of character count
_createCounter: function() {
// remove any obsolete counters
this.options.input.siblings('.counter').remove();
// create new counter
var min = 40000;
var a = this.$('.service_icon:not(.dim)');
if(a.length > 0){
$.each(a, function(index, value){
var num = parseInt($(value).attr('maxchar'));
if (min > num) { min = num; }
});
this.options.input.charCount({allowed: min, warning: min/10 });
}
},
// add or remove the input containing the selected service
_toggleServiceField: function(provider) {
var hidden_field = this.options.form.find('input[name="services[]"][value="'+provider+'"]');
if(hidden_field.length > 0){
hidden_field.remove();
} else {
var uid = _.uniqueId('services_');
this.options.form.append(
'<input id="'+uid+'" name="services[]" type="hidden" value="'+provider+'">');
}
}
});

View file

@ -0,0 +1,124 @@
// Uploader view for the publisher.
// Initializes the file uploader plugin and handles callbacks for the upload
// progress. Attaches previews of finished uploads to the publisher.
app.views.PublisherUploader = Backbone.View.extend({
allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'tif', 'tiff'],
sizeLimit: 4194304, // bytes
initialize: function() {
this.uploader = new qq.FileUploaderBasic({
element: this.el,
button: this.el,
//debug: true,
action: '/photos',
params: { photo: { pending: true }},
allowedExtensions: this.allowedExtensions,
sizeLimit: this.sizeLimit,
messages: {
typeError: Diaspora.I18n.t('photo_uploader.invalid_ext'),
sizeError: Diaspora.I18n.t('photo_uploader.size_error'),
emptyError: Diaspora.I18n.t('photo_uploader.empty')
},
onProgress: _.bind(this.progressHandler, this),
onSubmit: _.bind(this.submitHandler, this),
onComplete: _.bind(this.uploadCompleteHandler, this)
});
this.el_info = $('<div id="fileInfo" />');
this.options.publisher.el_wrapper.before(this.el_info);
this.options.publisher.el_photozone.on('click', '.x', _.bind(this._removePhoto, this));
},
progressHandler: function(id, fileName, loaded, total) {
var progress = Math.round(loaded / total * 100);
this.el_info.text(fileName + ' ' + progress + '%').fadeTo(200, 1);
},
submitHandler: function(id, fileName) {
this.$el.addClass('loading');
this._addPhotoPlaceholder();
},
// add photo placeholders to the publisher to indicate an upload in progress
_addPhotoPlaceholder: function() {
var publisher = this.options.publisher;
publisher.setButtonsEnabled(false);
publisher.el_wrapper.addClass('with_attachments');
publisher.el_photozone.append(
'<li class="publisher_photo loading" style="position:relative;">' +
' <img src="'+Handlebars.helpers.imageUrl('ajax-loader2.gif')+'" alt="" />'+
'</li>'
);
},
uploadCompleteHandler: function(id, fileName, response) {
this.el_info.text(Diaspora.I18n.t('photo_uploader.completed', {file: fileName})).fadeTo(2000, 0);
var id = response.data.photo.id,
url = response.data.photo.unprocessed_image.url;
this._addFinishedPhoto(id, url);
},
// replace the first photo placeholder with the finished uploaded image and
// add the id to the publishers form
_addFinishedPhoto: function(id, url) {
var publisher = this.options.publisher;
// add form input element
publisher.$('.content_creation form').append(
'<input type="hidden", value="'+id+'" name="photos[]" />'
);
// replace placeholder
var placeholder = publisher.el_photozone.find('li.loading').first();
placeholder
.removeClass('loading')
.append(
'<div class="x">X</div>'+
'<div class="circle"></div>'
)
.find('img').attr({'src': url, 'data-id': id});
// no more placeholders? enable buttons
if( publisher.el_photozone.find('li.loading').length == 0 ) {
this.$el.removeClass('loading');
publisher.setButtonsEnabled(true);
}
},
// remove an already uploaded photo
_removePhoto: function(evt) {
var self = this;
var photo = $(evt.target).parents('.publisher_photo')
var img = photo.find('img');
photo.addClass('dim');
$.ajax({
url: '/photos/'+img.attr('data-id'),
dataType: 'json',
type: 'DELETE',
success: function() {
$.when(photo.fadeOut(400)).then(function(){
photo.remove();
if( self.options.publisher.$('.publisher_photo').length == 0 ) {
// no more photos left...
self.options.publisher.el_wrapper.removeClass('with_attachments');
}
});
}
});
return false;
}
});

View file

@ -3,15 +3,13 @@
* the COPYRIGHT file.
*/
//= require ./publisher/services
//= require ./publisher/aspects_selector
//= require ./publisher/getting_started
//= require ./publisher/services_view
//= require ./publisher/aspect_selector_view
//= require ./publisher/getting_started_view
//= require ./publisher/uploader_view
//= require jquery.textchange
app.views.Publisher = Backbone.View.extend(_.extend(
app.views.PublisherServices,
app.views.PublisherAspectsSelector,
app.views.PublisherGettingStarted, {
app.views.Publisher = Backbone.View.extend({
el : "#publisher",
@ -21,16 +19,12 @@ app.views.Publisher = Backbone.View.extend(_.extend(
"click #hide_publisher" : "clear",
"submit form" : "createStatusMessage",
"click .post_preview_button" : "createPostPreview",
"click .service_icon": "toggleService",
"textchange #status_message_fake_text": "handleTextchange",
"click .dropdown .dropdown_list li": "toggleAspect",
"click #locator" : "showLocation",
"click #hide_location" : "destroyLocation",
"keypress #location_address" : "avoidEnter"
},
tooltipSelector: ".service_icon",
initialize : function(){
// init shortcut references to the various elements
this.el_input = this.$('#status_message_fake_text');
@ -46,9 +40,6 @@ app.views.Publisher = Backbone.View.extend(_.extend(
// init autoresize plugin
this.el_input.autoResize({ 'extraSpace' : 10, 'maxHeight' : Infinity });
// init tooltip plugin
this.$(this.tooltipSelector).tooltip();
// sync textarea content
if( this.el_hiddenInput.val() == "" ) {
this.el_hiddenInput.val( this.el_input.val() );
@ -73,10 +64,48 @@ app.views.Publisher = Backbone.View.extend(_.extend(
}
});
this.initSubviews();
return this;
},
initSubviews: function() {
var form = this.$('.content_creation form');
this.view_services = new app.views.PublisherServices({
el: this.$('#publisher_service_icons'),
input: this.el_input,
form: form
});
this.view_aspect_selector = new app.views.PublisherAspectSelector({
el: this.$('.public_toggle > .dropdown'),
form: form
});
this.view_getting_started = new app.views.PublisherGettingStarted({
el_first_msg: this.el_input,
el_visibility: this.$('.public_toggle > .dropdown'),
el_stream: $('#gs-shim')
});
this.view_uploader = new app.views.PublisherUploader({
el: this.$('#file-upload'),
publisher: this
});
},
// set the selected aspects in the dropdown by their ids
setSelectedAspects: function(ids) {
this.view_aspect_selector.updateAspectsSelector(ids);
},
// show the "getting started" popups around the publisher
triggerGettingStarted: function() {
this.view_getting_started.show();
},
createStatusMessage : function(evt) {
if(evt){ evt.preventDefault(); }
@ -120,14 +149,15 @@ app.views.Publisher = Backbone.View.extend(_.extend(
showLocation: function(){
if($('#location').length == 0){
$('#publisher_textarea_wrapper').after('<div id="location"></div>');
app.views.location = new app.views.Location();
this.view_locator = new app.views.Location();
}
},
// destroys the location
destroyLocation: function(){
if(app.views.location){
app.views.location.remove();
if(this.view_locator){
this.view_locator.remove();
delete this.view_locator;
}
},
@ -260,11 +290,11 @@ app.views.Publisher = Backbone.View.extend(_.extend(
},
tryClose : function(){
// if it is not submittable, close it.
// if it is not submittable, close it.
if( !this._submittable() ){
this.close()
}
},
},
open : function() {
// visually 'open' the publisher
@ -286,14 +316,18 @@ app.views.Publisher = Backbone.View.extend(_.extend(
checkSubmitAvailability: function() {
if( this._submittable() ) {
this.el_submit.removeAttr('disabled');
this.el_preview.removeAttr('disabled');
this.setButtonsEnabled(true);
} else {
this.el_submit.attr('disabled','disabled');
this.el_preview.attr('disabled','disabled');
this.setButtonsEnabled(false);
}
},
setButtonsEnabled: function(bool) {
bool = !bool;
this.el_submit.prop({disabled: bool});
this.el_preview.prop({disabled: bool});
},
// determine submit availability
_submittable: function() {
var onlyWhitespaces = ($.trim(this.el_input.val()) === ''),
@ -311,7 +345,7 @@ app.views.Publisher = Backbone.View.extend(_.extend(
});
}
}));
});
// jQuery helper for serializing a <form> into JSON
$.fn.serializeObject = function()

View file

@ -1,91 +0,0 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
:javascript
function createUploader(){
var aspectIds = "#{aspect_ids}".split(',');
var uploader = new qq.FileUploaderBasic({
element: document.getElementById('file-upload'),
params: {'photo' : {'pending' : 'true', 'aspect_ids' : aspectIds}, 'set_profile_image' : "#{set_profile_image if defined?(set_profile_image)}"},
allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'tiff'],
action: "#{photos_path}",
debug: true,
button: document.getElementById('file-upload'),
sizeLimit: 4194304,
onProgress: function(id, fileName, loaded, total){
var progress = Math.round(loaded / total * 100 );
$('#fileInfo').text(fileName + ' ' + progress + '%').fadeTo(200, 1);
},
messages: {
typeError: "#{t('.invalid_ext')}",
sizeError: "#{t('.size_error')}",
emptyError: "#{t('.empty')}"
},
onSubmit: function(id, fileName){
$('#file-upload').addClass("loading");
$('#publisher').find("input[type='submit']").attr('disabled','disabled');
$('#publisher').find("button.post_preview_button").attr('disabled','disabled');
app.publisher.el_wrapper.addClass("with_attachments");
$('#photodropzone').append(
"<li class='publisher_photo loading' style='position:relative;'>" +
"#{escape_javascript(image_tag('ajax-loader2.gif'))}" +
"</li>"
);
},
onComplete: function(id, fileName, responseJSON) {
$('#fileInfo').text(Diaspora.I18n.t("photo_uploader.completed", file=fileName)).fadeTo(2000, 0);
var id = responseJSON.data.photo.id,
url = responseJSON.data.photo.unprocessed_image.url,
currentPlaceholder = $('li.loading').first();
app.publisher.el_wrapper.addClass("with_attachments");
$('#new_status_message').append("<input type='hidden' value='" + id + "' name='photos[]' />");
// replace image placeholders
var img = currentPlaceholder.find('img');
img.attr('src', url);
img.attr('data-id', id);
currentPlaceholder.removeClass('loading');
currentPlaceholder.append("<div class='x'>X</div>" +
"<div class='circle'></div>");
////
var publisher = $('#publisher'),
textarea = publisher.find('textarea');
publisher.find("input[type='submit']").removeAttr('disabled');
publisher.find("button.post_preview_button").removeAttr('disabled');
$('.x').bind('click', function(){
var photo = $(this).closest('.publisher_photo');
photo.addClass("dim");
$.ajax({url: "/photos/" + photo.children('img').attr('data-id'),
dataType: 'json',
type: 'DELETE',
success: function() {
photo.fadeOut(400, function(){
photo.remove();
if ( $('.publisher_photo').length == 0){
app.publisher.el_wrapper.removeClass("with_attachments");
}
});
}
});
});
},
onAllComplete: function(completed_files){
}
});
}
createUploader();

View file

@ -14,7 +14,6 @@
= status.error_messages
%div
%params
#fileInfo
#publisher_textarea_wrapper
= link_to(content_tag(:div, nil, :class => 'icons-deletelabel'), "#", :id => "hide_publisher", :title => t('.discard_post'))
%ul#photodropzone
@ -90,5 +89,3 @@
#publisher_photo_upload
= render 'photos/new_photo', :aspect_ids => aspect_ids.join(',')

View file

@ -18,7 +18,7 @@ describe("app.views.Publisher", function() {
it("hides the close button in standalone mode", function() {
expect(this.view.$('#hide_publisher').is(':visible')).toBeFalsy();
});
it("hides the post preview button in standalone mode", function() {
expect(this.view.$('.post_preview_button').is(':visible')).toBeFalsy();
});
@ -65,7 +65,7 @@ describe("app.views.Publisher", function() {
this.view.clear($.Event());
expect(this.view.close).toHaveBeenCalled();
})
it("calls removePostPreview", function(){
spyOn(this.view, "removePostPreview");
@ -121,18 +121,18 @@ describe("app.views.Publisher", function() {
var form = this.view.$("form")
var submitCallback = jasmine.createSpy().andReturn(false);
form.submit(submitCallback);
var e = $.Event("keydown", { keyCode: 13 });
e.ctrlKey = true;
this.view.keyDown(e);
expect(submitCallback).toHaveBeenCalled();
expect($(this.view.el)).not.toHaveClass("closed");
})
})
});
context("#toggleService", function(){
context("services", function(){
beforeEach( function(){
spec.loadFixture('aspects_index_services');
this.view = new app.views.Publisher();
@ -156,52 +156,42 @@ describe("app.views.Publisher", function() {
expect(second.hasClass('dim')).toBeTruthy();
});
describe("#_createCounter", function() {
it("gets called in when you toggle service icons", function(){
spyOn(this.view, '_createCounter');
$(".service_icon").first().trigger('click');
expect(this.view._createCounter).toHaveBeenCalled();
});
it("removes the 'old' .counter span", function(){
spyOn($.fn, "remove");
$(".service_icon").first().trigger('click');
expect($.fn.remove).toHaveBeenCalled();
});
it("creates a counter element", function(){
expect(this.view.$('.counter').length).toBe(0);
$(".service_icon").first().trigger('click');
expect(this.view.$('.counter').length).toBe(1);
});
describe("#_toggleServiceField", function() {
it("gets called when you toggle service icons", function(){
spyOn(this.view, '_toggleServiceField');
$(".service_icon").first().trigger('click');
expect(this.view._toggleServiceField).toHaveBeenCalled();
});
it("removes any old counters", function(){
spyOn($.fn, "remove");
$(".service_icon").first().trigger('click');
expect($.fn.remove).toHaveBeenCalled();
});
it("toggles the hidden input field", function(){
expect($('input[name="services[]"]').length).toBe(0);
$(".service_icon").first().trigger('click');
expect($('input[name="services[]"]').length).toBe(1);
$(".service_icon").first().trigger('click');
expect($('input[name="services[]"]').length).toBe(0);
});
it("toggles the hidden input field", function(){
expect(this.view.$('input[name="services[]"]').length).toBe(0);
$(".service_icon").first().trigger('click');
expect(this.view.$('input[name="services[]"]').length).toBe(1);
$(".service_icon").first().trigger('click');
expect(this.view.$('input[name="services[]"]').length).toBe(0);
});
it("toggles the correct input", function() {
var first = $(".service_icon").eq(0);
var second = $(".service_icon").eq(1);
it("toggles the correct input", function() {
var first = $(".service_icon").eq(0);
var second = $(".service_icon").eq(1);
first.trigger('click');
second.trigger('click');
first.trigger('click');
second.trigger('click');
expect($('input[name="services[]"]').length).toBe(2);
expect(this.view.$('input[name="services[]"]').length).toBe(2);
first.trigger('click');
first.trigger('click');
var prov1 = first.attr('id');
var prov2 = second.attr('id');
var prov1 = first.attr('id');
var prov2 = second.attr('id');
expect($('input[name="services[]"][value="'+prov1+'"]').length).toBe(0);
expect($('input[name="services[]"][value="'+prov2+'"]').length).toBe(1);
});
expect(this.view.$('input[name="services[]"][value="'+prov1+'"]').length).toBe(0);
expect(this.view.$('input[name="services[]"][value="'+prov2+'"]').length).toBe(1);
});
});
@ -236,18 +226,12 @@ describe("app.views.Publisher", function() {
expect(this.check_els.last().hasClass('selected')).toBeTruthy();
});
describe("#_updateSelectedAspectIds", function(){
describe("hidden form elements", function(){
beforeEach(function(){
this.li = $('<li data-aspect_id="42" />');
this.view.$('.dropdown_list').append(this.li);
});
it("gets called when aspects are selected", function(){
spyOn(this.view, "_updateSelectedAspectIds");
this.check_els.last().trigger('click');
expect(this.view._updateSelectedAspectIds).toHaveBeenCalled();
});
it("removes a previous selection and inserts the current one", function() {
var selected = this.view.$('input[name="aspect_ids[]"]');
expect(selected.length).toBe(1);
@ -261,13 +245,13 @@ describe("app.views.Publisher", function() {
});
it("toggles the same item", function() {
expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1);
expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(0);
this.li.trigger('click');
expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1);
expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(1);
this.li.trigger('click');
expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(0);
expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(0);
});
it("keeps other fields with different values", function() {
@ -275,30 +259,13 @@ describe("app.views.Publisher", function() {
this.view.$('.dropdown_list').append(li2);
this.li.trigger('click');
expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1);
li2.trigger('click');
expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(2);
expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(1);
expect(this.view.$('input[name="aspect_ids[]"][value="99"]').length).toBe(1);
});
});
describe("#_addHiddenAspectInput", function(){
it("gets called when aspects are selected", function(){
spyOn(this.view, "_addHiddenAspectInput");
this.check_els.last().trigger('click');
expect(this.view._addHiddenAspectInput).toHaveBeenCalled();
});
it("adds a hidden input to the form", function(){
var id = 42;
this.view._addHiddenAspectInput(id);
var input = this.view.$('input[name="aspect_ids[]"][value="'+id+'"]');
expect(input.length).toBe(1);
expect(input.val()).toBe('42');
});
});
});
context("locator", function() {
@ -314,9 +281,9 @@ describe("app.views.Publisher", function() {
it("Show location", function(){
// inserts location to the DOM; it is the location's view element
setFixtures('<div id="publisher_textarea_wrapper"></div>');
setFixtures('<div id="publisher_textarea_wrapper"></div>');
// creates a fake Locator
// creates a fake Locator
OSM = {};
OSM.Locator = function(){return { getAddress:function(){}}};
@ -333,26 +300,11 @@ describe("app.views.Publisher", function() {
describe('#destroyLocation', function(){
it("Destroy location if exists", function(){
// inserts location to the DOM; it is the location's view element
setFixtures('<div id="location"></div>');
//Backup original view
var original_location = app.views.Location;
// creates a new Location view with the #location element
app.views.Location = new Backbone.View({el:"#location"});
// creates the mock
app.views.location = sinon.mock(app.views.Location).object;
// calls the destroy function and test the expected result
setFixtures('<div id="location"></div>');
this.view.view_locator = new app.views.Location({el: "#location"});
this.view.destroyLocation();
expect($("#location").length).toBe(0);
//Restore view
app.views.Location = original_location;
})
});
@ -368,5 +320,135 @@ describe("app.views.Publisher", function() {
});
});
context('uploader', function() {
beforeEach(function() {
jQuery.fx.off = true;
setFixtures(
'<div id="publisher">'+
' <div class="content_creation"><form>'+
' <div id="publisher_textarea_wrapper"></div>'+
' <div id="photodropzone"></div>'+
' <input type="submit" />'+
' <button class="post_preview_button" />'+
' </form></div>'+
'</div>'
);
});
it('initializes the file uploader plugin', function() {
spyOn(qq, 'FileUploaderBasic');
var publisher = new app.views.Publisher();
expect(qq.FileUploaderBasic).toHaveBeenCalled();
});
context('event handlers', function() {
beforeEach(function() {
this.view = new app.views.Publisher();
// replace the uploader plugin with a dummy object
var upload_view = this.view.view_uploader;
this.uploader = {
onProgress: _.bind(upload_view.progressHandler, upload_view),
onSubmit: _.bind(upload_view.submitHandler, upload_view),
onComplete: _.bind(upload_view.uploadCompleteHandler, upload_view)
};
upload_view.uploader = this.uploader;
});
context('progress', function() {
it('shows progress in percent', function() {
this.uploader.onProgress(null, 'test.jpg', 20, 100);
var info = this.view.view_uploader.el_info;
expect(info.text()).toContain('test.jpg');
expect(info.text()).toContain('20%');
});
});
context('submitting', function() {
beforeEach(function() {
this.uploader.onSubmit(null, 'test.jpg');
});
it('adds a placeholder', function() {
expect(this.view.el_wrapper.attr('class')).toContain('with_attachments');
expect(this.view.el_photozone.find('li').length).toBe(1);
});
it('disables the publisher buttons', function() {
expect(this.view.el_submit.prop('disabled')).toBeTruthy();
expect(this.view.el_preview.prop('disabled')).toBeTruthy();
});
});
context('completion', function() {
beforeEach(function() {
Diaspora.I18n.loadLocale({ photo_uploader: { completed: '<%= file %> completed' }});
$('#photodropzone').html('<li class="publisher_photo loading"><img src="" /></li>');
this.uploader.onComplete(null, 'test.jpg', { data: { photo: {
id: '987',
unprocessed_image: { url: 'test.jpg' }
}}});
});
it('shows it in text form', function() {
var info = this.view.view_uploader.el_info;
expect(info.text()).toBe(Diaspora.I18n.t('photo_uploader.completed', {file: 'test.jpg'}))
});
it('adds a hidden input to the publisher', function() {
var input = this.view.$('input[type="hidden"][value="987"][name="photos[]"]');
expect(input.length).toBe(1);
});
it('replaces the placeholder', function() {
var li = this.view.el_photozone.find('li');
var img = li.find('img');
expect(li.attr('class')).not.toContain('loading');
expect(img.attr('src')).toBe('test.jpg');
expect(img.attr('data-id')).toBe('987');
});
it('re-enables the buttons', function() {
expect(this.view.el_submit.prop('disabled')).toBeFalsy();
expect(this.view.el_preview.prop('disabled')).toBeFalsy();
});
});
});
context('photo removal', function() {
beforeEach(function() {
this.view = new app.views.Publisher();
this.view.el_wrapper.addClass('with_attachments');
this.view.el_photozone.html(
'<li class="publisher_photo">.'+
' <img data-id="444" />'+
' <div class="x">X</div>'+
' <div class="circle"></div>'+
'</li>'
);
spyOn(jQuery, 'ajax').andCallFake(function(opts) { opts.success(); });
this.view.el_photozone.find('.x').click();
});
it('removes the element', function() {
var photo = this.view.el_photozone.find('li.publisher_photo');
expect(photo.length).toBe(0);
});
it('sends an ajax request', function() {
expect($.ajax).toHaveBeenCalled();
});
it('removes class on wrapper element', function() {
expect(this.view.el_wrapper.attr('class')).not.toContain('with_attachments');
});
});
});
});