Knockout bindingHandler for comma separated numbers
Asked Answered
I

1

8

I'm building a very number-heavy app in KnockoutJS and I want to have the ability to format large numbers so that they're comma-seperated and nice on the eye (xxx,xxx).

As you'll see from the fiddle below, I do have this working by wrapping the binded value inside of a formatting function with a simple RegEx but the problem with this is that this overwrites the value inside the input and inserts ',' into the underlying value.

The large numbers are used further down the app and so to prevent NaN errors I've had to assign a data attribute to the input value containing the value with no ',' and this is the value that gets stored in sessionStorage.

I feel that I have unncessarily bloated my HTML markup and believe that what I want to achieve is possible with a bindingHandler but my binding handler isn't quite there.

Fiddle: http://jsfiddle.net/36sD9/2

formatLargeNumber = function (number) {
    if (typeof (number) === 'function') {
        return number().toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }
}

ko.bindingHandlers.largeNumber = {
    init: function(element, valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        var interceptor = ko.computed({
            read: function() {
                return formatLargeNumber(value);
            },
            write: function(newValue) {
                value(reverseFormat(newValue));
            }
        });

        if(element.tagName == 'input' )
            ko.applyBindingsToNode(element, {
                value: interceptor
            });
        else
            ko.applyBindingsToNode(element, {
                text: interceptor
            });
    }
}

Any ideas?

Inflammation answered 20/4, 2014 at 9:9 Comment(0)
T
14

You have multiple problems with your current approach:

  • element.tagName returns INPUT, etc so you need to take care of the casing when doing the comparing.

  • var value = ko.unwrap(valueAccessor()); you are unwrapping your observable so in your computed you are using its value and not the function itself. So you just need var value = valueAccessor(); and you need to call ko.unwrap in your computed read method.

  • You don't just need to format but you need to "unformat" in the write method, but your formatLargeNumber only do the format direction.

  • You have applied value and your largeNumber on the same input which make the two bindings interfering with each other

  • Don't write the formatting code yourself just use a library which already does this like: http://numeraljs.com/

So here is the corrected version of your binding using numeraljs:

ko.bindingHandlers.largeNumber = {
    init: function(element, valueAccessor) {
        var value = valueAccessor();
        var interceptor = ko.computed({
            read: function() {
                return numeral(ko.unwrap(value)).format('0,0');
            },
            write: function(newValue) {
                value(numeral().unformat(newValue));
                value.valueHasMutated();
            }
        }).extend({notify: 'always'});
        if(element.tagName.toLowerCase() == 'input' )
            ko.applyBindingsToNode(element, {
                value: interceptor
            });
        else
            ko.applyBindingsToNode(element, {
                text: interceptor
            });
    }
}

And use it like this:

<input data-bind="largeNumber: testVal">    

Demo JSFiddle.

Taynatayra answered 20/4, 2014 at 9:56 Comment(8)
Thanks so much! The fiddle is working perfectly but when I copy it into my local copy, after writing a new value to the input field I get "Uncaught Error: Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters." - Any ideas what can be causing this?Inflammation
On which line do you get the error? The error message implies that you are using your largeNumber with a ko.computed so data-bind="largeNumber: yourComputed" but because this is two-way binding you can only use it with writable computeds or regular observables.Taynatayra
Ah, I am using it on a computed function yes. This is because the value of the input is actually dependent/generated based on a value that's chosen from a <select>Inflammation
hi @nemesv, can you please have a look at the issue i am facing here (https://mcmap.net/q/1325546/-knockout-bindinghandler-for-comma-separated-numbers-with-same-entry-again-in-a-textbox/847872) when u have some free time? I was trying to modify your solution. ThanksHadlee
@Hadlee Hi you just need to write: value.valueHasMutated(); at and of the write method of the interceptor: jsfiddle.net/8YgNg so I've fixed here my answer, and if this fine for you, then you can delete your other question.Taynatayra
This is exactly what I needed. Thanks a lot. Why dont you just write down a single line to my question and I will accept it as answer?Hadlee
Is it possible to add a live binding on input field? It means that I'm writing 123(knockout is adding comma ,) and I'm continue writing. If I have 1,234 When I delete the "4" it's changing to 123 in input field?Coolidge
I had to use numeral(newValue).value() instead of numeral().unformat(newValue) FYI.Digenesis

© 2022 - 2024 — McMap. All rights reserved.