Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom binding no longer working in KnockoutJS 3.0

All of the sudden, the following code no longer works when targeting KnockoutJS 3.0. How can I work around this?

JavaScript:

ko.bindingHandlers.limitCharacters = {
    update: function(element, valueAccessor, allBindingsAccessor, viewModel)
    {
       element.value = element.value.substr(0, valueAccessor());
       allBindingsAccessor().value(element.value.substr(0, valueAccessor()));
    }
};

HTML:

<textarea data-bind="value: comment, valueUpdate: 'afterkeydown', limitCharacters: 20"></textarea>

See fiddle: http://jsfiddle.net/ReQrz/1/

like image 940
Brian David Berman Avatar asked Mar 21 '23 13:03

Brian David Berman


2 Answers

In KO 3.0 the bindings are Independent and ordered. You can read more about this here, and this should be considered a "breaking change", from the above linked example:

v2.x’s behavior about dependencies between bindings (described in the section about “Independent and ordered bindings” above), is an undocumented internal implementation detail so hopefully you aren’t relying on it. But if you are relying on that then obviously you will see a change of behavior because bindings are independent in v3 by design. You’ll need to stop relying on cross-binding dependencies, which by the way will make your code much cleaner and easier to understand.

So you binding is not working anymore because it assumed that when your comment property is changed it fires also your limitCharacters binging altough your limitCharacters binding has nothing to do with the comment property.

One possible solution for fixing this that you need to explicitly declare a dependency on the value binding in your update handler by acessing its value with allBindingsAccessor().value();:

ko.bindingHandlers.limitCharacters = {
    update: function(element, valueAccessor, allBindingsAccessor, viewModel)
    {
       var val = allBindingsAccessor().value();       
       allBindingsAccessor().value(val.substr(0, valueAccessor()));
    }
};

Demo JSFiddle.

like image 88
nemesv Avatar answered Mar 31 '23 18:03

nemesv


nemesv is completely right.

His modification is short and easy to read. One drawback of that though is the observable is called twice when in exceeds the limit.

If you don't want that, a solution is to create a custom value binder which derives from the original.

(function() {
    var limitValueBindingHandler = {};

    var valueBindingHandler = ko.bindingHandlers.value;
    for(var attr in valueBindingHandler) {
        if (valueBindingHandler.hasOwnProperty(attr)) {
            limitValueBindingHandler[attr] = valueBindingHandler[attr];
        }
    }

    limitValueBindingHandler.init = function(element, valueAccessor, allBindings) {
        var limitCharacters = allBindings.get("limitCharacters");

        element.addEventListener("keydown", function() {
            setTimeout(function() {
                //this is called after the element's value is updated
                //but before value binding event handler
                element.value = element.value.substr(0, limitCharacters);
            }, 0);
        }, false);

        valueBindingHandler.init(element, valueAccessor, allBindings);
    }

    ko.bindingHandlers['limitValue'] = limitValueBindingHandler;
})();

Sample: http://jsfiddle.net/DAFN6/

like image 44
LostInComputer Avatar answered Mar 31 '23 19:03

LostInComputer