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>