Pregunta

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?

¿Fue útil?

Solución

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.

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