Search code examples
javascriptknockout.jsknockout-validation

How to validate specific child items using Knockout-Validation


I am using Knockout-Validation's sample and added 2 child collections which contain validation rules.

var Item = function(id, name) {
  var self = this;
  self.id = ko.observable(id).extend({required: {message: 'item id required'}});
  self.name = ko.observable(name).extend(
  {
    minLength: {message :'item name must be at least 5 characters', params: 5}, 
    maxLength: 10
  });
   
   ko.validation.group(self, { live: true }); 
};

var itemsList = [new Item(1, 'test'), new Item(2, 'item 2'), new Item(0, '1')];

var viewModel = {
    items : ko.observableArray(itemsList),
    items2: ko.observableArray(itemsList),
    firstName: ko.observable().extend({minLength: 2, maxLength: 10}),
    lastName: ko.observable().extend({required: true}),
    emailAddress: ko.observable().extend({
        // custom message
        required: {
            message: 'Please supply your email address.'
        }
    }),
    age: ko.observable().extend({min: 1, max: 100}),
    location: ko.observable()
};

Using

ko.validation.group(viewmodel, { deep: true });

my child collections display the validation messages correctly, but I only want 1 child collection validated. The documentation says to pass in the items I want validated like:

ko.validation.group([viewModel.firstName, viewModel.lastName, viewModel.items]);

but when I do that, the child collection no longer displays the validation message.

To reproduce, go to the sample I linked above and click submit. Item 0 and Item 2 in both groups will have validation messages. Change

viewModel.errors = ko.validation.group(viewModel, {deep: true});

to

viewModel.errors = ko.validation.group([viewModel.lastName, viewModel.items]);

Run again and click submit.

enter image description here


Solution

  • When you create the ViewModel:

     var itemsList = [new Item(1, 'test'), new Item(2, 'item 2'), new Item(0, '1')];
     
     var viewModel = {
         items : ko.observableArray(itemsList),
         items2: ko.observableArray(itemsList),
    

    you are sharing the same observables in the two observable arrays, that is, items and items2.

    You could solve it by adding new observables (I add itemsList2 and then I use it to initialize items2):

     var itemsList = [new Item(1, 'test'), new Item(2, 'item 2'), new Item(0, '1')];
     var itemsList2 = [new Item(1, 'test'), new Item(2, 'item 2'), new Item(0, '1')]; <---
     
     var viewModel = {
         items : ko.observableArray(itemsList),
         items2: ko.observableArray(itemsList2), <---
         ...
     };
    

    Finally, you need to add {deep: true} in this sentence:

    viewModel.errors = ko.validation.group([viewModel.lastName, viewModel.items], {deep: true});
    

    Here it is a fiddle as an example, and the full ViewModel.

    var Item = function(id, name) {
      var self = this;
      self.id = ko.observable(id).extend({required: {message: 'item id required'}});
      self.name = ko.observable(name).extend(
      {
        minLength: {message :'item name must be at least 5 characters', params: 5}, 
        maxLength: 10
      });
       
       ko.validation.group(self, { live: true }); 
    };
    
    var itemsList = [new Item(1, 'test'), new Item(2, 'item 2'), new Item(0, '1')];
    var itemsList2 = [new Item(1, 'test'), new Item(2, 'item 2'), new Item(0, '1')];
    
    var viewModel = {
        items : ko.observableArray(itemsList),
        items2: ko.observableArray(itemsList2),
        firstName: ko.observable().extend({minLength: 2, maxLength: 10}),
        lastName: ko.observable().extend({required: true}),
        emailAddress: ko.observable().extend({
            // custom message
            required: {
                message: 'Please supply your email address.'
            }
        }),
        age: ko.observable().extend({min: 1, max: 100}),
        location: ko.observable()
    };