Hey so I am trying to switch from underscore to handlebar, but nothing is rendering from the model, but the templates are changing correctly. Also, when the editTemplate shows from clicking the edit button the #{firstName} and others show as undefined.
In my layout jade file I do include all the appropriate files, jquery, underscore,backbone and handlebar.
Here is my main.js file
(function () {
window.App = {
Models: {},
Collections: {},
Views: {},
// Templates: {},
Router: {}
};
// MODEL
App.Models.User = Backbone.Model.extend({
defaults: {
firstName: 'first',
lastName: 'last',
email: 'Email',
phone: '222',
birthday: 'date'
},
validate: function (attrs) {
if (!attrs.firstName) {
return 'You must enter a real first name.';
}
if (!attrs.lastName) {
return 'You must enter a real last name.';
}
if (attrs.email.length < 5) {
return 'You must enter a real email.';
}
if (attrs.phone.length < 10 && attrs.phone === int) {
return 'You must enter a real phone number, if you did please remove the dash and spaces.';
}
if (attrs.city.length < 2) {
return 'You must enter a real city.';
}
},
initialize: function() {
this.on('invalid', function (model, invalid) {
console.log(invalid);
});
}
});
//VIEW
App.Views.User = Backbone.View.extend({
model: App.Models.User,
el: 'user',
//tagName: 'div',
//id: 'user',
//className: 'userProfile',
initialize: function (){
},
render: function() {
var template = Handlebars.compile($("#userTemplate").html());
var editTemplate = Handlebars.compile($("#userEditTemplate").html());
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events: {
'click button.edit': 'editProfile',
// 'click button.save': 'saveEdits',
'click button.cancel': 'cancelEdits'
},
editProfile: function () {
this.$el.html(this.editTemplate(this.model.toJSON()));
},
cancelEdits: function() {
this.render();
}
});
//start history service
Backbone.history.start();
var user = new App.Views.User({model: new App.Models.User()});
user.render();
})();
Here is my jade file
extends layout
block content
div.centerContent
script(type="text/javascript", src="/js/main.js")
h4 User goes here with equal before it no space
div#user
p #{firstName} #{lastName}
p #{email}
p #{phone}
p #{birthday}
button.edit Edit
script(id="userTemplate", type ="text/template")
p #{firstName} #{lastName}
p #{email}
p #{phone}
p #{birthday}
button.edit Edit
script(id="userEditTemplate", type ="text/template")
div
form(action="#")
input(type="text", class="firstName", value=#{firstName}) input(type="text", class="lastName", value=#{lastName})
input(type="email", class="email", value=#{email})
input(type="number", class="phone", value=#{phone})
input(type="date", class="birthday", value=#{birthday})
button.save Save
button.cancel Cancel
hr
layout jade file
doctype 5
html
head
title=title
link(rel='stylesheet', href='/css/style.css', type='text/css')
link(rel='stylesheet', href='/css/bootstrap-responsive.css')
link(href='/css/bootstrap.css', rel='stylesheet', type='text/css')
link(href='/css/font-awesome.min.css', rel='stylesheet', type='text/css')
script(src='/js/jquery.min.js', type='text/javascript')
script(src='/js/jquery.validate.min.js', type='text/javascript')
script(src='/js/script.js', type='text/javascript')
script(src='/js/underscore.min.js', type='text/javascript')
script(src='/js/backbone.min.js', type='text/javascript')
script(src='/js/handlebars.js', type='text/javascript')
body
div#container
div#header
block content
include footer
If anyone wanted to see the answer, it was basically a mistake of calling this
#{firstName}
instead of the correct usage being:
{{firstName}}
Related
So I have a new form set up it saves temporarily and all but I want it to only be able to update when it is validated otherwise show some errors. This is during the view section for the saveEdits event Any clue as to what I am doing wrong?
This is my main.js file
(function () {
window.App = {
Models: {},
Collections: {},
Views: {},
Templates: {},
Router: {}
};
// MODEL
App.Models.User = Backbone.Model.extend({
defaults: {
firstName: 'first',
lastName: 'last',
email: 'Email',
phone: '222',
birthday: '07/22/1980'
},
validate: function (attrs) {
if (!attrs.firstName) {
return 'You must enter a real first name.';
}
if (!attrs.lastName) {
return 'You must enter a real last name.';
}
if (attrs.email.length < 5) {
return 'You must enter a real email.';
}
if (attrs.phone.length < 10 && attrs.phone === int) {
return 'You must enter a real phone number, if you did please remove the dash and spaces.';
}
if (attrs.city.length < 2) {
return 'You must enter a real city.';
}
},
initialize: function() {
this.on('invalid', function (model, invalid) {
console.log(invalid);
});
}
});
//var userModel = new App.Models.User();
//VIEW
App.Views.User = Backbone.View.extend({
el: '#user',
//model: userModel,
//tagName: 'div',
//id: 'user',
//className: 'userProfile',
//template: _.template($("#userTemplate").html()),
//editTemplate: _.template($("#userEditTemplate").html()),
initialize: function (){
},
render: function() {
this.template = Handlebars.compile($("#userTemplate").html());
this.editTemplate = Handlebars.compile($("#userEditTemplate").html());
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events: {
'click button.edit': 'editProfile',
'click button.save': 'saveEdits',
'click button.cancel': 'cancelEdits'
},
editProfile: function () {
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveEdits: function () {
var form = $(this.el).find('form#updateUser');
this.model.set({
firstName : form.find('.firstName').val(),
lastName : form.find('.lastName').val(),
email: form.find('.email').val(),
phone: form.find('.phone').val(),
birthday: form.find('.birthday').val()
});
this.model.validate();
this.render();
},
cancelEdits: function() {
this.render();
}
});
//start history service
Backbone.history.start();
var user = new App.Views.User({model: new App.Models.User()});
user.render();
})();
It works fine until I insert the this.model.validate and an error shows up stating this:
Uncaught TypeError: Cannot read property 'firstName' of undefined
You don't call validate explicitly -- it's meant called by the Backbone framework:
By default validate is called before save, but can also be called before set if {validate:true} is passed.
So to fix the code in the OP, use validate: true in the call to set:
this.model.set({
firstName : form.find('.firstName').val(),
// ...
}, { validate: true });
Note that, if you wanted to call validate, then you have to pass it the attrs parameter, as in this.model.validate(this.model.toJSON());
Hey so I am having a problem where my templates change but the backbone model does not seem to provide the info I need. I am trying to solve this so I can move on to making collections. I am not getting any errors and I am using handlebars but have tried underscore's templating as well and the problem doesn't lie there but against the info displaying. When I click edit and the edit template shows it is showing undefined for #{firstName} and the others.
Main.js file
(function () {
window.App = {
Models: {},
Collections: {},
Views: {},
Templates: {},
Router: {}
};
// MODEL
App.Models.User = Backbone.Model.extend({
defaults: {
firstName: 'first',
lastName: 'last',
email: 'Email',
phone: '222',
birthday: 'date'
},
validate: function (attrs) {
if (!attrs.firstName) {
return 'You must enter a real first name.';
}
if (!attrs.lastName) {
return 'You must enter a real last name.';
}
if (attrs.email.length < 5) {
return 'You must enter a real email.';
}
if (attrs.phone.length < 10 && attrs.phone === int) {
return 'You must enter a real phone number, if you did please remove the dash and spaces.';
}
if (attrs.city.length < 2) {
return 'You must enter a real city.';
}
},
initialize: function() {
this.on('invalid', function (model, invalid) {
console.log(invalid);
});
}
});
//VIEW
App.Views.User = Backbone.View.extend({
el: '#user',
//model: userModel,
//tagName: 'div',
//id: 'user',
//className: 'userProfile',
//template: _.template($("#userTemplate").html()),
//editTemplate: _.template($("#userEditTemplate").html()),
initialize: function (){
},
render: function() {
this.template = Handlebars.compile($("#userTemplate").html());
this.editTemplate = Handlebars.compile($("#userEditTemplate").html());
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events: {
'click button.edit': 'editProfile',
// 'click button.save': 'saveEdits',
'click button.cancel': 'cancelEdits'
},
editProfile: function () {
this.$el.html(this.editTemplate(this.model.toJSON()));
},
//saveEdits: function (e) {
// e.preventDefault();
// var formData = {},
// prev = this.model.previousAttributes();
//
//get form data
// $(e.target).closest('form').find(':input').not('button').each(function(){
// var el = $(this);
// formData[el.attr('class')] = el.val();
// });
//update model
// this.model.set(formData);
//render view
// this.render();
//update array
//},
cancelEdits: function() {
this.render();
}
});
//start history service
Backbone.history.start();
var user = new App.Views.User({model: new App.Models.User()});
user.render();
})();
Jade file
extends layout
block content
div.centerContent
h4 User goes here with equal before it no space
div#user
p #{firstName} #{lastName}
p #{email}
p #{phone}
p #{birthday}
button.edit Edit
script(id="userTemplate", type ="text/template")
p #{firstName} #{lastName} 1
p #{email} 2
p #{phone} 3
p #{birthday} 4
button.edit Edit
script(id="userEditTemplate", type ="text/template")
div
form(action="#")
input(type="text", class="firstName", value='#{firstName}')
input(type="text", class="lastName", value='#{lastName}')
input(type="email", class="email", value='#{email}')
input(type="number", class="phone", value='#{phone}')
input(type="date", class="birthday", value='#{birthday}')
button.save Save
button.cancel Cancel
hr
script(type="text/javascript", src="/js/main.js")
Try removing this line from your view
model: App.Models.User,
You are trying to assign a Backbone function directly to your view.
Also the problem is because of this line in your render method..
render: function() {
var template = Handlebars.compile($("#userTemplate").html());
var editTemplate = Handlebars.compile($("#userEditTemplate").html());
The editemplate is local to the render method and not available in other methods.
editProfile: function () {
this.$el.html(this.editTemplate(this.model.toJSON()));
},
You are trying to access the template using this.editTemplate , but the variable is local to the render method and not to the view.. So you are not able to access it..
Instead of creating it in the local scope, instantiate to the view it self so that it is available in other methods of the view.
render: function() {
this.template = Handlebars.compile($("#userTemplate").html());
this.editTemplate = Handlebars.compile($("#userEditTemplate").html());
Then it should be available to the other methods as well in the same view.
so I am trying to get the validate from my model to actually disable the save button but to re-validate when new input is included. Anyone know the best way to attempt this. Thanks! The problem I have with my method is that once it is disabled it doesn't return in state.
Here is the main.js file
(function () {
window.App = {
Models: {},
Collections: {},
Views: {},
Templates: {},
Router: {}
};
// MODEL
App.Models.User = Backbone.Model.extend({
defaults: {
firstName: 'first',
lastName: 'last',
email: 'Email',
phone: '222',
birthday: '07/22/1980'
},
validate: function (attrs) {
if (!attrs.firstName) {
return 'You must enter a real first name.';
}
if (!attrs.lastName) {
return 'You must enter a real last name.';
}
if (attrs.email.length < 5) {
return 'You must enter a real email.';
}
if (attrs.phone.toString().length < 10 ) {
//&& attrs.phone === int
return 'You must enter a real phone number, if you did please remove the dash and spaces.';
}
// if (attrs.birthday.length < 2) {
// return 'You must enter a real city.';
//}
},
initialize: function() {
this.on('invalid', function (model, invalid) {
console.log(invalid);
alert(invalid);
});
}
});
//var userModel = new App.Models.User();
//VIEW
App.Views.User = Backbone.View.extend({
el: '#user',
//model: userModel,
//tagName: 'div',
//id: 'user',
//className: 'userProfile',
//template: _.template($("#userTemplate").html()),
//editTemplate: _.template($("#userEditTemplate").html()),
initialize: function (){
},
render: function() {
this.template = Handlebars.compile($("#userTemplate").html());
this.editTemplate = Handlebars.compile($("#userEditTemplate").html());
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events: {
'click button.edit': 'editProfile',
'click input.save': 'saveEdits',
'click button.cancel': 'cancelEdits'
},
editProfile: function () {
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveEdits: function () {
var form = $(this.el).find('form#updateUser');
this.model.set({
firstName : form.find('.firstName').val(),
lastName : form.find('.lastName').val(),
email: form.find('.email').val(),
phone: form.find('.phone').val(),
birthday: form.find('.birthday').val()
}, {validate: true} );
if(!this.model.isValid()) {
console.log('run');
$('.save').attr("disabled", "disabled");
} else {
console.log('run2');
alert('Changes have been made.');
$('.save').removeAttr("disabled");
return this.render();
}
},
},
cancelEdits: function() {
this.render();
}
});
//start history service
Backbone.history.start();
var user = new App.Views.User({model: new App.Models.User()});
user.render();
})();
And here is the Jade File:
extends layout
block content
div.centerContent
h4 User goes here with equal before it no space
div#user
p #{firstName} #{lastName}
p #{email}
p #{phone}
p #{birthday}
button.edit Edit
script(id="userTemplate", type ="text/template")
p {{firstName}} {{lastName}} 1
p {{email}} 2
p {{phone}} 3
p {{birthday}} 4
button.edit Edit
script(id="userEditTemplate", type ="text/template")
div
form(action="#")#updateUser
input(type="text", class="firstName", value='{{firstName}}')
input(type="text", class="lastName", value='{{lastName}}')
input(type="email", class="email", value='{{email}}')
input(type="number", class="phone",, value='{{phone}}')
input(type="date", class="birthday", value='{{birthday}}')
input(type="submit", value="Save").save Save
button.cancel Cancel
hr
script(type="text/javascript", src="/js/main.js")
The problem here is that you're calling the { validate: true } option to your model.set method, then you're subsequently calling model.isValid().
When you call model.set with the validate option set to true, Backbone.js will not set the properties you pass unless they all pass validation. So, by the time you call model.isValid() the model has been changed back to the previous version (before the .set call). model.isValid() automatically calls the model.validate() method and passes the current attributes of the model to it.
In your example, the values being passed to validate by the isValid method are the current (valid) attributes of your model. Because of this, isValid() is always going to validate to true. Which will cause you to never reach your else clause.
The solution here is instead of calling isValid to see if your model is valid (after passing validate: true to the set method) check the value of model.validationError. If model.validationError is a truthy value, then you know your model is invalid.
Here's a JSFiddle with an example of how to do that, with some documentation.
so I will include the error along with my main.js file, if anyone can tell me what it is refering to? I think it is the templates but I cannot figure out what I am doing wrong.
Main.js file:
(function(){
//app can be the name of the project/app
window.App = {
Models: {},
Collections: {},
Views: {},
Templates: {},
Routes: {}
};
window.template = function (id) {
return _.template($('#' + id).html());
};
//Can get rid of the Collection and views out of the names of each
//User Model
App.Models.User = Backbone.Model.extend({
defaults: {
firstName: 'J.R.',
lastName: 'Smith',
email: 'jsmith#knicks.com',
phone: '212-424-6234',
birthday: '03/05/1982',
city: 'New York'
},
location: function(){
return this.get('firstName') + ' ' + this.get('lastName') + 'is currently in ' + this.get('city') + '.';
},
validate: function(attrs) {
if(!attrs.firstName) {
return 'You must enter a real name.';
}
if(!attrs.lastName) {
return 'You must enter a real name.';
}
if(attrs.email.length < 5 ) {
return 'You must enter a real email.';
}
if(attrs.phone.length < 10 && attrs.phone === int) {
return 'You must enter a real phone number, if you did please remove the dash and spaces.';
}
if(attrs.city.length < 2 ) {
return 'You must enter a real city.';
}
},
initialize: function(){
this.on('invalid', function(model, error){
console.log(error);
//when setting a user user.set('age', -55, {validate : true}); the validate true makes sure it validates
});
}
});
// list of users
App.Collections.UsersCollection = Backbone.Collection.extend({
model: App.Models.User
});
//User View
App.Views.UserView = Backbone.View.extend({
tagName: 'li',
events: {
'click .edit': 'edit'
},
edit: function() {
},
template: template('userTemplate'),
initialize: function() {
console.log("Render");
this.render();
},
render: function() {
var template = this.template(this.model.toJSON());
this.$el.html(template);
return this;
//always return this on render methods
}
});
// view for users
App.Views.UsersView = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
},
render: function() {
this.collection.each(function(user) {
//user is the model associated to the new created user
var userView = new App.Views.UserView({model: user});
this.$el.append(userView.el);
}, this);
return this;
}
});
/*var user = new App.Collections.UsersCollection( );
var usersView = new App.Views.UsersView( users );
$( document.body ).append( usersView.render().el );
*/
})();
This is the jade file:
extends layout
block content
h1= title
p Welcome to #{title}
script(src='/js/main.js', type='text/javascript')
script(id='userTemplate', type='text/template')
<%=firstName%>
button.edit Edit
<%=lastName%>
button.edit Edit
<%=email%>
button.edit Edit
<%=phone%>
button.edit Edit
<%=birthday%>
button.edit Edit
<%=city%>
button.edit Edit
I don't think you are passing the JSON model to the template function.
Have you tried amending the code as follows:
Global templating function
window.template = function (id, model) {
return _.template($('#' + id).html(), model);
};
In your UserView
template: function() {
template('userTemplate', this.model.toJSON());
}
render: function() {
var template = this.template();
this.$el.html(template);
return this;
//always return this on render methods
}
I've just started foundational work for a Backbone JS SPA (single page application). I'm using the basic Underscore templating support, and am having issues with unexpected routing occurring.
Basically, the sign up view is shown initially as expected, POSTs succesfully when I click a button and I have it navigate to a simple test view. However, the test view is quickly rendered and then I get re-routed to the default sign up view again.
I see the history of the test page and if I hit the back button I go back to that test view which works fine. I see there is some event being triggered in Backbone which is routing me back to the blank fragment (sign up page), but I have no idea why. I've tried messing with the replace and trigger options on the navigate call with no luck.
As a side note, the start() and stop() View functions were adapted from this article: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/ . I tried removing this and it had no effect.
$(document).ready( function(){
Backbone.View.prototype.start = function() {
console.debug('starting');
if (this.model && this.modelEvents) {
_.each(this.modelEvents,
function(functionName, event) {
this.model.bind(event, this[functionName], this);
}, this);
}
console.debug('started');
return this;
};
Backbone.View.prototype.stop = function() {
console.debug('stopping');
if (this.model && this.modelEvents) {
_.each(this.modelEvents,
function(functionName, event) {
this.model.unbind(event, this[functionName]);
}, this);
}
console.debug('stopped');
return this;
};
var myApp = {};
myApp.SignUp = Backbone.Model.extend({
urlRoot:"rest/v1/user",
defaults: {
emailAddress: "email#me.com",
firstName: "First Name",
lastName: "Last Name",
password: "",
confirmPassword: ""
}
});
myApp.SignUpView = Backbone.View.extend({
el: $('#bodyTarget'),
modelEvents: {
'change' : 'render'
},
render: function(){
document.title = "Sign Up Page";
// Compile the template using underscore
var template = _.template( $("#signUpTemplate").html(), this.model.toJSON());
// Load the compiled HTML into the Backbone "el"
this.$el.html( template );
return this;
},
events: {
"click .signUpButton": "signUp"
},
signUp: function() {
bindFormValues(this);
this.model.save(this.model.attributes, {error: this.signUpFailure});
myApp.router.navigate("test", {trigger: true});
},
signUpFailure: function(model, response) {
alert("Failure: " + response);
}
});
myApp.TestView = Backbone.View.extend({
el: $('#bodyTarget'),
modelEvents: {
'change' : 'render'
},
render: function() {
document.title = "Test Page";
this.$el.html( "<div>this is a test view</div>");
return this;
}
});
// for now, just pull values from form back into model
function bindFormValues(view) {
var mod = view.model;
var el = view.$el;
var updates = {};
for (var prop in mod.attributes) {
var found = el.find('* [name="' + prop + '"]');
if (found.length > 0) {
updates[prop] = found.val();
}
}
mod.set(updates/*, {error: errorHandler}*/);
}
// routers
myApp.Router = Backbone.Router.extend({
routes: {
'test': 'test',
'': 'home',
},
home: function() {
console.debug('home:enter');
this.signUpView = new myApp.SignUpView({model: new myApp.SignUp()});
this.showView(this.signUpView);
console.debug('home:exit');
},
test: function() {
console.debug('test:enter');
this.testView = new myApp.TestView({model: new myApp.SignUp()});
this.showView(this.testView);
console.debug('test:exit');
},
showView: function(view) {
if (this.currentView) {
this.currentView.stop();
}
this.currentView = view.start().render();
}
});
myApp.router = new myApp.Router();
Backbone.history.start();
});
My HTML page just brings in the relevant scripts and has the div element bodyTarget which is injected with the views when it loads.
Edit: Duh, I found the problem. It turns out I needed to prevent event propagation on the call to signUp() by returning false.