-
-
Save marius7383/5bd1db68e1056af2cdc651e58640cbac to your computer and use it in GitHub Desktop.
Used to display and read formatted money via Knockout binding
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
// Credit for escapeRegExp goes to MDN | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters | |
function escapeRegExp(str) { | |
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); | |
} | |
// Credit for replaceAll goes to Sean Bright | |
// http://stackoverflow.com/a/1144788/2919694 | |
function replaceAll(str, find, replace) { | |
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); | |
} | |
var toMoney = function (num, decimalSeparator, thousandsSeparator, currency, leadingCurrency) { | |
if ((num != 0 && !num) || isNaN(num)) { | |
return num; // prevent data loss if invalid input is provided | |
} | |
if (decimalSeparator == thousandsSeparator) { | |
throw "Decimal separator must not be the same string as thousands separator"; | |
} | |
var output = ''; | |
if (currency && leadingCurrency) { | |
output = currency; | |
} | |
if (typeof num === 'string') { | |
num = parseFloat(num); | |
} | |
var fixed = num.toFixed(2); | |
// as long as toLocaleString is not properly defined (ES5 15.7.4.3) or | |
// ECMA-402 not properly implemented in all major browsers (Safari is missing, customers require < IE11), | |
// we have to do it ourselves | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Browser_compatibility | |
fixed = (fixed.replace(/(\d)(?=(\d{3})+\.)/g, '$1' + thousandsSeparator)); | |
// the following works under the condition that toFixed(2) provides two decimals | |
// get part in front of decimal separator | |
var thousands = fixed.slice(0, -3); | |
// get poart after decimal separator | |
var decimals = fixed.substr(-2, 2); | |
output += (thousands + decimalSeparator + decimals); | |
if (currency && !leadingCurrency) { | |
output += currency; | |
} | |
return output; | |
}; | |
var fromMoney = function (num, decimalSeparator, thousandsSeparator, currency, leadingCurrency) { | |
if (!num) { | |
return num; // prevent data loss if invalid input is provided | |
} | |
if (decimalSeparator == thousandsSeparator) { | |
throw "Decimal separator must not be the same string as thousands separator"; | |
} | |
if (typeof num !== 'string') { | |
num = num.toString(); | |
} | |
if (currency) { | |
if (leadingCurrency) { | |
num = num.substr(currency.length); | |
} else if (!leadingCurrency) { | |
num = num.slice(0, -1 * currency.length); | |
} | |
} | |
// remove thousands separators and replace decimal separator by dot | |
num = replaceAll(num, thousandsSeparator, ""); | |
num = replaceAll(num, decimalSeparator, "."); | |
if (isNaN(num)) { | |
return num; // prevent data loss if invalid input is provided | |
} | |
num = parseFloat(num); | |
return num; | |
}; | |
var subscribe = function (valueAccessor, callback) { | |
var config = valueAccessor(); | |
var members = ['decimalSeparator', 'thousandsSeparator', 'currency', 'leadingCurrency']; | |
for (var i = 0; i < members.length; i++) { | |
var observable = config[members[i]]; | |
if (ko.isObservable(observable)) { | |
observable.subscribe(callback); | |
} | |
} | |
}; | |
var process = function (valueAccessor, value, parse) { | |
var config = valueAccessor(); | |
value = value || ko.unwrap(config.value); | |
var decimalSeparator = ko.unwrap(config.decimalSeparator) || '.'; | |
var thousandsSeparator = ko.unwrap(config.thousandsSeparator) || ','; | |
var currency = ko.unwrap(config.currency) || ''; | |
var leadingCurrency = ko.unwrap(config.sourceFormat) || false; | |
var text = null; | |
if (!parse) { | |
text = toMoney(value, decimalSeparator, thousandsSeparator, currency, leadingCurrency); | |
} else { | |
text = fromMoney(value, decimalSeparator, thousandsSeparator, currency, leadingCurrency); | |
} | |
return text; | |
}; | |
var readonlyUpdate = function (element, valueAccessor) { | |
var text = process(valueAccessor, null, false); | |
ko.utils.setTextContent(element, text); | |
}; | |
var valueUpdate = function (element, valueAccessor) { | |
var text = process(valueAccessor, null, false); | |
$(element).val(text); | |
}; | |
/** | |
* Binding handler that formats the output value of a given observable as money. | |
* | |
* Structure of binding value: | |
* money: { | |
* value: the observable containing the value | |
* decimalSeparator: can be observable or constant string, defaults to dot '.' | |
* thousandsSeparator: can be observable or constant string, defaults to comma ',' | |
* currency: the currency sign like $ or €, which is either appended or prepended; can be observable or constant string, defaults to empty string ('') | |
* leadingCurrency: determines if the currency sign should be prepended (= true) or appended (=false); can be observable or constant string, defaults to false; | |
* } | |
*/ | |
ko.bindingHandlers.money = { | |
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { | |
subscribe(valueAccessor, readonlyUpdate.bind(this, element, valueAccessor)); | |
return {'controlsDescendantBindings': true}; | |
}, | |
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { | |
readonlyUpdate(element, valueAccessor); | |
} | |
}; | |
/** | |
* Binding handler that formats the output value of a given observable as money into a text box. | |
* Changes to the input are parsed to float and given back to the observable. | |
* If the input is erroneous, the unparsed input is given back to the observable to prevent data loss. | |
* | |
* Structure of binding value: | |
* money: { | |
* value: the observable containing the value | |
* decimalSeparator: can be observable or constant string, defaults to dot '.' | |
* thousandsSeparator: can be observable or constant string, defaults to comma ',' | |
* currency: the currency sign like $ or €, which is either appended or prepended; can be observable or constant string, defaults to empty string ('') | |
* leadingCurrency: determines if the currency sign should be prepended (= true) or appended (=false); can be observable or constant string, defaults to false; | |
* } | |
*/ | |
ko.bindingHandlers.moneyValue = { | |
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { | |
subscribe(valueAccessor, valueUpdate.bind(this, element, valueAccessor)); | |
$(element).change(function (e) { | |
var config = valueAccessor(); | |
var $this = $(this); | |
var value = $this.val(); | |
value = process(valueAccessor, value, true); | |
config.value(value); | |
}); | |
return {'controlsDescendantBindings': true}; | |
}, | |
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { | |
valueUpdate(element, valueAccessor); | |
} | |
}; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script type="text/javascript"> | |
ko.applyBindings({ | |
value: ko.observable(2500000.00), | |
currency: ko.observable('€'), | |
leadingCurrency: ko.observable(false), | |
decimalSeparator: ko.observable(','), | |
thousandsSeparator: ko.observable('.') | |
}); | |
</script> | |
<!-- Sets span's text to 2.500.000,00€ but keeps observable at 250000.00 --> | |
<span data-bind="money: { value: value, currency: currency, leadingCurrency: leadingCurrency, decimalSeparator: decimalSeparator, thousandsSeparator: thousandsSeparator }"></span> | |
<!-- Sets textbox value to 2.500.000,00€ but keeps observable at 250000.00 --> | |
<input type="text" data-bind="moneyValue: { value: value, currency: currency, leadingCurrency: leadingCurrency, decimalSeparator: decimalSeparator, thousandsSeparator: thousandsSeparator }"> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment