treecoder
treecoder

Reputation: 45131

Backbone: validating attributes one by one

I need to validate a form with a bunch of inputs in it. And, if an input is invalid, indicate visually in the form that a particular attribute is invalid. For this I need to validate each form element individually.

I have one model & one view representing the entire form. Now when I update an attribute:

this.model.set('name', this.$name.val())

the validate method on the model will be called.

But, in that method I am validating all the attributes, so when setting the attribute above, all others are also validated, and if any one is invalid, an error is returned. This means that even if my 'name' attribute is valid, I get errors for others.

So, how do I validate just one attribute?

I think that it is not possible to just validate one attribute via the validate() method. One solution is to not use the validate method, and instead validate every attribute on 'change' event. But then this would make a lot of change handlers. Is it the correct approach? What else can I do?

I also think that this points to a bigger issue in backbone:

Whenever you use model.set() to set an attribute on the model, your validation method is run and all attributes are validated. This seems counterintuitive as you just want that single attribute to be validated.

Upvotes: 10

Views: 12465

Answers (5)

Lizzard
Lizzard

Reputation: 193

I had to make a modification to the backbone.validation.js file, but it made this task much easier for me. I added the snippet below to the validate function.

validate: function(attrs, setOptions){
            var model = this,
                opt = _.extend({}, options, setOptions);
            if(!attrs){
                return model.validate.call(model, _.extend(getValidatedAttrs(model), model.toJSON()));
            }

            ///////////BEGIN NEW CODE SNIPPET/////////////
            if (typeof attrs === 'string') {
                var attrHolder = attrs;
                attrs = [];
                attrs[attrHolder] = model.get(attrHolder);
            }
            ///////////END NEW CODE SNIPPET///////////////

            var result = validateObject(view, model, model.validation, attrs, opt);
            model._isValid = result.isValid;

            _.defer(function() {
                model.trigger('validated', model._isValid, model, result.invalidAttrs);
                model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs);
            });

            if (!opt.forceUpdate && result.errorMessages.length > 0) {
                return result.errorMessages;
            }
        }

I could then call validation on a single attribute like so

this.model.set(attributeName, attributeValue, { silent: true });
this.model.validate(attributeName);

Upvotes: 0

Krunal
Krunal

Reputation: 81

You can also overload your model's set function with your own custom function to pass silent: true to avoid triggering validation.

set: function (key, value, options) {
    options || (options = {});
    options = _.extend(options, { silent: true });
    return Backbone.Model.prototype.set.call(this, key, value, options);
}

This basically passes {silent:true} in options and calls the Backbone.Model set function with {silent: true}. In this way, you won't have to pass {silent: true} as options everywhere, where you call this.model.set('propertyName',val, {silent:true})

For validations you can also use the Backbone.Validation plugin https://github.com/thedersen/backbone.validation

Upvotes: 0

Greg Franko
Greg Franko

Reputation: 1770

I recently created a small Backbone.js plugin, Backbone.validateAll, that will allow you to validate only the Model attributes that are currently being saved/set by passing a validateAll option.

https://github.com/gfranko/Backbone.validateAll

Upvotes: 2

nikoshr
nikoshr

Reputation: 33364

Validate is used to keep your model in a valid state, it won't let you set an invalid value unless you pass a silent:true option.

You could either set all your attributes in one go:

var M=Backbone.Model.extend({
    defaults:{
        name:"",
        count:0
    },

    validate: function(attrs) {
        var invalid=[];
        if (attrs.name==="") invalid.push("name");
        if (attrs.count===0) invalid.push("count");

        if (invalid.length>0) return invalid;
    }
});

var obj=new M();
obj.on("error",function(model,err) {
    console.log(err);
});
obj.set({
    name:"name",
    count:1
});

or validate them one by one before setting them

var M=Backbone.Model.extend({
    defaults:{
        name:"",
        count:0
    },

    validate: function(attrs) {
        var invalid=[];
        if ( (_.has(attrs,"name"))&&(attrs.name==="") )
            invalid.push("name");
        if ( (_.has(attrs,"count"))&&(attrs.count===0) )
            invalid.push("count");

        if (invalid.length>0) return invalid;
    }
});

var obj=new M();
obj.on("error",function(model,err) {
    console.log(err);
});

if (!obj.validate({name:"name"}))
    obj.set({name:"name"},{silent:true});

Upvotes: 9

Yaroslav
Yaroslav

Reputation: 4659

That is not the issue of Backbone, it doesn't force you to write validation in some way. There is no point in validation of all attributes persisted in the model, cause normally your model doesn't contain invalid attributes, cause set() doesn't change the model if validation fails, unless you pass silent option, but that is another story. However if you choose this way, validation just always pass for not changed attributes because of the point mentioned above.

You may freely choose another way: validate only attributes that are to be set (passed as an argument to validate()).

Upvotes: 1

Related Questions