Search code examples
javascriptjqueryvalidationknockout.jsknockout-validation

Knockout Validation on Collections of validatable objects


So I'm using Knockout 2.3 and Knockout Validation 2.0.2. I have a viewModel with a property that is an observableArray of custom javascript "objects" (HRAdmin). An HRAdmin has 3 properties that need validation. I've having a hard time with how to manage this type of situation. I've tried different things, but this is where the code sits at this point.

For the sake of brevity, I've stripped out all of the other properties of my viewModel that ARE validating just fine. But what this also tells me is none of my other code is interfering.

You can run and step through the code here and see that even when you leave all fields blank and click the "Go to Step 3" link, this line of code always results in the object being valid.

if (!obj[i].isValid())

It shouldn't be valid. Ideas/Suggestions??

   // check validity of each object in array
   ko.validation.rules['collectionValidator'] = {
     validator: function(obj, params) {
       for (var i = 0; i < obj.length; i++) {
         if (!obj[i].isValid()) {
           obj[i].notifySubscribers();
           return false;
         }
       }
       return true;
     }
   };

   // validate a certain number of object exist in array
   ko.validation.rules['minArrayLength'] = {
     validator: function(obj, params) {
       return obj.length >= params.minLength;
     },
     message: "Must have at least {0} {1}"
   };

   ko.validation.registerExtenders();


   // HRAdmin "object"
   function HRAdmin() {
     this.FirstName = ko.observable("").extend({
       required: true,
       minLength: 1,
       maxLength: 50
     });
     this.LastName = ko.observable("").extend({
       required: true,
       minLength: 1,
       maxLength: 50
     });
     this.Email = ko.observable("").extend({
       required: true,
       minLength: 1,
       maxLength: 100,
       email: true
     });
   }


   var viewModel = function() {

     var self = this;

     // Must be at least one HRAdmin and all fields of EACH HRAdmin must validate
     self.HrAdmins = ko.observableArray([ko.validatedObservable(new HRAdmin())]).extend({
       minArrayLength: {
         params: {
           minLength: 1,
           objectType: "Account Manager"
         },
         message: 'Must specify at least one Account Manager'
       },
       collectionValidator: {
         message: 'Please check the Account Manager information'
       }
     });

     self.AddHrAdmin = function(data, event) {
       self.HrAdmins.push(new ko.validatedObservable(new HRAdmin()))
     };

     self.GoToStep3 = function(data, event) {
       // validate at least one HRAdmin and ALL fields on each are valid          
       if (!self.HrAdmins.isValid()) {
         self.HrAdmins.notifySubscribers();
         return;
       }

       // on to step 3 ...
     };
   };

   ko.applyBindings(new viewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.2/knockout.validation.min.js"></script>


<div data-bind="foreach: HrAdmins">
  <input type="text" placeholder="First Name" data-bind="value: FirstName" />
  <input type="text" placeholder="Last Name" data-bind="value: LastName" />
  <input type="text" placeholder="Email Address" data-bind="value: Email" />
</div>
<p data-bind="validationMessage: HrAdmins" class="validationMessage"></p>
<a href="#" data-bind="click: $root.AddHrAdmin">Add Manager</a>
<a href="#" data-bind="click: $root.GoToStep3">Go to Step 3</a>


Solution

  • So after a lot of trail and error I've found a solution. Not sure if it's the BEST solution, but it works fine.

    I've gotten rid of ko.validation.rules['collectionValidator'] and added a validator grouping.

    self.HrAdminsErrors = ko.validation.group(self.HrAdmins, {deep: true, live: true});
    

    The operational code is at the following fiddle:

    http://jsfiddle.net/3b3o87dy/6/