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 ./pages
//= require_tree ./collections //= require_tree ./collections
//= require_tree ./views //= require_tree ./views
//= require ./forms
//= require_tree ./forms //= require_tree ./forms
var app = { 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", templateName : "picture-form",
events : { events : {

View file

@ -1,48 +1,13 @@
app.forms.Post = app.forms.Base.extend({ app.forms.Post = app.views.Base.extend({
templateName : "post-form", templateName : "post-form",
formSelector : ".new-post", className : "post-form",
subviews : { subviews : {
".aspect_selector" : "aspectsDropdown",
".service_selector" : "servicesSelector",
".new_picture" : "pictureForm" ".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() { initialize : function() {
this.aspectsDropdown = new app.views.AspectsDropdown();
this.servicesSelector = new app.views.ServicesSelector();
this.pictureForm = new app.forms.Picture(); 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() { 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({ app.pages.Framer = app.views.Base.extend({
templateName : "framer", templateName : "flow",
id : "post-content", id : "post-content",
events : {
"click button.done" : "saveFrame"
},
subviews : { subviews : {
".post-view" : "postView", ".flow-content" : "postView",
".template-picker" : "templatePicker" ".flow-controls .controls" : "framerControls"
}, },
initialize : function(){ initialize : function(){
@ -18,7 +14,8 @@ app.pages.Framer = app.views.Base.extend({
this.model.bind("change", this.render, this) this.model.bind("change", this.render, this)
this.model.bind("sync", this.navigateToShow, 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(){ postView : function(){
@ -27,9 +24,25 @@ app.pages.Framer = app.views.Base.extend({
navigateToShow : function(){ navigateToShow : function(){
app.router.navigate(this.model.url(), {trigger: true, replace: true}) 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(){ saveFrame : function(){
this.model.save() 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", "followed_tags": "stream",
"tags/:name": "stream", "tags/:name": "stream",
"posts/new" : "newPost", "posts/new" : "composer",
"posts/:id": "singlePost", "posts/:id": "singlePost",
"p/:id": "singlePost", "p/:id": "singlePost",
"framer": "framer" "framer": "framer"
@ -41,8 +41,8 @@ app.Router = Backbone.Router.extend({
$("#main_stream").html(app.page.render().el); $("#main_stream").html(app.page.render().el);
}, },
newPost : function(){ composer : function(){
var page = new app.pages.PostNew(); var page = new app.pages.Composer();
$("#container").html(page.render().el) $("#container").html(page.render().el)
}, },

View file

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

View file

@ -304,7 +304,27 @@ article { //mood posts
$bring-dark-accent-forward-color: #DDD; $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(); @include info-container-base();
z-index: 999; z-index: 999;
@ -312,8 +332,14 @@ $bring-dark-accent-forward-color: #DDD;
width: 100%; width: 100%;
bottom: 0; bottom: 0;
padding: 20px 0;
max-height: 68px;
box-sizing: border-box;
-moz-box-sizing: border-box;
.controls { .controls {
margin: 20px auto; margin: 0 auto;
max-width: 960px; max-width: 960px;
text-align: center; 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 { a.mood {
@include border-radius(); @include border-radius();
margin-right: 20px; 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{ .new_photo .photo{
display: inline; display: inline;
max-width: 200px; max-width: 200px;
max-height: 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="container">
<div class='span8 offset2 new-post-section'> <div id="new-post" class="row">
<div class="new_picture"/> <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"> <div class="new_picture"/>
<fieldset> </div>
<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> </div>
</div> </div>

View file

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

View file

@ -68,7 +68,7 @@ Then /I mention "([^"]*)"$/ do |text|
end end
When /^I select "([^"]*)" in my aspects dropdown$/ do |title| When /^I select "([^"]*)" in my aspects dropdown$/ do |title|
within ".aspect_selector" do within ".aspect-selector" do
select_from_dropdown(title, aspects_dropdown) select_from_dropdown(title, aspects_dropdown)
end end
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); 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(); 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(){ it("passes the model down to the post view", function(){
expect(this.page.postView().model).toBe(app.frame) expect(this.page.postView().model).toBe(app.frame)
}); });
@ -29,5 +25,12 @@ describe("app.pages.Framer", function(){
this.page.model.trigger("sync") this.page.model.trigger("sync")
expect(app.router.navigate).toHaveBeenCalled() 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() { beforeEach(function() {
$('#jasmine_content').html(spec.readFixture("underscore_templates")); $('#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(); jasmine.Clock.useMock();