0
votes

I am trying to create my own validation without using the knockout validation library. Am trying to create a common Validate extender that can do all type of validations i want it to do. Am doing this by passing type of validation and the required flag in an object to the extender. The problem am having is that the validate method only fires when the Password field is changed and not when the PasswordVisible property is changed. This is causing problem when the Password is already empty and when the PasswordVisible property is changed, the attempt to empty Password is not considered as change and hence not firing the extender.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <script type="text/javascript" src="knockout-3.4.0.js"></script>

    Name:<input type="text" data-bind="value:Name" /><br />
    Already A User: <input type="checkbox" data-bind="checked:AlreadyUser" /><br />
    New Password:<input type="password" data-bind="value:Password,visible:PasswordVisible" /><br />
    <input type="button" value="Submit" onclick="validateModel();" />

    <script type="text/javascript" >
        var pageModel;

        ko.extenders.Validate = function (target, validateOptions) {
            target.HasErrors = ko.observable(false);
            var required = validateOptions.required();
            var validationType = validateOptions.validationType;
            function validate(newValue) {
                alert('validating');
                if (required) {
                    switch (validationType) {
                        case "Text":
                            target.HasErrors(newValue == "" ? false : true);
                            break;
                        default:
                            target.HasErrors(false);
                            break;
                    }
                }
            }

            validate(target());
            target.subscribe(validate);
            return target;
        };

        //The model itself
        var ViewModel = function () {            
            var self = this;
            self.Name = ko.observable('');
            self.AlreadyUser = ko.observable(false);
            //computed variable that sets the visibility of the password field. I have to clear the password when am making it invisible
            self.PasswordVisible = ko.computed(function () { return !this.AlreadyUser(); }, this).extend({ notify: 'always' });
            //this field is only required when visible
            self.Password = ko.observable('').extend({ Validate: { required: function () { return self.PasswordVisible() }, validationType: "Text" } });
            self.PasswordVisible.subscribe(function (newVal) { self.Password(''); });
            self.HasErrors = ko.computed(function () { return self.Password.HasErrors(); },self);
        };



        //The method calls on click of button
        function validateModel() {
            alert(pageModel.HasErrors());
            }

        //create new instance of model and bind to the page
        window.onload = function () {          
            pageModel = new ViewModel();
            ko.applyBindings(pageModel);
        };

    </script>
</body>
</html>

How to fire the validate when the PasswordVisible is changed as well.

1

1 Answers

1
votes

You could make HasErrors a ko.computed to automatically create subscriptions to any observable used. It might trigger some unneeded re-evaluations though...

ko.extenders.Validate = function(target, validateOptions) {
  target.HasErrors = ko.computed(function() {
    // Create subscription to newValue
    var newValue = target();

    // Create subscriptions to any obs. used in required
    var required = validateOptions.required();
    var validationType = validateOptions.validationType;

    if (ko.unwrap(required)) {
      switch (validationType) {
        case "Text":
          return newValue == "";
      }
    };


    return false;
  }, null, {
    deferEvaluation: true
  });

  return target;
};

Note that you also do not need to wrap the PasswordVisible observable in a function to execute it; you can use ko.unwrap instead.

Here's my approach in your code. You might want to take another look at the multiple validations once you hide the password when there's a value inside (the clear via self.Password('') triggers another validation).

var pageModel;
var i = 0;
ko.extenders.Validate = function(target, validateOptions) {
  target.HasErrors = ko.computed(function() {
    console.log("validating " + ++i);

    // Create subscription to newValue
    var newValue = target();

    // Create subscriptions to any obs. used in required
    var required = validateOptions.required();
    var validationType = validateOptions.validationType;

    if (ko.unwrap(required)) {
      switch (validationType) {
        case "Text":
          return newValue == "";
      }
    };


    return false;
  }, null, {
    deferEvaluation: true
  });

  return target;
};

//The model itself
var ViewModel = function() {
  var self = this;
  self.Name = ko.observable('');
  self.AlreadyUser = ko.observable(false);
  //computed variable that sets the visibility of the password field. I have to clear the password when am making it invisible
  self.PasswordVisible = ko.computed(function() {
    return !this.AlreadyUser();
  }, this).extend({
    notify: 'always'
  });
  //this field is only required when visible
  self.Password = ko.observable('').extend({
    Validate: {
      required: function() {
        return self.PasswordVisible()
      },
      validationType: "Text"
    }
  });
  self.PasswordVisible.subscribe(function(newVal) {
    self.Password('');
  });
  self.HasErrors = ko.computed(function() {
    return self.Password.HasErrors();
  }, self);
};



//The method calls on click of button
function validateModel() {
  console.log(pageModel.HasErrors());
}

//create new instance of model and bind to the page
window.onload = function() {
  pageModel = new ViewModel();
  ko.applyBindings(pageModel);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Name:
<input type="text" data-bind="value:Name" />
<br />Already A User:
<input type="checkbox" data-bind="checked:AlreadyUser" />
<br />New Password:
<input type="password" data-bind="value:Password,visible:PasswordVisible" />
<br />
<input type="button" value="Submit" onclick="validateModel();" />