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", formattedNumber = Globalize.format(currentValue, format); return formattedNumber; } }, write: function (newValue) { var currentValue = ko.utils.unwrapObservable(observable), numberValue = Globalize.parseFloat(newValue); if (!isNaN(numberValue)) { if (numberValue !== currentValue) { // The value has changed so update the observable observable(numberValue); } } else if (newValue.length === 0) { if (ko.utils.unwrapObservable(properties.isNullable)) { // If newValue is a blank string and the isNullable property has been set then nullify the observable observable(null); } else { // If newValue is a blank string and the isNullable property has not been set then set the observable to 0 observable(0); } } } }); ko.utils.registerEventHandler(element, "focus", handleElementFocusIn); ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE ko.utils.registerEventHandler(element, "blur", handleElementFocusOut); ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE if (element.tagName.toLowerCase() === 'input') { ko.applyBindingsToNode(element, { value: interceptor }); } else { ko.applyBindingsToNode(element, { text: interceptor }); } } }; // Go German Globalize.culture("de-DE"); // Go Knockout ko.applyBindings({ iAmANumber: ko.observable(1.234), format: ko.observable("n1"), nullableNumber: ko.observable(false) });
<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>