Search code examples
knockout.jsknockout-mapping-pluginknockout-validation

knockout validation on a dynamic form using mapping


I'm trying to do a validation on dynamic form since some days and I can't get it. I would apreciate your help a lot.

I need to extend the ViewModel's properties with the information received from the web service. I have tried to personalize the mapping, but in checkboxes cases, enters once from every value of the array. Furthermore, in radios and checkboxes cases, only must be applied the needed attribute in the first of them, just only to appear once the validation message.

var answerMapping = {
    "values": {
        create: function(options) {                    
            var answer = ko.observable(options.data);                        
            answer.extend({
                required: {
                    onlyIf: function () { return (options.parent.required() === true); }
                }
            });     
            return answer;
        }
    },  
    "value": {
        create: function(options) {                    
            var answer = ko.observable(options.data);                        
            answer.extend({
                required: {
                    onlyIf: function () { return (options.parent.required() === true); }
                }
            });     
            if (options.parent.type() === 3) {
                answer.extend({ number: true, min: options.parent.min, max: options.parent.max });
            }
            if (options.parent.type() === 4) {
                answer.extend({ dateISO: true });
            }
            return answer;
        }
    }
};

You can see sample in jsfiddle before customize mapping: jsfiddle


Solution

  • There are several problems with your fiddle.

    The main problem is that the required validator cannot be used like this for an observable array (it seems it only checks if the content is null, not if the array is empty).

    You can use this custom validator instead:

    ko.validation.rules['requiredNonEmpty'] = {
        validator: function (val, other) {
            console.log(val);
            return val !== null && val.length > 0;
        },
        message: "Required non empty"
    }
    ko.validation.registerExtenders();
    

    Other problems included:

    • values declared as observable instead of observableArray
    • some missing parenthesis (for the min and max)
    • the grouping was not deep (myVM.errors = ko.validation.group(myVM, {deep: true});)

    If you want to display the message for the values observableArray, add the following:

    <span data-bind="validationMessage: values"></span>
    

    Update

    You are right. The problem is that values: { create: function(options) {} } is called for each value in values: ["A1", "A2"] but not for the whole array.

    You have to add the validation after the mapping :(

    Like this:

    self.load = function (data) {
        ko.mapping.fromJSON(data, answerMapping, self);
        for (var i = 0; i < self.questions().length; i++) {
            var question = self.questions()[i];
            if (ko.isObservable(question.values) 
                && question.values() instanceof Array) {
                question.values.extend({
                    requiredNonEmpty: {
                        onlyIf: function () { return (question.required()=== true); }
                    }
                });
            }
        }
    };
    

    And remove the values part in the mapping.

    Updated fiddle