Validating model data

Usually, on frontend applications, the inputs are validated with UI plugins such as jQuery Validation, which focuses on the user interface. In other words, the data is validated directly on the DOM. However, on bigger applications that would not be the best approach.

Validations in Backbone can be done manually or through plugins. Of course, the best approach is to use a plugin because it saves time and effort, but before we learn how to use the backbone.validation plugin, I want to show you how native validation works.

Manual validation

Backbone models have three properties to help us validate the model data: validate(), validationError(), and isValid(). The validate()method should return nothing if the model data is correct, or a value otherwise.

Backbone leaves open what should be returned by the validate() method, so you can return just a plain string message or a complex object:

class Chapter extends Backbone.Model{
  validate(attrs, options) {
    if (attrs.end < attrs.start) {
      return "can't end before it starts";
    }
  }
}

You can call the isValid() method to ensure that your model has a valid state; internally, Backbone will call the validate() method and will return a Boolean value depending on the returned value: true if validate() returns nothing, false if it returns something.

With validationError, you can get the latest validation error in the model—for example:

var one = new Chapter({
  title : "Chapter One: The Beginning",
  start: 15,
  end: 10
});

If (!one.isValid()) {
  alert(one.validationError);
}

The validate() method is called by Backbone when you want to save the model, and will trigger an 'invalid' event if the model is not valid:

one.on("invalid", function(model, error) {
  alert(model.get("title") + " " + error);
});

one.save({
  start: 15,
  end: 10
});

In the contacts editor, we are not validating anything. It's time to start with some validations; let's validate the name of the contact:

// apps/contacts/models/contact.js
var Contact extends Backbone.Model {
  validate(attrs) {
    if(_.isEmpty(attrs.name)) {
      return {
        attr: 'name',
        message: 'name is required'
      };
    }
  }
}

var contact = new Contact({
// ...
});

Remember, the validate() method can return anything. Backbone will assume that the model is valid only if validate() does not return something. In this case, an object is returned. Objects are more useful that plain string, because return more information that can be used for a better user experience.

When an error occurs an 'invalid' event will be triggered. The editor form should display the error, so that the form will be listening for the 'invalid' event in the model, and managing the DOM to display error messages:

// apps/contacts/contactEditor.js
class ContactForm extends Layout {
  constructor(options) {
    super(options);
    this.template = '#contact-form';
    this.regions = {
      phones: '.phone-list-container',
      emails: '.email-list-container'
    };

    this.listenTo(this.model, 'invalid', this.showError);
  }

// ...

  showError(model, error) {
    this.clearErrors();

    var selector = '#' + error.attr;
    var $msg = $('<span>')
      .addClass('error')
      .addClass('help-block')
      .html(error.message);
    this.$(selector)
      .closest('.form-group')
      .addClass('has-error');
    this.$(selector)
      .after($msg);
  }

  clearErrors() {
    this.$('.has-error').removeClass('has-error');
    this.$('span.error').remove();
  }
}

The showError() method appends a span message below the input box, so the user can see what's wrong. With the attr property in the error object, we can put the error message in the right box; that's why using an error object is better than plain-text messages.

Note that we are creating a DOM element in the showError() method. I'm creating the element dynamically to simplify the code in the view. Of course, you can create a span element directly in the template too and show/hide it as needed.

Validating with the Backbone.Validation plugin

Backbone.Validation simplifies the validation process, allowing us to write validation rules in a declarative way instead of programmatically. Also, it comes with built-in validation rules that you can use out-of-the-box. When using Backbone.Validation, the way you validate models is simplified, as we will show next.

To start with Backbone.Validation, install the plugin after including Backbone.

<script src="js/vendor/backbone.js"></script>
<script src="js/vendor/backbone-validation.js"></script>

Now we can use the plugin; Backbone.Validation uses a validation property in the models to specify validation rules:

class Contactextends Backbone.Model {
get validation: {
    name: {
      required: true,
      minLength: 3
    }
  }
}

Instead of using the validate() method, you can write the validation rules in a configuration object, where the keys of the object are the name of the fields in the model; in this case, we are validating the name field. The required and minLength validation rules are applied to the name field by Backbone.Validation.

Now that the Contact model has the validation configuration, we need to override the default validate() method in the Backbone model to activate the Backbone.Validation plugin. To do it, we need to call the Backbone.Validation.bind() method in the onRender() method:

class ContactForm extends Layout {
// ...

  onRender() {
    Backbone.Validation.bind(this);
  }

  // …
});

The showError() and clearErrors()are now unnecessary because we override the validate() method on the model. The Backbone.Validation plugin provides hooks to tell you when a model is valid; we will use these hooks as a shortcut. For now, the save:contact handler should change to invoke the isValid() method:

formLayout.on('save:contact', function() {
  if (!contact.isValid(true)) {
    return;
  }
  contact.unset('phones', { silent: true });
  contact.set('phones', phoneCollection.toJSON());
});

Note the true argument in the isValid() method; this argument should be used to validate all the model attributes.

When Backbone.Validation detects that a field is invalid, it will try to show an error message in the form; however, the default behavior is based on the name attribute in the form inputs. You can change the default behavior to show errors properly in our layout:

// app.js
_.extend(Backbone.Validation.callbacks, {
  valid(view, attr) {
    var $el = view.$('#' + attr);
    if ($el.length === 0) {
      $el = view.$('[name~=' + attr + ']');
    }

    // If input is inside an input group, $el is changed to
    // remove error properly
    if ($el.parent().hasClass('input-group')) {
      $el = $el.parent();
    }

    var $group = $el.closest('.form-group');
    $group.removeClass('has-error')
      .addClass('has-success');

    var $helpBlock = $el.next('.help-block');
    if ($helpBlock.length === 0) {
      $helpBlock = $el.children('.help-block');
    }
    $helpBlock.slideUp({
      done: function() {
        $helpBlock.remove();
      }
    });
  },

  invalid(view, attr, error) {
    var $el = view.$('#' + attr);
    if ($el.length === 0) {
      $el = view.$('[name~=' + attr + ']');
    }

    $el.focus();

    var $group = $el.closest('.form-group');
    $group.removeClass('has-success')
      .addClass('has-error');

    // If input is inside an input group $el is changed to
    // place error properly
    if ($el.parent().hasClass('input-group')) {
      $el = $el.parent();
    }

    // If error already exists and its message is different to new
    // error's message then the previous one is replaced,
    // otherwise new error is shown with a slide down animation
    if ($el.next('.help-block').length !== 0) {
      $el.next('.help-block')[0].innerText = error;
    } else if ($el.children('.help-block').length !== 0) {
      $el.children('.help-block')[0].innerText = error;
    } else {
      var $error = $('<div>')
                 .addClass('help-block')
                 .html(error)
                 .hide();

      // Placing error
      if ($el.prop('tagName') === 'div' &&
 !$el.hasClass('input-group')) {
        $el.append($error);
      } else {
        $el.after($error);
      }

      // Showing animation on error message
      $error.slideDown();
    }
  }
});

The invalid() method will be called when invalid data is found; the callback is called with an instance of the view, the field name that has the invalid data, and a message. With that information, we can create a span error message and add the has-error class to the control-group that contains the input.

Please consult the Backbone.Validation documentation to learn more about its advantages and usage.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.32.222