10

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)
          }
    });
Rohan Büchner
  • 5,333
  • 4
  • 62
  • 106

3 Answers3

6

Here is the solution that worked for me:

I made use of the custom validation feature, more specifically the single use custom validation as this wont be re-used elsewhere.

[Attempt 3]

    self.userInfo = {    
        IdOrPassportNumber: ko.observable().extend({
            required: true,
            pattern: {
                message: 'A message',
                params: /some regex/
            },
            validation: {
               validator: function (val) {
                   if (self.userInfo.IdType() === 'Id') { 
                      return val.length === 13; 
                   } else { 
                      return val.length === 15; 
                   }
                },
               message: function () {
                  if (self.userInfo.IdType() === 'Id') {
                    return 'Required: 13 characters';
                  } else {
                    return 'Required: 15 characters';
                  }
               }
            }
        })
     }
Rohan Büchner
  • 5,333
  • 4
  • 62
  • 106
  • 3
    As a side note. I noticed that the order of the validations also matter, so say you want your custom validator (or any other) for that matter to trigger first, order them accordingly – Rohan Büchner Jun 26 '13 at 14:26
  • Isn't it better to use two different fields with fixed validation and change binding on the page ``? On save just write `var idOrPassword=id()||password()`. – blazkovicz Oct 06 '14 at 05:59
5

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})?$' : ''
                  })
    });
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • 2
    For anyone else reading this. I'm unable to test this against this exact situation I had at this at that stage, (no longer on this project). I do prefer @Simon_Weaver's solution as the accepted solution, and will mark it as such. – Rohan Büchner Jun 02 '15 at 07:10
  • 1
    Thanks. I have to say it only occured to me today that I might be able to pass an observable as a parameter to a rule, and I got very excited - then realized perhaps it might not work - but then I got excited again that it seems to work very well :-) From what I remember it isn't clearly documented, but it's very easy if you already know how to use Knockout and adds some great flexibility. – Simon_Weaver Jun 02 '15 at 07:26
4

consider this

self.iDNumber = ko.observable('').extend({
        required: {
            params: true,
            message: 'ID Number is a required field.',
            insertMessages: false
        },
        SouthAfricanIDNumber: {
            message: 'Please enter a valid South African ID number.',
            onlyIf: function() {
                return self.identityType() === 'SAID';
            }
        }
    });

where SouthAfricanIDNumber is a custom validation that uses regex.

spacerogue
  • 37
  • 2