KnockoutJS Validation with dynamic observables
Asked Answered
T

2

10

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?

Truncation answered 14/12, 2013 at 18:52 Comment(5)
Have you managed to try out my proposed solutions? Are they usable for you, or do you need further help?Bandylegged
@Bandylegged I think your solutions work very well, both of them. I'm just waiting for acceptance, since someone opened a bounty on thisTruncation
Oh, I haven't noticed that you're not the one who have stared the bounty... so @artlung have you managed to try out my proposed solutions? Are they usable for you (they've worked well for brazorf), or do you need some further help?Bandylegged
@Bandylegged I offered the bounty just because I thought the question needed more attention. I recently used a bounty for my own question and got great help and I wanted to help a knockout.js question. I will apply the bounty to whatever post the OP wants or whatever question gets the most votes.Crelin
Which answer do you wish to get the bounty?Crelin
B
13

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.

Bandylegged answered 17/12, 2013 at 19:58 Comment(3)
"you cannot add validation to a property after the property was bound on the UI." can you please share how did you get this conclusion?Mollie
@ebramtharwat from the source code of the plugin... the validation plugin is amendening the value and the checked bidings and in the init function it checks that the bound property is validatable or not. If the property is not validatable in the init then the validation plugin don't care with the property anymore: github.com/Knockout-Contrib/Knockout-Validation/blob/master/Src/…Bandylegged
If you do value: person().name and the value of person is replaced, the binding will be re-evaluated. However, it won't call init again; just update.Repossess
H
1

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

Holzer answered 17/12, 2013 at 20:18 Comment(2)
uhm there's maybe something wrong with your test code, i don't have such an error in mineTruncation
I didn't use JSFiddle, so I'm pointing out what errors could pop up. The kind of things that anyone else, facing a similar problem, should keep an eye open for (the smells). Anyway, were you able to make it work properly?Holzer

© 2022 - 2024 — McMap. All rights reserved.