Search code examples
knockout.jsknockout-2.0durandalknockout-validation

Knockout validation: Dynamic constraints


I'm using Durandal, which in turn leverages off of Knockout.

I want to be able to Change validation lengths dynamically

enter image description here

Fiddle

The fiddle seems to be behaving slightly different than my "working" solution, but its still not doing what I'm wanting/expecting it to.

Viewmodel JS:

[Attempt 1]

define(function () {

   var self = this;

   self.userInfo = {       
        IdOrPassportNumber: ko.observable().extend({
            required: true,
            pattern: {
                message: 'A message',
                params: /some regex/
            }
        }),
        IdType: ko.observable()
    },

    self.isIdValid = ko.validatedObservable({ 
         IdOrPassportNumber: self.userInfo.IdOrPassportNumber 
    });

    self.userInfo.IdOrPassportNumber.subscribe(function (value) {
          if (isIdValid.isValid()) {
               console.log('YOLO!');
          }
    });

    self.userInfo.IdType.subscribe(function (value) {
        console.log(value);
        if (value === 'Passport') {
            self.userInfo.IdOrPassportNumber.extend({ maxLength: 15 });
        } else {
            self.userInfo.IdOrPassportNumber.extend({ maxLength: 13 });
        }
    });    

    var viewModel = {
        userInfo: self.userInfo
    };

    viewModel["errors"] = ko.validation.group(viewModel.userInfo);
    viewModel["errors"].showAllMessages();

    return viewModel;
});

What seems to be happening is that when i start typing i get the max & min validation of 13, but if i continue typing the validation changes to 15. I have tried another route of, setting the min & max length in the initial observable extend EG, just after the regex, and then setting the min and max length to use an observable, to no success.

[Attempt 2]

   self.userInfo = {       
       IdOrPassportNumber: ko.observable().extend({               
            maxLength: self.maxLength(), 
            minlength: self.maxLength()
       }),
       IdType: ko.observable()
   },

   self.maxLength = ko.observable();

   self.userInfo.IdType.subscribe(function (value) {

          if (value === 'Passport') {
             self.maxLength(15)
          } else {
              self.maxLength(3)
          }
    });

Solution

  • You were so close :-) You must provide the observable itself, not the unwrapped value. So just remove the () from maxLength() - the validation library will automatically unwrap it for you.

    self.userInfo = {       
       IdOrPassportNumber: ko.observable().extend({               
            maxLength: self.maxLength, 
            minlength: self.maxLength
       }),
       IdType: ko.observable()
    },
    

    Here's another example with dynamic regex patterns.

        zipPostalPattern = ko.pureComputed(() => this.countryCode() === 'US' ? '^\\d{5}(?:[-\\s]\\d{4})?$' : '');
        zipOrPostal: KnockoutObservable<string> = ko.observable('').extend(
        {
            required: true,
            pattern: {
                message: 'This is not a valid postcode for the country',
                params: this.zipPostalPattern
            }
        });
    

    or (if you don't want a message).

        zipPostalPattern = ko.pureComputed(function() { return this.countryCode() === 'US' ? '^\\d{5}(?:[-\\s]\\d{4})?$' : ''});
        zipOrPostal: KnockoutObservable<string> = ko.observable('').extend(
        {
            required: true,
            pattern:  self.zipPostalPattern
        });
    

    Important: If you don't want a custom message don't just remove the message parameter and leave pattern = { params: this.zipPostalPattern } because it won't work. If you don't have a message you must set the Regex/string directly for the pattern parameter.

    Or of course you can just define the computed observable in place (here it's ok to call countryCode() as a function because that's how computed's work)

        zipOrPostal: KnockoutObservable<string> = ko.observable('').extend(
        {
            required: true,
            pattern:  ko.pureComputed(function() { 
                         return self.countryCode() === 'US' ? '^\\d{5}(?:[-\\s]\\d{4})?$' : ''
                      })
        });