styler
styler

Reputation: 16501

Selectively validate model attributes using Backbone Validation plugin?

I'm using the Backbone Validation plugin and would like to selectively validate different sets of model attributes at different times, is this possible?

My test model has validation for name, age and address and if I wanted to validate only address, how would I do this?

I thought it was a case of calling this.model.validate('address'); but all the validation rules seem to run?

JS

    console.clear();


const Model = Backbone.Model.extend({
    validation: {
    name: {
        required: true
    },
    age: {
      range: [1, 80],
      required: true
    },
    address: {
        minLength: 1,
        msg: 'Address required',
      required: false
    }
  }
});
const View = Backbone.View.extend({

    template: Handlebars.compile( $('.tmpl-person').html() ),

    className: 'view',

    events: {
        'click .js-action--validate-keys': 'actionValidateKeysClicked',
        'click .js-action--validate-key': 'actionValidateKeyClicked'
    },

    initialize() {
        Backbone.Validation.bind(this);

        this.listenTo(this.model, 'validated', this.onModelValidated, this);
        this.listenTo(this.model, 'validated:valid', this.onModelValid, this);
        this.listenTo(this.model, 'validated:invalid', this.onModelInvalid, this);
    },

    render() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },

    actionValidateKeysClicked(event) {
        event.preventDefault();
        console.log('actionValidateKeysClicked');

        this.model.set({
            'name': 'Joe Bloggs',
            'age': 23
        });

        this.model.validate(['name', 'age']);
    },

    actionValidateKeyClicked(event) {
        event.preventDefault();
        console.log('actionValidateKeyClicked');
        this.model.set('address', '123 ABC');
        this.model.validate('address');
    },

  /**
   *    The validated event is triggered after validation is performed, either it was successful or not.
   *    isValid is true or false depending on the result of the validation.
   */
  onModelValidated(isValid, model, errors) {
    console.log('onModelValidated', isValid, model, errors);
  },

  onModelValid(model) {
    console.log('onModelValid', model);
  },

  onModelInvalid(model, errors) {
    console.log('onModelInvalid', model, errors);
  }
});

const newModel = new Model();
const newView = new View({
    model: newModel
});


document.body.append(newView.render().el);

JSfiddle http://jsfiddle.net/kyllle/qmx2y9yr/

Upvotes: 1

Views: 775

Answers (1)

Emile Bergeron
Emile Bergeron

Reputation: 17430

isValid

To validate only certain fields, Backbone.Validation offers a modified version of isValid.

If you pass the name of an attribute or an array of names, you can check whether or not the attributes are valid:

// Check if name is valid
var isValid = model.isValid('name');

// Check if name and age are valid
var isValid = model.isValid(['name', 'age']);

isValid will trigger an 'invalid' event (source) only if there's an error with the validation, and won't trigger a 'validated' event or a 'validated:valid'.

preValidate

Sometimes it can be useful to check (for instance on each key press) if the input is valid - without changing the model - to perform some sort of live validation. You can execute the set of validators for an attribute, or a hash of attributes, by calling the preValidate method and pass it the name of the attribute and the value to validate, or a hash of attributes.

If the value is not valid, the error message is returned (truthy), otherwise it returns a falsy value.

// Validate one attribute
// The `errorsMessage` returned is a string
var errorMessage = model.preValidate('attributeName', 'Value');

// Validate a hash of attributes
// The errors object returned is a key/value pair of attribute name/error, e.g
// {
//   name: 'Name is required',
//   email: 'Email must be a valid email'
// }
var errors = model.preValidate({name: 'value', email: '[email protected]'});

Demo

I took your code and made a small example below to demonstrate that the invalid event is not triggered when using isValid, but it is trigger when using validate. Also, note that no events are triggered with preValidate.

const Model = Backbone.Model.extend({
  validation: {
    name: {
      required: true
    },
    age: {
      range: [1, 80],
      required: true
    },
    address: {
      minLength: 1,
      msg: 'Address required',
      required: true
    }
  }
});

const View = Backbone.View.extend({

  template: $('.tmpl-person').html(),

  className: 'view',

  events: {
    'click .validate': 'onValidateClick',
    'click .is-valid': 'onIsValidClick',
    'click .pre-validate': 'onPreValidateClick',
  },

  initialize() {
    Backbone.Validation.bind(this);

    this.listenTo(this.model, {
      'all': this.onAllModelEvent,
      //  'validated': this.onModelValidated,
      //  'validated:valid': this.onModelValid,
      //  'validated:invalid': this.onModelInvalid
    });

  },

  render() {
    this.$el.html(this.template);
    this.$checkbox = this.$('.invalid');
    return this;
  },

  getAddress() {
 
    return this.$checkbox.is(':checked') ? null : '123 ABC';
  },

  onValidateClick() {
    console.log('validate click');
    this.model.set('address', this.getAddress());
    console.log("validate:", this.model.validate('address'));
  },

  onIsValidClick() {
    console.log('isValid click');
    this.model.set('address', this.getAddress());
    console.log("isValid:", this.model.isValid('address'));
  },

  onPreValidateClick() {
    console.log('preValidate click');
    this.model.set('address', this.getAddress());

    console.log("preValidate:", this.model.preValidate('address'));
  },

  onAllModelEvent: function(event) {
    console.log("event:", event);
  }

});

const newModel = new Model();
const newView = new View({
  model: newModel
});


document.body.append(newView.render().el);
* {
  -webkit-font-smoothing: antialiased;
}
body {
  padding: 5%;
  font-size: 16px;
  line-height: 1.4;
}
.view {
  width: 100%;
  height: 100vh;
  background: lightBlue;
}
.btn {
  outline: none;
  border: none;
  background: #1C90F3;
  border-radius: 4px;
  color: white;
  padding: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.validation/0.11.5/backbone-validation-min.js"></script>
<script type="text/template" class="tmpl-person">
  Set 'address' and use:
  <button type="button" class="btn validate">validate</button>
  <button type="button" class="btn is-valid">isValid</button>
  <button type="button" class="btn pre-validate">preValidate</button>
  <br>
  <input type="checkbox" class="invalid">make address invalid.
</script>

Additional information


Side note

Notice how I used listenTo with an object instead of calling it 3 times.

this.listenTo(this.model, {
    'validated': this.onModelValidated,
    'validated:valid': this.onModelValid,
    'validated:invalid': this.onModelInvalid
});

Also, there's no this argument to pass to listenTo, It's probably a confusion with the old on/bind syntax.

Upvotes: 1

Related Questions