HTML5 Zone is brought to you in partnership with:

I'm software developer and microISV, with a lot of passion to technologies.. having a fun writing code, writing blog posts and giving public speeches. For about 10 years of total experince I've been working in many branches of software development, including management and QA. I try to follow and adopt agile practices and signed up for software craftsmenship manifesto. Alexander is a DZone MVB and is not an employee of DZone and has posted 66 posts at DZone. You can read more from them at their website. View Full User Profile

Baby Steps to Backbone.js: Model Validation

11.12.2012
| 19160 views |
  • submit to reddit

Let's go back for a moment for previous post where we started to bootstrap some basic Backbone.js applications. It's very simple now, just gathering all the data and posting the data to the 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. The user then applies corrections and re-submit data.

In case of a `Feedback` model, we are interested that user always inputs 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.






Published at DZone with permission of Alexander Beletsky, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)