Knockout valueNumber binding

A demo of a valueNumber binding which uses Globalize.

by johnny_reilly

HTML

<script src="//ajax.aspnetcdn.com/ajax/globalize/0.1.1/globalize.min.js"></script>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<script src="//ajax.aspnetcdn.com/ajax/globalize/0.1.1/cultures/globalize.culture.de-DE.js"></script>
<h4>A demo of the valueNumber binding</h4>

<p>Using the Globalize "de-DE" locale...</p>

<div class="form-group">
    <label>iAmANumber using the valueNumber binding with an input:</label>
    <input data-bind="valueNumber: iAmANumber, 
                      numberFormat: format, 
                      isNullable: nullableNumber" type="text" />
</div>

<div class="form-group">
    <label>iAmANumber formatter (eg "n"/"c"/"p"/"d"):</label>
    <input data-bind="value: format" type="text" />
</div>

<div class="form-group">
    <label>iAmANumber is nullable <input type="checkbox" data-bind="checked: nullableNumber" /></label>
</div>


<div class="alert alert-success">
    <strong>iAmANumber using the valueNumber binding with a span and the "c3" format:</strong>
    <span data-bind="valueNumber: iAmANumber, numberFormat: 'c3', isNullable: nullableNumber"></span>
</div>
    
<div class="alert alert-success">
    <strong>iAmANumber underlying value:</strong>
    <span data-bind="text: iAmANumber"></span>
</div>

JavaScript

ko.bindingHandlers.valueNumber = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here

        // Adapted from the hasfocus handleElementFocusChange function
        function elementIsFocused() {
            var isFocused = false,
                ownerDoc = element.ownerDocument;
            if ("activeElement" in ownerDoc) {
                var active;
                try {
                    active = ownerDoc.activeElement;
                } catch(e) {
                    // IE9 throws if you access activeElement during page load
                    active = ownerDoc.body;
                }
                isFocused = (active === element);
            }

            return isFocused;
        }

        function handleElementFocusChange(isFocused) {
            elementHasFocus(isFocused);
        }

        var observable = valueAccessor(),
            properties = allBindingsAccessor(),
            elementHasFocus = ko.observable(elementIsFocused()),
            handleElementFocusIn = handleElementFocusChange.bind(null, true),
            handleElementFocusOut = handleElementFocusChange.bind(null, false);

        var interceptor = ko.computed({
            read: function () {
                var currentValue = ko.utils.unwrapObservable(observable);
                if (elementHasFocus()) {
                    return (!isNaN(currentValue) && (currentValue !== null) && (currentValue !== undefined))
                        ? currentValue.toString().replace(".", Globalize.findClosestCulture().numberFormat["."]) // Displays correct decimal separator for the current culture (so de-DE would format 1.234 as "1,234")
                        : null;
                } else {
                    var format = ko.utils.unwrapObservable(properties.numberFormat) || "n2",
                       ...