Search code examples
knockout.jsknockout-validation

knockout unable to extend multiple observable using extender


This is related to my another question here ! I am trying to create a custom validation extender without using validation library.

Am not able to extend my extender on to multiple observable. In the below scenario the extender is applied on the Password field but not on the Retype Password field.

<!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 />
    Password:<input type="password" data-bind="value:Password,visible:PasswordVisible" /><br />
    Retype Password:<input type="password" data-bind="value:RetypePassword,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.computed(function () {
                var newValue = target();

                var required = validateOptions.required();
                var validationType = validateOptions.validationType;                

                if (ko.unwrap(required)) {
                    switch (validationType) {
                        case "Text":
                            return newValue == '';
                       default:
                            target.HasErrors(false);
                            break;
                    }
                }
                return false;
            }, null, { deferEvaluation: true }).extend({ notify: 'always' });            
            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.RetypePassword = ko.observable('').extend({ Validate: { required: function () { return self.PasswordVisible(); }, validationType: "Text" } });                        
            self.AlreadyUser.subscribe(function (newVal) {  self.RetypePassword(''); });
            self.HasErrors = ko.computed(function () { return self.Password.HasErrors() && self.RetypePassword.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>

Solution

  • I copied your code to an interactive snippet and noticed there was a console error... Also, not all paths in your computed returned a value.

    I've fixed these issues and everything works as you'd expect. Did you check the console before posting?

    Errors:

    • newValue was not defined

    Computed mistake:

    • You have to create a subscription by calling target() inside the computed's method
    • You "set" the computed by returning a value, not by computedObs(newVal) inside the method.

    var pageModel;
    
    ko.extenders.Validate = function(target, validateOptions) {
      target.HasErrors = ko.computed(function() {
        var required = validateOptions.required();
        var validationType = validateOptions.validationType;
        var newValue = target();
        
        if (ko.unwrap(required)) {
          switch (validationType) {
            case "Text":
              return newValue == '';
            default:
              return false
          }
        }
        return false;
      }, null, {
        deferEvaluation: true
      }).extend({
        notify: 'always'
      });
      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.RetypePassword = ko.observable('').extend({
        Validate: {
          required: function() {
            return self.PasswordVisible();
          },
          validationType: "Text"
        }
      });
      self.AlreadyUser.subscribe(function(newVal) {
        self.RetypePassword('');
      });
      self.HasErrors = ko.computed(function() {
        return self.Password.HasErrors() && self.RetypePassword.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);
    };
    .has-error {
      background:red;
    }
    <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 />Password:
    <input type="password" data-bind="value:Password, visible:PasswordVisible, css: {'has-error': Password.HasErrors }" />
    <br />Retype Password:
    <input type="password" data-bind="value:RetypePassword,visible:PasswordVisible, css: {'has-error': RetypePassword.HasErrors }" />
    <br />
    <input type="button" value="Submit" onclick="validateModel();" />