0
votes

I am working on an extender that will trigger a requirement on a field input. However, I want the input field to be initially blank, only after triggering an initial focus and then blur, do I want to display any error on the page if you have not provided any input on the field.

The problems I have found is:

  1. no manual subscription to the observable will be fired if the value has not changed
  2. providing events such as 'blur' to the valueUpdate binding won't trigger the change either if original value has not changed
  3. I am trying to avoid having to write a custom binding just to trigger the validation.
  4. computed's are supposedly always called, but again are based on either being referenced explicitly (i.e. UI) or because an observable they reference has been updated (which again is not triggered because it has not changed).

I like the idea of using an extender an not have to make explicit reference to the validation in the UI by using a binding. But I can't seem to get pass the initial state of being blank and triggering if they focus and blur without any input.

Any suggestions?

vs. knockout 3.0

2

2 Answers

3
votes

So I typically solve this with a paired extender and custom binding. Standard validation scenario to only want the error after the field has been entered and left. I like this method, since it easier to understand, and reuse. Here it is in a fiddle.

I don't see a problem with referencing a custom binding in the UI, even if it hints at validation. The UI knowing about bindings is standard for knockout, and there is nothing at all wrong with custom bindings. They are encouraged!

extender

ko.extenders.isValid = function (target, validator) {
    //Use for tracking whether validation should be used
    //The validate binding will init this on blur, and clear it on focus
    //So that editing the field immediately clears errors
    target.isActive = ko.observable(false);

    if (typeof validator !== 'function') {
        validator = function(value) {
            return value !== undefined && value !== null && value.length > 0;
        };
    }

    target.isValid = ko.computed(function () {
        return validator(target());
    });

    //Just a convienient wrapper to bind against for error displays
    //Will only show errors if validation is active AND invalid
    target.showError = ko.computed(function () {
        //This intentionally does NOT short circuit, to establish dependency
        return target.isActive() & !target.isValid();
    });

    return target;
};

Example Use

var login = {
    //use default validation
    email: ko.observable('').extend({ isValid: true }),
    //Use custom validation function
    password: ko.observable('').extend({
        isValid: function (value) {
            return value.length > 0 && value.length <= passwordMaxLength;
        }
    })
};

Binding

//Just activate whatever observable is given to us on first blur
ko.bindingHandlers.validate = {
    init: function (element, valueAccessor) {
        ko.bindingHandlers.value.init.apply(this, arguments); //Wrap value init
        //Active will remain false until we have left the field
        //Starting the input with validation errors is bad
        ko.utils.registerEventHandler(element, 'blur', function() {
            valueAccessor().isActive(true);
        });
        //Validation should turn off while we are in the field
        ko.utils.registerEventHandler(element, 'focus', function() {
            valueAccessor().isActive(false);
        });
    },
    update: ko.bindingHandlers.value.update //just wrap the update binding handler
};

Example Use

<input class="inputField" data-bind="validate: email"/>
1
votes

If you want an observable to always notify a "change" even if it's not changed, you can do so with the notify extender:

myObservable.extend({notify:'always'});