if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str) {
return this.substring(0, str.length) === str;
}
};
function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
}
else if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
}
}
function setCaretToPos (input, pos) {
setSelectionRange(input, pos, pos);
}
(function ($) {
var cleanInput = function (value) {
value = value || '0';
value = value == "-" ? "0" : value;
console.log("dirty str: " + value);
var isNegative = value.startsWith('(') || value.startsWith('-');
var cleanStr = value.replace(/[^0-9.]/g, '');
console.log("cleaned str:" + cleanStr);
var result = parseFloat(cleanStr) * (isNegative? -1 : 1);
console.log("cleaned: " + result);
return result == Number.NaN ? 0 : result;
}
var formatInput = function (value) {
toks = value.toFixed(2).replace('-', '').split('.');
var display = '$' + $.map(toks[0].split('').reverse(), function (elm, i) {
return [(i % 3 == 0 && i > 0 ? ',' : ''), elm];
}).reverse().join('') + '.' + toks[1];
display = value < 0 ? '(' + display + ')' : display;
console.log("formatted: " + display);
return display;
}
ko.bindingHandlers.money = {
init: function (elm, valueAccessor) {
$(elm).keydown(
function(e){
-1!==$.inArray(e.keyCode,[46,8,9,27,13,110,190])||/65|67|86|88/.test(e.keyCode)&&(!0===e.ctrlKey||!0===e.metaKey)||35<=e.keyCode&&40>=e.keyCode||(e.shiftKey||48>e.keyCode||57<e.keyCode)&&(96>e.keyCode||105<e.keyCode)&&e.preventDefault();
console.log("e.keycode="+e.keyCode);
// prevent putting dashes anywhere but first position
if($.inArray(e.keyCode, [189,173]) >= 0) {
console.log("e.keycode="+e.keyCode);
elm.value = elm.value.startsWith("-") ? elm.value : ("-" + elm.value);
setCaretToPos(elm, 1);
}
}).keyup(function (e) {
valueAccessor()(cleanInput(elm.value));
}).focus(function () {
elm.value = valueAccessor()();
}).blur(function() {
elm.value = formatInput(valueAccessor()());
}).addClass('money');
},
update: function (elm, valueAccessor, allBindingsAccessor) {
var value =ko.utils.unwrapObservable(valueAccessor())
$elm = $(elm),
method = $elm.is(":input") ? "val" : "html";
if(!$elm.is(":focus"))
$elm[method](formatInput(value));
$elm.toggleClass('negative', value < 0);
}
};
})(jQuery);
//Not part of the custom binding, just wiring the viewModel up to the bindings.
$(function(){
var viewModel={
Cash:ko.observable(-1234.56)
};
ko.applyBindings(viewModel);
});
<body class='ui-widget'>
<header class='ui-widget-header'>
<h1>Give me all of your money</h1>
<br />
<h2> All your hugs and kisses too</h2>
</header>
<div class='ui-widget-content'>
<p>
<label>How Much?</label> <input type="text" data-bind="money:Cash" /> or? <input type="text" data-bind="value:Cash" /></p>
<p>
<label>Non inputs too!</label> <span data-bind="money:Cash" /></p>
<p>
<label>Normal binding:</label> <span data-bind="text:Cash" /></p>
</div>
</body>
.money {text-align:right}
.money.negative{color:#f00}
External resources loaded into this fiddle: