Baby steps to Backbone.js: Model Validation
Let’s go back for a moment for previous post where we started to bootstrap some basic Backbone.js application. It’s very simple now, just gathering all data and posting those data to server.
Any reliable system is almost impossible without validation. If some field is required or must conform to some particular rule, it should be validated as soon as possible and validation information should be displayed to user. User then applies corrections and re-submit data.
In case of `Feedback` model, we are interested that user always input her email and feedback message. Backbone.js provides very straight forward for models validation. If model requires validation, it should implement validate method.
So, let’s extend our model with validate method.
var Feedback = Backbone.Model.extend({
url: '/feedback',
defaults: {
'email': '',
'website': '',
'feedback': ''
},
validate: function (attrs) {
if (!attrs.email) {
return 'Please fill email field.';
}
if (!attrs.feedback) {
return 'Please fill feedback field.';
}
}
});
As you can see, in case of email or feedback is missing, we just simply return string with error message.
To better understand what’s going on, let’s look on some piece of the code from Backbone.js framework. Namely, to `_validate` method of `Backbone.Model`, which is actually responsible for validation.
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
if (options && options.error) {
options.error(this, error, options);
} else {
this.trigger('error', this, error, options);
}
return false;
}
You can see, if `validate` returns either undefined or null or false, `_validate` just returns true - meaning the model is valid. Otherwise, it would check if `options.error` function initialized and call it, if not model event `error` is triggered.
During the model saving, we typically provide both success and error callbacks. It means, that error callback will be called, if model does not pass validation requirements. Inside the callback, we might decided what to do with errors. Right now, let’s just do alert.
var options = {
success: function () {
alert('Thanks for the feedback!');
},
error: function (model, error) {
alert(error);
}
};
var feedback = {
email: this.$('#email').val(),
website: this.$('#website').val(),
feedback: this.$('#feedback').val()
};
this.model.save(feedback, options);
Notice that `error` callback receiving model itself as first argument and error object (one returned from `validate` method) as second argument. Let’s try this code: leave email and feedback fields empty and press submit button.
There are several drawback of such implementation, though. First of all, `alert` windows are awful, second if user corrects email, next time she presses the submit button next alert with another message appears. This is terrible UX, so let’s fix it.
So, we should basically do 2 things: aggregate all errors during validation and apply some nice styles to errors.
Instead of returning simple strings, we’ll return an array of objects, containing name of failed and field and message.
validate: function (attrs) {
var errors = [];
if (!attrs.email) {
errors.push({name: 'email', message: 'Please fill email field.'});
}
if (!attrs.feedback) {
errors.push({name: 'feedback', message: 'Please fill feedback field.'});
}
return errors.length > 0 ? errors : false;
}
Change the `save` method options, to show errors if any error appeared and hide errors if save was successful.
var me = this;
var options = {
success: function () {
me.hideErrors();
},
error: function (model, errors) {
me.showErrors(errors);
}
};
And implement 2 simple methods:
showErrors: function(errors) {
_.each(errors, function (error) {
var controlGroup = this.$('.' + error.name);
controlGroup.addClass('error');
controlGroup.find('.help-inline').text(error.message);
}, this);
},
hideErrors: function () {
this.$('.control-group').removeClass('error');
this.$('.help-inline').text('');
}
Let’s test the code. As all fields are left empty, it will look like,
As fields are filled and form submitted, all errors are cleared from form.
Conclusions
That was very simple “baby-step” style of approaching model validation. I would could it, validation “from the box”. Even if it’s very useful there are a lot of different approaches of making even things better. The source code is availble on github.
Stay tuned for next Backbone.js baby steps soon.
Let’s go back for a moment for previous post where we started to bootstrap some basic Backbone.js application. It’s very simple now, just gathering all data and posting those data to server.
Any reliable system is almost impossible without validation. If some field is required or must conform to some particular rule, it should be validated as soon as possible and validation information should be displayed to user. User then applies corrections and re-submit data.
In case of `Feedback` model, we are interested that user always input her email and feedback message. Backbone.js provides very straight forward for models validation. If model requires validation, it should implement validate method.
So, let’s extend our model with validate method.
var Feedback = Backbone.Model.extend({ url: '/feedback', defaults: { 'email': '', 'website': '', 'feedback': '' }, validate: function (attrs) { if (!attrs.email) { return 'Please fill email field.'; } if (!attrs.feedback) { return 'Please fill feedback field.'; } } });
As you can see, in case of email or feedback is missing, we just simply return string with error message.
To better understand what’s going on, let’s look on some piece of the code from Backbone.js framework. Namely, to `_validate` method of `Backbone.Model`, which is actually responsible for validation.
_validate: function(attrs, options) { if (options.silent || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validate(attrs, options); if (!error) return true; if (options && options.error) { options.error(this, error, options); } else { this.trigger('error', this, error, options); } return false; }
You can see, if `validate` returns either undefined or null or false, `_validate` just returns true - meaning the model is valid. Otherwise, it would check if `options.error` function initialized and call it, if not model event `error` is triggered.
During the model saving, we typically provide both success and error callbacks. It means, that error callback will be called, if model does not pass validation requirements. Inside the callback, we might decided what to do with errors. Right now, let’s just do alert.
var options = { success: function () { alert('Thanks for the feedback!'); }, error: function (model, error) { alert(error); } }; var feedback = { email: this.$('#email').val(), website: this.$('#website').val(), feedback: this.$('#feedback').val() }; this.model.save(feedback, options);
Notice that `error` callback receiving model itself as first argument and error object (one returned from `validate` method) as second argument. Let’s try this code: leave email and feedback fields empty and press submit button.
There are several drawback of such implementation, though. First of all, `alert` windows are awful, second if user corrects email, next time she presses the submit button next alert with another message appears. This is terrible UX, so let’s fix it.
So, we should basically do 2 things: aggregate all errors during validation and apply some nice styles to errors.
Instead of returning simple strings, we’ll return an array of objects, containing name of failed and field and message.
validate: function (attrs) { var errors = []; if (!attrs.email) { errors.push({name: 'email', message: 'Please fill email field.'}); } if (!attrs.feedback) { errors.push({name: 'feedback', message: 'Please fill feedback field.'}); } return errors.length > 0 ? errors : false; }
Change the `save` method options, to show errors if any error appeared and hide errors if save was successful.
var me = this; var options = { success: function () { me.hideErrors(); }, error: function (model, errors) { me.showErrors(errors); } };
And implement 2 simple methods:
showErrors: function(errors) { _.each(errors, function (error) { var controlGroup = this.$('.' + error.name); controlGroup.addClass('error'); controlGroup.find('.help-inline').text(error.message); }, this); }, hideErrors: function () { this.$('.control-group').removeClass('error'); this.$('.help-inline').text(''); }
Let’s test the code. As all fields are left empty, it will look like,
As fields are filled and form submitted, all errors are cleared from form.
Conclusions
That was very simple “baby-step” style of approaching model validation. I would could it, validation “from the box”. Even if it’s very useful there are a lot of different approaches of making even things better. The source code is availble on github.
Stay tuned for next Backbone.js baby steps soon.