Wowzaaa
Wowzaaa

Reputation: 4080

backbone collection add does not trigger model validate

I am rather new to backbone and wanted to test a simple script that handles a to do list. Here is the code i used so far:

(function() {

    window.App = {
        Models: {},
        Collections: {},
        Views: {}
    };

    window.template = function(id) {
        return _.template($('#' + id).html());
    }

    App.Models.Task = Backbone.Model.extend({
        validate: function(attributes) {
            if ( !$.trim(attributes.title) ) {
                return 'Invalid title';
            }
        }
    });
    App.Collections.Tasks = Backbone.Collection.extend({
        model: App.Models.Task
    });
    App.Views.Task = Backbone.View.extend({
        tagName: 'li',
        template: template('taskTemplate'),
        initialize: function () {
            this.model.on('change', this.render, this);
            this.model.on('destroy', this.remove, this);
        },
        events: {
            'click .edit': 'editTask',
            'click .delete': 'destroy'
        },
        destroy: function() {
            if (confirm('Are you sure?')) {
                this.model.destroy();
            }
        },
        remove: function() {
            this.$el.remove();
        },
        editTask: function() {
            var newTaskTitle = prompt('New title:', this.model.get('title'));
            this.model.set('title', newTaskTitle, {validate: true});
        },
        render: function() {
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        }
    });
    App.Views.AddTask = Backbone.View.extend({
        el: 'form#addTask',
        initialize: function() {

        },
        events: {
            'submit': 'submit'
        },
        submit: function(event) {
            event.preventDefault();
            var newTaskTitle = $(event.currentTarget).find('input[type=text]').val();
            var task = new App.Models.Task({ title: newTaskTitle });
            this.collection.add(task, {add: true, merge: false, remove: false});
        }
    });
    App.Views.Tasks = Backbone.View.extend({
        tagName: 'ul',
        initialize: function() {
            this.collection.on('add', this.addOne, this);
        },
        render: function() {
            this.collection.each(this.addOne, this);
            return this;
        },
        addOne: function(task) {
            var taskView = new App.Views.Task({ model: task });
            this.$el.append(taskView.render().el);
        }
    });

    var tasks = new App.Collections.Tasks([
        {
            title: 'Go to store',
            priority: 4
        },
        {
            title: 'Go to mall',
            priority: 3
        },
        {
            title: 'Get to work',
            priority: 5
        }
    ]);
    var addTaskView = new App.Views.AddTask({ collection: tasks });
    var tasksView = new App.Views.Tasks({ collection: tasks });
    $('div.tasks').append(tasksView.render().el);
})();

So the model validation works fine ... the only pb is that collection.add does not validate the newly added model .... is the a way to force the validation?

Thanks, Rares

Upvotes: 2

Views: 4036

Answers (2)

mu is too short
mu is too short

Reputation: 434685

From the fine manual:

validate model.validate(attributes, options)

[...] By default validate is called before save, but can also be called before set if {validate:true} is passed.

Collection#add does not call save nor does it call set with the validate: true option. If you want to validate during add, say so:

collection.add(models, { validate: true });

That will get validate:true all that way down to Model#set.

A quick look at a simplified example may be helpful:

var M = Backbone.Model.extend({
    set: function() {
        console.log('setting...');
        Backbone.Model.prototype.set.apply(this, arguments);
    },
    validate: function() {
        console.log('validating...');
        return 'Never!';
    }
});
var C = Backbone.Collection.extend({
    model: M
});
var c = new C;
c.on('add', function() {
    console.log('Added: ', arguments);
});
c.on('invalid', function() {
    console.log('Error: ', arguments);
});

Now if we do this (http://jsfiddle.net/ambiguous/7NqPg/):

c.add(
    { where: 'is', pancakes: 'house?' },
    { validate: true }
);

You'll see that set is called with validate: true, validate will be called, and you'll get an error. But if you say this (http://jsfiddle.net/ambiguous/7b2mn/):

c.add(
    { where: 'is', pancakes: 'house?' },
    {add: true, merge: false, remove: false} // Your options
);

You'll see that set is called without validate: true, validate will not be called, and the model will be added to the collection.


The above behavior is quite strongly implied but not explicitly specified so you may not want to trust it. Model#initialize does say:

you can pass in the initial values of the attributes, which will be set on the model.

and set does explicitly mention the validate option. However, there is no guarantee that Collection#add will send options to the model constructor or set or that the model's constructor will send the options to set. So if you want to be really paranoid and future proof, you could add a quick check for this "options get all the way down to set" behavior to your test suite; then, if it changes you'll know about it and you can fix it.

Upvotes: 2

Rayweb_on
Rayweb_on

Reputation: 3727

if you pass options to your collection add method, the validation method will not be called and as your arguments in this case are all set to the default value, there is not need to pass them

 this.collection.add(task); 

you may want to take a look at this question. Prevent Backbone.js model from validating when first added to collection

Upvotes: 1

Related Questions