Pregunta

I have created a JSFiddle that demonstrates my issue: http://jsfiddle.net/jeffreyrswenson/hsW25/4/

I have created a custom Knockout extender to fix a problem I'm having with serializing to JSON. If this custom extender is placed before the Knockout-Validation extender everything works as I'd expect. If I put the custom extender after the Knockout-Validation extender, the validation stops working.

Why does this happen? It would be easy enough to put one extender before the other, but if someone wasn't aware of the issue, it would be very difficult to figure out why there was a problem.

Html:

<label>First name:
    <input data-bind='value: firstName, valueUpdate: "afterkeydown"' />
</label>
<br/>
<label>Last name:
    <input data-bind='value: lastName, valueUpdate: "afterkeydown"' />
</label>
<br>
<pre data-bind="text: ko.toJSON($data,null,2)"></pre>

<br> <span data-bind='text: errors().length'></span> errors

JS:

ko.extenders.undefinedIfBlank = function (target, option) {
    if (option) {
        var result = ko.computed({
            read: target,
            write: function (newValue) {
                var current = target();
                if (newValue !== current) {                    
                    if (typeof newValue === "string" && newValue.length == 0) {
                        target.notifySubscribers(newValue);
                        target();
                    } else {               
                        target(newValue);
                    }
                } else {
                    var x = 1;
                }
            }
        }).extend({
            notify: 'always'
        });

        result(target());

        return result;
    }

    return target;
};

ko.validation.configure({
    decorateElement: true,
    registerExtenders: true,
    messagesOnModified: true,
    insertMessages: true,
    parseInputAttributes: true,
    messageTemplate: null
});

var viewModel = function () {
    this.firstName = ko.observable().extend({
        undefinedIfBlank: true
    }).extend({
        required: true
    });
    this.lastName = ko.observable().extend({
        undefinedIfBlank: true
    }).extend({
        required: true
    });
    this.errors = ko.validation.group(this);
};

ko.applyBindings(new viewModel());
¿Fue útil?

Solución

Your custom extension creates a new computed then returns it with the unwrapped value of the target stored inside. This does not pass along any of the other existing attributes from target, except for its value. Therefore, if your extension is ran second it will clear any previous extensions. I don't know enough about extensions to help you make yours cascading friendly (Edit: see bottom for alternate implementation).

    //Creates a new computed that doesn't carry over previous extensions
    var result = ko.computed({
        read: target,
        write: function (newValue) {
            var current = target();
            if (newValue !== current) {                    
                if (typeof newValue === "string" && newValue.length == 0) {
                    target.notifySubscribers(newValue);
                    target();
                } else {               
                    target(newValue);
                }
            } else {
                var x = 1;
            }
        }
    }).extend({
        notify: 'always'
    });

    //copies value but nothing else
    result(target());

    return result;

I did notice a second issue. If you type in some text, then delete or enter a zero length string it will not allow you to delete your last character. Maybe this is what you wanted it to do?

    ko.extenders.undefinedIfBlank = function (target, option) {
        if (option) {
            var result = ko.computed({
                read: target,
                write: function (newValue) {
                    var current = target();
                    if (newValue !== current) {                    
                        if (typeof newValue === "string" && newValue.length == 0) {
                            //invoking target() alone doesn't really do anything
                            //target();
                            target(undefined); 
                            target.notifySubscribers(undefined);
                        } else {               
                            target(newValue);
                        }
                    } else {
                        var x = 1;
                    }
                }
            }).extend({
                notify: 'always'
            });

            result(target());

            return result;
        }

        return target;
    };

http://jsfiddle.net/rnkLb39b/2/

Edit: Still not an expert here, but this is an implementation of your extension that works. Instead of creating a new computed, it just subscribes to the existing object thereby extending it rather than replacing it.

    ko.extenders.undefinedIfBlank = function (target, option) {
        if (option) {
            var oldValue;
            target.subscribe(function(currentValue){
                oldValue = currentValue;
            }, null, 'beforeChange');

            target.subscribe(function (newValue) {
                    if (newValue !== oldValue) {                    
                        if (typeof newValue === "string" && newValue.length == 0) {
                            target(undefined);
                            target.notifySubscribers(undefined);
                        } else {            
                            target(newValue);
                        }
                    } else {
                        //do nothing?
                    }
            });

            return target;
        }

        return target;
    };

http://jsfiddle.net/13nugjca/

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top