2
votes

I'm trying to validate an entry in a list to be unique from all other entries in the list using ko.validation, but I'm having issues with validation running when it shouldn't.

I have an editable list (a ko.observableArray), and each item in that array is a view model with a ko.observable on it:

var vm = function (data) {
    var self = this;

    self.items = ko.observableArray();

    _.each(data.words, function (word) {
        self.items.push(new listItemVm({parent: self, word: word.word}));
    });
};

var listItemVm = function (data) {
    var self = this;
    self.parent = data.parent;
    self.word = ko.observable(data.word);
};

Then I add some validation to listItemVm.word ko.observable. I want each one to be unique:

var listItemVm = function (data) {
    var self = this;
    self.parent = data.parent;
    self.word = ko.observable(data.word).extend({
        validation: {
            validator: function (name, params) {
                console.log("validating " + name);
                // word we are editing must be different from all other words

                // uncommenting this next line causes the behaviour
                // I would expect because params.parent.items() 
                // is not called
                //return true;

                var allWords = params.parent.items();

                // exclude current view model we are editing
                var otherWordViewModels = _.filter(allWords, function (row) {
                    return row !== params.currentRow;
                });

                var otherWords = _.map(otherWordViewModels, function (item) {
                    return item.word();
                });

                return !_.contains(otherWords, name);
            },
            message: 'Must be unique',
            params: {
                currentRow: self,
                parent: self.parent
            }
        }
    });
};

I give it some data, and wrap it in some HTML: http://jsfiddle.net/9kw75/3/

Now, this does work - the validation runs correctly and shows invalid when the values of the two inputs are equal - but have a look in the console on that fiddle. Why does the validation routine run three five times on load, and why do both fields validate when just one value updates?

  • On page load
    • Expected: validation runs once for each input field.
    • Actual: validation runs three times for one input, and twice for the other.
  • On value update (either input field)
    • Expected: validation runs for altered input field only
    • Actual: validation runs for both input fields

It's worth noting that this strange behaviour is only observed after reading params.parent.items() in the validator. If the return is commented out, the behaviour I would expect is observed.

1
I don't understand - that fiddle is working perfect? - PW Kad
@PWKad I don't think it is. The messages display correctly, but open the console and look at when the validation routine runs. Updated question to make this clearer. - sennett

1 Answers

2
votes

I believe the way this works is that the "validator" function is used in a computed observable. Thus, any observables that are read as it executes are now dependencies for the computed. Since you are reading each item's word observable in this function, each one triggers validation for all of the others.

It makes sense that it works this way, though in the case of your particular application, it doesn't make sense. You could use peek to read the observables while not triggering the dependency detection:

var allWords = params.parent.items.peek();

// ...

var otherWords = _.map(otherWordViewModels, function (item) {
    return item.word.peek();
});