Number Formatting in Knockout js

Standard behaviour

If left alone, the templates will use the standard knockout ‘text’-binding to render any text, including numbers. This uses the default conversion to string of number.

The result will be that all numbers are rendered as ‘123.456’.

Solutions

Solution 1: custom ko-binding

The first solution is to create a custom binding in knockout that renders the numbers in a correct way based on the locale.

Below, a numberFormat binding is created that will read the value and if the value is a number (not null or undefined), it will use the toLocaleString function to convert the number to the correct format. It then passes the formatted string to the default text binding to render it.

// number-format.js
define(["require", "exports", "knockout"], function (require, exports, ko) {
    function formatValue(valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        if (typeof value === "number") {
            return function () { return value.toLocaleString(); };
        } else {
            return valueAccessor;
        }
    }

    ko.bindingHandlers.numberFormat = {
        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            ko.bindingHandlers.text.init(element, formatValue(valueAccessor), allBindings, viewModel, bindingContext);
        },
        update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            ko.bindingHandlers.text.update(element, formatValue(valueAccessor), allBindings, viewModel, bindingContext);
        }
    };
});

Next we register the binding in the ‘nsx/nsx-knockout-bindings.js’ (this requires an overlay):

// nsx/nsx-knockout-bindings.js
define([
    //...
    'path/to/number-format',
    //...
], function() {
    return {};
});

And we alter the template to use our new binding:

<tr>
  <th>
    <span data-bind="translate: 'testComp.bill.total'"></span>
  </th>
  <td>
    <span data-bind='numberFormat: total'></span>
  </td>
</tr>

Solution 2: overwrite ‘text’-binding

If the previous solution proves to cumbersome, it may be useful to take another approach; to overwrite the default text-binding.

In this case the js file will look like this:

// number-format.js
define(["require", "exports", "knockout"], function (require, exports, ko) {
    function formatValue(valueAccessor) {
        var value = ko.unwrap(valueAccessor());
        if (typeof value === "number") {
            return function () { return value.toLocaleString(); };
        } else {
            return valueAccessor;
        }
    }

    var originalTextBinding = ko.bindingHandlers.text;
    ko.bindingHandlers.text = {
        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            originalTextBinding.init(element, formatValue(valueAccessor), allBindings, viewModel, bindingContext);
        },
        update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            originalTextBinding.update(element, formatValue(valueAccessor), allBindings, viewModel, bindingContext);
        }
    };
});

We still check whether the value is a number, so that common strings are filtered out.

We still need to register the binding in the ‘nsx/nsx-knockout-bindings.js’ (this requires an overlay):

// nsx/nsx-knockout-bindings.js
define([
    //...
    'path/to/number-format',
    //...
], function() {
    return {};
});

After this, all numbers that our rendered with the ‘text’-binding will be formatted according to the locale settings.

Hard-coding a locale

If the locale should be set to strictly 1 locale, it is possible to pass the desired locale to the toLocaleString function:

function formatValue(valueAccessor) {
    var value = ko.unwrap(valueAccessor());
    if (typeof value === "number") {
        return function () { return value.toLocaleString('NL'); };
    } else {
        return valueAccessor;
    }
}