Why won't validate fire the error as "fred" should make the validate condition return true when it is set?
Person = Backbone.Model.extend({
initialize: function () {
console.log('inisialize Person');
this.bind("change:name", function () {
console.log(this.get('name') + ' is now the name value')
});
this.bind("error", function (model, error) {
console.log(error);
});
},
defaults: {
name: '',
height: ''
},
validate: function (attributes, options) {
if (attributes.name == "fred") { //why wont this work?
return "oh no fred is not allowed";
}
}
});
//var person = new Person({ name: 'joe', height: '6 feet' });
var person = new Person();
person.set({ name: 'fred', height: '200' });
Your validate() is called when saving, but not when setting an attribute unless you explicitly tell it to do so. From the docs:
By default validate is called before save, but can also be called
before set if {validate:true} is passed.
Try this: In the initialize(), change the this.bind('error') to this.on('invalid') The 'error' event is for failures at the server, after when calling save(). The 'invalid' is for validation errors on the client side. Lastly, add {validate: true} as a second argument to the person.set() calls. Backbone doesn't validate set() by default.
Person = Backbone.Model.extend({
defaults: {
name: '',
height: ''
},
validate: function(attributes) {
if(attributes.name === 'fred' )
return 'oh no fred is not allowed';
},
initialize: function() {
alert('welcome');
this.on('invalid', function(model, error){
alert(error);
});
//listeners
this.on('change:name', function(model) {
var name = model.get('name');
alert('changed ' + name);
});
});
var person = new Person();
person.set({ name: 'fred', height: '200' }, {validate: true}); //oh no fred is not allowed
Related
I have Uncaught Type Error : UserRegisterView is not a constructor.I dont understand this error.I looked all code but i dont find it.
Sorry of my bad english.Please help me
Thanks for answer
UPDATED
UserRegisterView is here
var UserRegisterView = Backbone.View.extend({
model: User,
el: '#form',
events: {
'click input[id="infoWeek"]': 'infoWeek',
'click input[id="infoMonth"]': 'infoMonth'
},
infoWeek: function() {
this.$el.find("#dayOfMonth").hide();
this.render();
},
infoMonth: function() {
this.$el.find("#dayOfWeek").hide();
this.render();
}
});
var AddUserView = Backbone.View.extend({
el: $(".page"),
events: {
'click #saveUser': 'saveUser'
},
saveUser: function() {
var user = new User();
user.set({
username: $("#username").val(),
lastName: $("#lastName").val(),
regNumber: $("#regNumber").val(),
password: $("#password").val(),
departmentName: $("#departmentName").val(),
email: $("#email").val(),
role: $("#role").val()
});
user.save();
if (document.getElementById('isOpen').checked) {
user.set("isOpen", $("#isOpen").val("1"));
user.save();
} else {
user.set("isOpen", $("#isOpen").val("0"));
user.save();
}
if (document.getElementById('dayOfWeek').checked) {
user.set("dayOfWeek", $("#dayOfWeek").val());
user.save();
} else if (document.getElementById('dayOfMonth').checked) {
user.set("dayOfMonth", $("#dayOfMonth").val());
user.save();
}
$("#username").val("");
$("#firstName").val("");
$("#lastName").val("");
$("#regNumber").val("");
$("#password").val("");
$("#deparmentName").val("");
$("#email").val("");
$("#isOpen").val("");
$("#dayOfWeek").val("");
$("#dayOfMonth").val("");
},
render: function() {
var that = this;
var template = Handlebars.compile(UserRegister);
var myHtml = template(that.model.toJSON());
that.$el.html(myHtml);
return this;
}
});
return {
AddUserView: AddUserView,
UserRegisterView: UserRegisterView
};
});
router user func.
define([
'jquery',
'underscore',
'backbone',
'handlebars',
'spin',
'app/models/LoginModel',
'app/views/LoginView',
'app/views/UserRegisterView'
], function($,
_,
Backbone,
Handlebars,
Spinner,
Login,
LoginView,
UserRegisterView
) {
var Router = Backbone.Router.extend({
routes: {
'search': 'search',
'login': 'login',
'travels': 'travels',
'user': 'user',
'menu': 'menu',
'': 'home'
},
user: function() {
disposeView(new UserRegisterView().render());
}
dispose.view on util.js
function disposeView(view) {
Backbone.View.prototype.close = function() {
this.unbind();
this.undelegateEvents();
};
/* Şu anki viewi yok et */
if (this.currentView !== undefined) {
this.currentView.close();
}
/* Yeni view oluştur. */
this.currentView = view;
this.currentView.delegateEvents();
return this.currentView;
}
What's happening
Your UserRegisterView module returns an object which contains two constructors.
return {
AddUserView: AddUserView,
UserRegisterView: UserRegisterView
};
When using this module, what you're getting is the object above.
define([
// ...
'app/views/UserRegisterView'
], function(
// ...
UserRegisterView // value of the return in the module
) {
So you're kind of misleading yourself by calling it UserRegisterView as it's not the constructor, but the object containing the constructor.
To get a new UserRegisterView view instance with the current way your module is setup, you'd need to call it like so:
var userView = new UserRegisterView.UserRegisterView();
Or to create a AddUserView instance:
var addView = new UserRegisterView.AddUserView();
Solutions
Split up the module, one for each view constructor.
Change the name so at least it's not misleading (like UserViewsModule)
Other improvements
That being said, there are other improvements that could be made to your Backbone code.
var UserRegisterView = Backbone.View.extend({
// that's useless (if not used) and not a view property.
// model: User,
// don't use `el` like that, especially when using the view as a shared Constructor
el: '#form',
events: {
'click input[id="infoWeek"]': 'onInfoWeekClick',
'click input[id="infoMonth"]': 'onInfoMonthClick'
},
initialize: function() {
// Cache jQuery object of the view's element
this.$dayOfMonth = this.$("#dayOfMonth");
this.$dayOfMonth = this.$("#dayOfMonth");
// also use the shortcut function instead of `this.$el.find()`
}
onInfoWeekClick: function(e) {
this.$dayOfMonth.hide();
// calling render here is useless unless your using it as a parent
// view, where the child view overrides the render function.
},
onInfoMonthClick: function(e) {
this.$dayOfMonth.hide();
}
});
The disposeView function could be simplified:
function disposeView(view) {
var current = this.currentView;
if (current) current.close();
current = this.currentView = view;
current.delegateEvents();
return current;
}
Don't change the default Backbone view prototype each time the function is called. Instead, add the function once.
_.extend(Backbone.View.prototype, {
close: function() {
this.unbind();
this.undelegateEvents();
},
// any other function you want to add can go here.
});
In another answer, I go into details on how to extend Backbone's core classes with requirejs transparently.
You're already using jQuery, so don't use JavaScript DOM API document.getElementById('isOpen') interspersed with jQuery selectors $('#isOpen').
I made some improvements to the following view. Take the time to create yourself some utility functions (like reset and getValues) to simplify the flow of the code and encapsulate the complexity.
var AddUserView = Backbone.View.extend({
el: $(".page"),
events: {
'click #saveUser': 'saveUser'
},
// compile the template once while creating the view class
template: Handlebars.compile(UserRegister),
// get the selector string out of the code and place them in one place
// easy to change and maintain.
fields: {
username: "#username",
firstName: "#firstName",
lastName: "#lastName",
regNumber: "#regNumber",
password: "#password",
deparmentName: "#deparmentName",
email: "#email",
isOpen: "#isOpen",
dayOfWeek: "#dayOfWeek",
dayOfMonth: "#dayOfMonth",
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
// cache jQuery object of every field once after a render
this.field = _.reduce(this.fields, function(fields, selector, key) {
fields['$' + key] = this.$(selector);
return fields;
}, {}, this);
return this;
},
reset: function() {
// reset all the fields once without repeating code.
_.each(this.field, function($field) {
$field.val("");
});
return this;
},
getValues: function(keys) {
// get the value of multiple fields returned in a nice object
// ready to be sent to a Backbone model.
return _.reduce(keys, function(data, key) {
data[key] = this.field[key].val();
return data;
}, {}, this);
},
saveUser: function() {
var field = this.field,
user = new User(this.getValues([
'username',
'lastName',
'regNumber',
'password',
'departmentName',
'email',
'role',
]));
user.set({ isOpen: field.$isOpen.is(':checked') });
if (field.$dayOfWeek.is(':checked')) {
user.set("dayOfWeek", field.$dayOfWeek.val());
} else if (field.$dayOfMonth.is(':checked')) {
user.set("dayOfMonth", field.$dayOfMonth.val());
}
user.save();
this.reset();
},
});
In the following snippet, you're putting the context (this) into a local variable. I see that a lot and I could say that 90% of the times I see it on Stack Overflow questions, it makes no sense. It clearly screams copy-pasted.
render: function() {
var that = this;
// ...
that.$el.html(myHtml);
return this;
}
Please tell me you see that you're putting this into that, then using that throughout the function, then you still return this?!
Putting the context into a local variable is useful when the object is needed in a dynamically created callback.
render: function() {
var that = this; // this is available here
setTimeout(function() {
// here this is not available.
that.handleCallback();
}, 10);
// here we are in the same context as the first line.
return this;
}
I have a model as
var Info = Backbone.Model.extend({
url: '',
defaults: {
username: '',
email: '',
error: ''
},
initialize: function(){
console.log('Obj created');
},
validate: function(attr){
if(!attr.username){
attr.error = 'Username cannot be empty';
}
}
});
View
var model = new Info();
var View1 = Backbone.View.extend({
model: model,
initialize: function(){
this.render();
},
render: function(){
var template = _.template($('#App1').html());
this.$el.html(template);
},
'events': {
'click #btn1': 'submit'
},
submit: function(){
var obj = {
username: $('#username').val(),
email: $('#pwd').val)
};
this.model.save();
}
});
Here I want to set model.set({error: 'Message goes here'}) during validate event.
However, validate only returns true / false. Is there a way we can set the value in the validate method.
You can set an error property inside validate() function:
this.set('error', 'Message goes here')
Once set, error message will be accessible via this.model.get('error') from within view object code.
I am validating model's attribute (Name), in order to make sure, customer have to input their name in the register form.
View :
define(["jquery" ,
"underscore" ,
"backbone" ,
"text!templates/CustomerTemplate.html",
"models/Customer"
],function($ , _ , Backbone, CustomerTemplate, CustomerModel){
var CustomerView = Backbone.View.extend({
initialize : function(){
this.listenTo(this.model, 'change', this.render);
},
events : {
'submit #customerForm' : 'Customer'
},
Customer : function(e){
e.preventDefault()
var _customer = new CustomerModel({
UID: "00000000-0000-0000-0000-000000000000",
Sex: 0,
Name: $("#name").val(),
});
this.model.save(_customer,{validate: true},{
wait:true,
success:function(model, response) {
console.log('Successfully saved!');
},
error: function(model, error) {
console.log(model.toJSON());
console.log('error.responseText');
}
});
},
render : function(){
var customertemplate = _.template(CustomerTemplate);
this.$el.html(customertemplate(this.model.toJSON()));
return this;
}
});
return CustomerView;
});
Model:
define(["underscore" , "backbone"],function(_ , Backbone){
var CustomerModel = Backbone.Model.extend({
urlRoot: "myurl",
initialize : function(){
this.bind('invalid', function(model, error) {
console.log(error);
});
},
validate: function (attrs){
if ( !attrs.Name) {
return 'You must provide a name';
}
},
defaults : {
UID: "00000000-0000-0000-0000-000000000000",
Sex: 0,
Name: "",
}
});
return CustomerModel;
});
Problem : Even the attribute Name is not null, the error message in validate method still appears (You must provide a name).
Any idea what could be causing this is appreciate. Thanks.
When you call this.model.save in your CustomerView, you're passing it a new Customer model you instantiated in the previous statement. This isn't quite what you want; you either want to call _customer.save() to save the brand new model, or - more likely - you want to pass your new attributes to the existing model, and save that:
var newAttrs = {
UID: "00000000-0000-0000-0000-000000000000",
Sex: 0,
Name: $("#name").val(),
};
this.model.save(newAttrs);
When you call this.model.save(_customer, {validate: true}) in your existing code, that Customer model get passed to your validate() function. And that model doesn't have a Name attribute. It does have a Name property - you can access it via _customer.get('Name') - but you should follow the Backbone convention and presume that your validate method is getting a 'simple' JavaScript object, not a Backbone model.
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());
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.