Refactor composer and framer to user same layout

composer touchup; added controls to the bottom; centered input area; removed forms.Base view

Fix tests
This commit is contained in:
danielgrippi 2012-04-09 18:45:39 -07:00 committed by Dennis Collinson
parent ecf77b6ab4
commit a1d4ca9bec
24 changed files with 242 additions and 222 deletions

View file

@ -6,7 +6,6 @@
//= require_tree ./pages
//= require_tree ./collections
//= require_tree ./views
//= require ./forms
//= require_tree ./forms
var app = {

View file

@ -1,14 +0,0 @@
app.forms.Base = app.views.Base.extend({
formSelector : "form",
initialize : function() {
this.setupFormEvents()
},
setupFormEvents : function(){
this.events = {}
this.events['submit ' + this.formSelector] = 'setModelAttributes';
this.delegateEvents();
},
})

View file

@ -1,4 +1,4 @@
app.forms.Picture = app.forms.Base.extend({
app.forms.Picture = app.views.Base.extend({
templateName : "picture-form",
events : {

View file

@ -1,48 +1,13 @@
app.forms.Post = app.forms.Base.extend({
app.forms.Post = app.views.Base.extend({
templateName : "post-form",
formSelector : ".new-post",
className : "post-form",
subviews : {
".aspect_selector" : "aspectsDropdown",
".service_selector" : "servicesSelector",
".new_picture" : "pictureForm"
},
formAttrs : {
// "textarea#text_with_markup" : "text", //fix mentions
"textarea.text" : "text",
"input.aspect_ids" : "aspect_ids",
"input.service:checked" : "services"
},
initialize : function() {
this.aspectsDropdown = new app.views.AspectsDropdown();
this.servicesSelector = new app.views.ServicesSelector();
this.pictureForm = new app.forms.Picture();
this.setupFormEvents();
},
setModelAttributes : function(evt){
if(evt){ evt.preventDefault(); }
var form = this.$(this.formSelector);
this.model.set(_.inject(this.formAttrs, setValueFromField, {}))
//pass collections across
this.model.photos = this.pictureForm.photos
this.model.set({"photos": this.model.photos.toJSON() })
function setValueFromField(memo, attribute, selector){
var selectors = form.find(selector);
if(selectors.length > 1) {
memo[attribute] = _.map(selectors, function(selector){
return $(selector).val()
})
} else {
memo[attribute] = selectors.val();
}
return memo
}
},
postRenderTemplate : function() {

View file

@ -0,0 +1,67 @@
app.pages.Composer = app.views.Base.extend({
templateName : "flow",
subviews : {
".flow-content" : "postForm",
".flow-controls .controls" : "composerControls"
},
events : {
"click button.next" : "navigateNext"
},
formAttrs : {
// "textarea#text_with_markup" : "text", //fix mentions
"textarea.text" : "text",
"input.aspect_ids" : "aspect_ids",
"input.service:checked" : "services"
},
initialize : function(){
app.frame = this.model = new app.models.StatusMessage();
this.postForm = new app.forms.Post({model : this.model});
this.composerControls = new app.views.ComposerControls({model : this.model});
},
navigateNext : function(){
this.setModelAttributes();
app.router.navigate("framer", true);
},
setModelAttributes : function(evt){
if(evt){ evt.preventDefault(); }
var form = this.$el;
this.model.set(_.inject(this.formAttrs, setValueFromField, {}))
this.model.photos = this.postForm.pictureForm.photos
this.model.set({"photos": this.model.photos.toJSON() })
function setValueFromField(memo, attribute, selector){
var selectors = form.find(selector);
if(selectors.length > 1) {
memo[attribute] = _.map(selectors, function(selector){
return this.$(selector).val()
})
} else {
memo[attribute] = selectors.val();
}
return memo
}
}
});
app.views.ComposerControls = app.views.Base.extend({
templateName : 'composer-controls',
subviews : {
".aspect-selector" : "aspectsDropdown",
".service-selector" : "servicesSelector",
},
initialize : function() {
this.aspectsDropdown = new app.views.AspectsDropdown();
this.servicesSelector = new app.views.ServicesSelector();
}
})

View file

@ -1,15 +1,11 @@
app.pages.Framer = app.views.Base.extend({
templateName : "framer",
templateName : "flow",
id : "post-content",
events : {
"click button.done" : "saveFrame"
},
subviews : {
".post-view" : "postView",
".template-picker" : "templatePicker"
".flow-content" : "postView",
".flow-controls .controls" : "framerControls"
},
initialize : function(){
@ -18,7 +14,8 @@ app.pages.Framer = app.views.Base.extend({
this.model.bind("change", this.render, this)
this.model.bind("sync", this.navigateToShow, this)
this.templatePicker = new app.views.TemplatePicker({ model: this.model })
this.framerControls = new app.views.framerControls({model : this.model})
},
postView : function(){
@ -27,9 +24,25 @@ app.pages.Framer = app.views.Base.extend({
navigateToShow : function(){
app.router.navigate(this.model.url(), {trigger: true, replace: true})
}
})
app.views.framerControls = app.views.Base.extend({
templateName : 'framer-controls',
events : {
"click button.done" : "saveFrame"
},
subviews : {
".template-picker" : 'templatePicker'
},
initialize : function(){
this.templatePicker = new app.views.TemplatePicker({ model: this.model })
},
saveFrame : function(){
this.model.save()
}
})
})

View file

@ -1,20 +0,0 @@
app.pages.PostNew = app.views.Base.extend({
templateName : "post-new",
subviews : { "#new-post" : "postForm"},
events : {
"click button.next" : "navigateNext"
},
initialize : function(){
this.model = new app.models.StatusMessage()
this.postForm = new app.forms.Post({model : this.model})
},
navigateNext : function(){
this.postForm.setModelAttributes()
app.frame = this.model;
app.router.navigate("framer", true)
}
});

View file

@ -18,7 +18,7 @@ app.Router = Backbone.Router.extend({
"followed_tags": "stream",
"tags/:name": "stream",
"posts/new" : "newPost",
"posts/new" : "composer",
"posts/:id": "singlePost",
"p/:id": "singlePost",
"framer": "framer"
@ -41,8 +41,8 @@ app.Router = Backbone.Router.extend({
$("#main_stream").html(app.page.render().el);
},
newPost : function(){
var page = new app.pages.PostNew();
composer : function(){
var page = new app.pages.Composer();
$("#container").html(page.render().el)
},

View file

@ -2,7 +2,7 @@
// licensed under the Affero General Public License version 3 or later. See
// the COPYRIGHT file.
@import "_mixins.css.scss";
@import "mixins";
#login {
width: 400px;

View file

@ -304,7 +304,27 @@ article { //mood posts
$bring-dark-accent-forward-color: #DDD;
.framer-controls {
div[data-template=flow] {
display : table;
width : 100%;
height : 100%;
position : absolute;
}
.flow-content {
display : table;
width : 100%;
height : 100%;
}
.post-form {
display : table-cell;
vertical-align : middle;
}
.flow-controls {
@include info-container-base();
z-index: 999;
@ -312,8 +332,14 @@ $bring-dark-accent-forward-color: #DDD;
width: 100%;
bottom: 0;
padding: 20px 0;
max-height: 68px;
box-sizing: border-box;
-moz-box-sizing: border-box;
.controls {
margin: 20px auto;
margin: 0 auto;
max-width: 960px;
text-align: center;
@ -323,6 +349,33 @@ $bring-dark-accent-forward-color: #DDD;
}
}
.aspect-selector {
float: left;
i {
display: none;
}
.selected i {
display: inline-block;
}
a {
display : inline-block;
}
}
.service-selector {
float: left;
margin-left: 100px;
}
.dropdown-menu {
bottom : 0;
top: auto;
text-align: left;
}
a.mood {
@include border-radius();
margin-right: 20px;

View file

@ -1,27 +1,5 @@
.aspects_dropdown {
i {
display: none;
}
.selected i {
display: inline-block;
}
a {
display : inline-block;
}
}
.new_photo .photo{
display: inline;
max-width: 200px;
max-height: 200px;
}
.new-post-section {
margin-top: 100px;
}
.aspect_selector {
float: right;
}

View file

@ -0,0 +1,3 @@
<button class="done btn-primary next">Next</button>
<div class="aspect-selector"/>
<div class="service-selector"/>

View file

@ -0,0 +1,5 @@
<div class="flow-content"/>
<div class="flow-controls">
<div class="controls"/>
</div>

View file

@ -0,0 +1,2 @@
<button class="done btn-primary">done</button>
<div class='template-picker'></div>

View file

@ -1,7 +0,0 @@
<div class="framer-controls">
<div class="controls">
<button class="done btn-primary">done</button>
<div class='template-picker'></div>
</div>
</div>
<div class="post-view"></div>

View file

@ -1,21 +1,20 @@
<div class='row'>
<div class='span8 offset2 new-post-section'>
<div class="new_picture"/>
<div class="container">
<div id="new-post" class="row">
<div class='span8 offset2 new-post-section'>
<form class="new-post">
<fieldset>
<legend>
New Post
</legend>
<label>
text
<textarea class="text span8"/>
</label>
<textarea id="text_with_markup" style="display:none;"/>
</fieldset>
</form>
<form class="new-post">
<fieldset>
<legend>
New Post
</legend>
<label>
text
<textarea class="text span8"/>
</label>
<textarea id="text_with_markup" style="display:none;"/>
<div class="aspect_selector"></div>
<div class="service_selector"></div>
</fieldset>
</form>
<div class="new_picture"/>
</div>
</div>
</div>

View file

@ -3,7 +3,3 @@
<button class="btn-primary next">Next</button>
</div>
</div>
<div class="container">
<div id="new-post"></div>
</div>

View file

@ -68,7 +68,7 @@ Then /I mention "([^"]*)"$/ do |text|
end
When /^I select "([^"]*)" in my aspects dropdown$/ do |title|
within ".aspect_selector" do
within ".aspect-selector" do
select_from_dropdown(title, aspects_dropdown)
end
end

View file

@ -1,42 +0,0 @@
describe("app.forms.Post", function(){
beforeEach(function(){
this.post = new app.models.Post();
this.view = new app.forms.Post({model : this.post})
})
describe("rendering", function(){
beforeEach(function(){
this.view.render()
})
describe("submitting a valid form", function(){
beforeEach(function(){
this.view.$("form .text").val("Oh My")
this.view.$("form .aspect_ids").val("public")
/* appending checkboxes */
this.view.$(".new-post").append($("<input/>", {
value : "fakeBook",
checked : "checked",
"class" : "service",
"type" : "checkbox"
}))
this.view.$(".new-post").append($("<input/>", {
value : "twitter",
checked : "checked",
"class" : "service",
"type" : "checkbox"
}))
})
it("instantiates a post on form submit", function(){
this.view.$(".new-post").submit()
expect(this.view.model.get("text")).toBe("Oh My")
expect(this.view.model.get("aspect_ids")).toBe("public")
expect(this.view.model.get("services").length).toBe(2)
})
})
})
})

View file

@ -23,5 +23,4 @@ describe("app.models.Photo", function() {
expect(this.photo.createdAt()).toEqual(+date);
});
});
});

View file

@ -0,0 +1,56 @@
describe("app.pages.Composer", function(){
beforeEach(function(){
this.page = new app.pages.Composer()
})
it("stores a reference to the form as app.composer" , function(){
expect(this.page.model).toBeDefined()
expect(app.frame).toBe(this.page.model)
});
describe("rendering", function(){
beforeEach(function(){
this.page.render();
})
describe("clicking next", function(){
beforeEach(function(){
spyOn(app.router, "navigate")
})
it("navigates to the framer", function(){
this.page.$("button.next").click()
expect(app.router.navigate).toHaveBeenCalledWith("framer", true)
});
describe(" setting the model's attributes from the various form fields", function(){
beforeEach(function(){
this.page.$("form .text").val("Oh My")
this.page.$("input.aspect_ids").val("public")
/* appending checkboxes */
this.page.$(".service-selector").append($("<input/>", {
value : "fakeBook",
checked : "checked",
"class" : "service",
"type" : "checkbox"
}))
this.page.$(".service-selector").append($("<input/>", {
value : "twitter",
checked : "checked",
"class" : "service",
"type" : "checkbox"
}))
})
it("instantiates a post on form submit", function(){
this.page.$("button.next").click()
expect(this.page.model.get("text")).toBe("Oh My")
expect(this.page.model.get("aspect_ids")).toBe("public")
expect(this.page.model.get("services").length).toBe(2)
})
});
})
})
});

View file

@ -5,10 +5,6 @@ describe("app.pages.Framer", function(){
this.page = new app.pages.Framer();
});
it("passes the model down to the template picker", function(){
expect(this.page.templatePicker.model).toBe(app.frame)
});
it("passes the model down to the post view", function(){
expect(this.page.postView().model).toBe(app.frame)
});
@ -29,5 +25,12 @@ describe("app.pages.Framer", function(){
this.page.model.trigger("sync")
expect(app.router.navigate).toHaveBeenCalled()
})
it("makes and renders a new post view when the template is changed", function(){
expect(app.frame.get("frame_name")).not.toBe("Night") //pre conditions, yo
this.page.$("a.mood[data-mood=Night]").click()
expect(app.frame.get("frame_name")).toBe("Night")
expect(this.page.$("article")).toHaveClass("night")
})
});
});

View file

@ -1,32 +0,0 @@
describe("app.pages.PostNew", function(){
beforeEach(function(){
this.page = new app.pages.PostNew()
})
describe("rendering", function(){
beforeEach(function(){
this.page.render();
})
describe("clicking next", function(){
beforeEach(function(){
spyOn(app.router, "navigate")
spyOn(this.page.postForm, "setModelAttributes")
this.page.$("button.next").click()
})
it("calls tells the form to set the models attributes", function(){
expect(this.page.postForm.setModelAttributes).toHaveBeenCalled();
});
it("stores a reference to the form as app.composer" , function(){
expect(this.page.model).toBeDefined()
expect(app.frame).toBe(this.page.model)
});
it("navigates to the framer", function(){
expect(app.router.navigate).toHaveBeenCalledWith("framer", true)
});
})
})
});

View file

@ -11,9 +11,6 @@
beforeEach(function() {
$('#jasmine_content').html(spec.readFixture("underscore_templates"));
// NOTE Commented (as well as in afterEach) to keep the listeners from rails.js alive.
//spec.clearLiveEventBindings();
jasmine.Clock.useMock();