Backbone-validator

Backbone model-view validator

View the Project on GitHub fantactuka/backbone-validator

backbone-validator Build Status

Backbone model validator allows you to define validation rules for model and utilize it for model-standalone validation or bind its events to the view so you can display errors if needed. Inspired by @thedersen's backbone-validation

Installation

Using Bower bower install backbone-validator or just copy backbone-validator.js

Examples

Usage

Model

var User = Backbone.Model.extend({
  validation: {
    name: {
      blank: false,
      message: 'Name is required'
    },

    email: {
      required: true,
      format: 'email',
      message: 'Does not match format'
    },

    books: {
      collection: true
    },

    address: {
      model: true
    },

    phone: [{
      format: 'number',
      message: 'Does not match format'
    }, {
      maxLength: 15,
      message: function(attrName, attrValue, attrExpectation, validatorName) {
        return 'Passed ' + attrName ' is too long. It is expected to be shorter than ' + attrExpectation + ' chars';
      }
    }]
  }
});

var user = new User();

Setting attributes

user.set({ email: 'wrong_format_email', phone: 'wrong_format_and_also_very_long' }, { validate: true }); 
// Attributes won't be set, since validation failed. Validation errors are stored

user.set({ email: 'wrong_format_email', phone: 'wrong_format_and_also_very_long' }, { validate: true, suppress: true }); 
// Attributes will be set, but model will trigger its validation events, and store validation errors as for previous case

user.validationError; // => { email: ['Does not match format'], phone: ['Does not match format', 'Too long'] };

Saving model

user.save(); 
// Validation triggered automatically. If nothing passed, it will validate entire model.

user.save({ email: 'user@example.com' }); 
// Validation triggered automatically. Validates only email.

Checking model validity

// Model#isValidreturns boolean depending on model validity
user.isValid();                   // Will check all attributes
user.isValid(['email', 'name']);  // Will check specific attributes
user.isValid('email');            // Will check specific attribute

// Model#validate returns null if model is valid (no errors), or errors object if any validation failed
user.validate();                   // Will check all attributes
user.validate(['email', 'name']);  // Will check specific attributes
user.validate('email');            // Will check specific attribute

Triggering validation events manually

Let's say you've sent data to the back-end and it returned server-validation errors (e.g. that email is already taken) and you want to display these errors on UI. It could be done by calling triggerValidated method that will trigger validation events, listened by the view and it will display errors:

user.save(null, {
  success: function(response) {
    if (response.errors) {
      // Errors object should have same format as Backbone.Validator errors:
      // { <attribute>: [<error>, <error>, <error>] }
      //
      // First argument is a list of attributes that will be triggered, second one - errors object
      user.triggerValidated(_.keys(response.errors), response.errors);
    }
  }
});

Another case is when you want to reset validation on the form, and hide any validation error message, then you can run triggerValidated without any params, which will indicate (for the view) that none of model attributes is invalid:

user.clear();               // Unset attributes
user.triggerValidated();    // Trigger `validated` events for all attributes passing no errors into it

Runtime configuration

In some cases you might want to configure validation in a runtime so it dependant on model's state/fields. You can do it by specifying it as a function, returning validation object:

var User = Backbone.Model.extend({
  validation: function() {
    return {
      name: {
        required: true
      },

      company: {
        required: !this.isEmployee()
      }
    }
  },

  isEmployee: function() {
    ...
  }
});

Errors post-processing

In some cases you might need to re-format errors, e.g. flatten keys of nested models' errors, etc. In this case you can do it globally for all models via implementing custom Backbone.Validator.ModelCallbacks.processErrors, or you can override it for particular model via options passed to validate() and isValid() methods. E.g.

model.validate(null, { 
  processErrors: function(errors) { 
    return flatten(errors); 
  } 
});

// or any other method that calls #validate internaly

model.set(newAttrs, { 
  processErrors: function(errors) { 
    return flatten(errors); 
  } 
});

Error messages generator

When using any internationalization you might want to have globaly defined error messages generator, that will translate errors into current locale in a runtime. In this case you can specify Backbone.Validator.createMessage method that will take precedence over default error messages:

Backbone.Validator.createMessage = function(attrName, attrValue, validatorExpectation, validatorName) {
  return i18n.translate(attrName + '.' + validatorName, { value: attrValue, expectation: validatorExpectation });
};

View

var UserView = Backbone.View.extend({
  initialize: function() {
    ...
    this.model = new User();
    this.bindValidation();
  },

  onValidField: function(attrName, attrValue, model) {
    // Triggered for each valid attribute
  },

  onInvalidField: function(attrName, attrValue, errors, model) {
    // Triggered for each invalid attribute.
  }
});

Note that onValidField and onInvalidField methods are optional for the view. By default it's taken from Backbone.Validator.ViewCallbacks. So you can override those defaults:

Validator.ViewCallbacks = {
  onValidField: function(name /*, value, model*/) {
    var input = this.$('input[name="' + name + '"]');
    input.next('.error-text').remove();
  },

  onInvalidField: function(name, value, errors /*, model*/) {
    var input = this.$('input[name="' + name + '"]');
    input.next('.error-text').remove();
    input.after('<div class="error-text">' + errors.join(', ') + '</div>');
  }
};

These methods could be also passed as option to bindValidation method:

bindValidation(this.model, {
  onValidField: function() { ... },
  onInvalidField: function() { ... }
});

Built-in validators

Please note: string validators (format, minLength, maxLength) does not require field to exist. E.g. phone number could be optional, but should match format if it is not empty. So in case you need to check field existance as well - use required validator, otherwise empty string (undefined, null, false) will pass the validation.

Usage examples:

var User = Backbone.Model.extend({
  validation: {
    name: {
      required: true,
      blank: false,
      minLength: 2,
      maxLength: 20,
      fn: function(value) {
        return ~valie.indexOf('a') ? 'Name should have at least one "a" letter' : true;
      }
    },

    phone: {
      format: 'number'
    },

    documents: {
      collection: true
    }
  }
});

Adding validator

Backbone.Validator.add('myCustomValidator', function(value, expectation) {
  return value * value === expectation;
}, 'Default error message');

Validation method could return true/false as well as error message or array of messages which will be treated as validation failure:

Backbone.Validator.add('myCustomValidator', function(value, expectation) {
  return value === expectation ? true : 'Value does not match expectation. Should be ' + expectation;
});

Standalone validator

In fact you can utilize validator for plain objects, so you can do something like this:

var validations = {
  name: {
    blank: false,
    message: 'Name is required'
  },

  email: {
    blank: false,
    format: 'email',
    message: 'Does not match format'
  }
};

Backbone.Validator.validate({ name: '', email: '' }, validations); // -> { name: ['Name is required'], email: ['Does not match format'] }

Contributing