Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KnockoutJS Validation with dynamic observables

I am using this plugin https://github.com/ericmbarnard/Knockout-Validation and i am trying to validate an object that is loaded dynamically.

Javascript:

function VM() {
    var self = this;
    // This is a static observable, just to ensure that basic validation works fine.
    self.static = ko.observable();
    self.static.extend({required: true});

    // This is the observable that will be updated to my model instance.
    self.person = ko.observable({});

    // This is an handler for manual trigger.
    // I'm not even sure this is needed.
    self.a = function(){
        self.errors.showAllMessages();
        self.staticErrors.showAllMessages();
    }

    // Here i'm loading current person from somewhere, i.e. a rest service.
    self.load = function() {
        // Update observable
        self.person(new Model());

        // Define validation rules
        self.person().name.extend({required: true});
        self.person().email.extend({required: true});

        // Set person data
        self.person().name('Long');
        self.person().email('John'); 

        // Set validators
        self.errors = ko.validation.group(self.person);
        self.staticErrors = ko.validation.group(self.static);
    }
}

// Just a test model.
function Model() {
    this.name = ko.observable();
    this.email = ko.observable();
}

ko.validation.init();
var vm = new VM();
ko.applyBindings(vm);

Markup

<ul>
    <li>1. Hit "Load"</li>
    <li>2. Hit "Show errors", or maunally change input data.</li>
</ul>
<button data-bind='click: load'>Load</button>
<br/>

<h1>This is working properly.</h1>
<input type='text' data-bind='value: static' />
<br/>

<h1>This is not working.</h1>
<input type='text' data-bind='value: person().name' />
<input type='text' data-bind='value: person().email' />
<br/>
<button data-bind='click: a'>Show errors</button>

Fiddle http://jsfiddle.net/qGzfr/

How do I make this work?

like image 300
brazorf Avatar asked Dec 14 '13 18:12

brazorf


2 Answers

The validation plugin only gets applied in your bindings only if by the time when the binding is parsed by Knockout your properties are validate.

In different words: you cannot add validation to a property after the property was bound on the UI.

In your example you are using an empty object in self.person = ko.observable({}); as a default value, so when Knockout executes the data-bind='value: person().name' expression you don't have a name property so the validation won't work even if you later add the name property to your object.

In your example you can solve this with changing your Model constructor to include the validation rules:

function Model() {
    this.name = ko.observable().extend({required: true});
    this.email = ko.observable().extend({required: true});
}

And use an empty Model object as the default person:

self.person = ko.observable(new Model());

And when calling Load don't replace the person object but update its properties:

self.load = function() {

    // Set person data
    self.person().name('Long');
    self.person().email('John'); 
}

Demo JSFiddle.

Note: Knockout does not always handles well if you replace whole object like self.person(new Model()); so it is anyway a better practice to only update the properties and not throw away the whole object.

A different solution would be to use the with binding because inside the with binding KO will reevaluate the bindings if the bound property changes.

So change your view:

<!-- ko with: person -->
    <input type='text' data-bind='value: name' />
    <input type='text' data-bind='value: email' />
<!-- /ko -->

In this case you need to use null as the default person:

self.person = ko.observable();

And in your Load you need to add the validation before assigning your person property so by the time KO applies the bindings your properties have the validation:

self.load = function() {

    var model = new Model()

    model.name.extend({required: true});
    model.email.extend({required: true});

    self.person(model);

    // Set person data
    self.person().name('Long');
    self.person().email('John'); 
}

Demo JSFiddle.

like image 94
nemesv Avatar answered Sep 20 '22 15:09

nemesv


I was able to make it work, this are the changes required:

<head>                                                                           
    <script type="text/javascript" src ="knockout-2.3.0.js"></script>            
    <script type="text/javascript" src ="knockout.validation.min.js"></script>   
</head>                                                                          

<body>                                                                           
    <!-- no changes -->                                                          

    <script>                                                                     
        function VM() { ... }                           

        function Model() { ... }                        

        // ko.validation.init();                                                 
        var vm = new VM();                        
        ko.applyBindings(vm);                                                    

    </script>                                                                    
</body> 

What was done?

  • Include KnockoutJS and the validation plugin.
  • Bind after the elements have been added. Remeber that HTML pages are parsed from top to bottom.

How could you tell? In the console this errors appeared:

Cannot read property 'nodetype' of null

and

Cannot call method 'group' of undefined

like image 1
givanse Avatar answered Sep 19 '22 15:09

givanse