// This file is part of the jQuery formatCurrency Plugin. // // The jQuery formatCurrency Plugin is free software: you can redistribute it // and/or modify it under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // The jQuery formatCurrency Plugin is distributed in the hope that it will // be useful, but WITHOUT ANY WARRANTY; without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with // the jQuery formatCurrency Plugin. If not, see . ;(function ($) { $.formatCurrency = {}; $.formatCurrencyLive = {}; $.toNumber = {}; $.asNumber = {}; var fcDefaults = { colorize: false, region: '', roundToDecimalPlace: 2, // roundToDecimalPlace: -2; for not allowing decimals; roundToDecimalPlace: -1; for no rounding; 0 to round to the dollar; 1 for one digit cents; 2 for two digit cents; 3 for three digit cents; ... minimumDecimalPlaces: 9, eventOnDecimalsEntered: false, suppressCurrencySymbol: false, removeTrailingZerosOnDecimal: false, parseAsFloat: false }; var fcLiveDefaults = { decPointCharCodes: [], // by index: 0 - key code for key press event; 1 & 2 key codes for keyup event for the decimal point keys on the keyboard and keypad, respectively formatOnBlur: true, filterKeys: true, formatOnType: true }; var toNumberDefaults = { region: '' }; var asNumberDefaults = { region: '', parse: true, parseType: 'Float' }; $.formatCurrency.regions = []; // default Region is en $.formatCurrency.regions[''] = { symbol: '$', positiveFormat: '%s%n', negativeFormat: '(%s%n)', decimalSymbol: '.', digitGroupSymbol: ',', groupDigits: true }; $.formatCurrencyLive.setDefaults = function(settings) { $.extend(fcLiveDefaults, settings); }; $.fn.formatCurrencyLive = function (settings) { return this.each(function () { var $this = $(this); settings = buildSettingsObjGraph( settings, $.extend( {}, fcDefaults, fcLiveDefaults, ($this.data('formatCurrency') ? $this.data('formatCurrency') : {}) ) ); // setup the decimal point charCode setting according to the region if (settings.roundToDecimalPlace === -2) settings.decPointCharCodes = [-1, -1, -1]; else { if (settings.decimalSymbol == ',') settings.decPointCharCodes = [44, 188, 110]; else settings.decPointCharCodes = [46, 190, 110]; } $this.data('formatCurrency', settings); if (settings.filterKeys) $this.off('keypress.formatCurrency') .on('keypress.formatCurrency', function (ev) { if (!keyAllowed(ev, settings.decPointCharCodes)) ev.preventDefault(); }); if (settings.formatOnType) { var settingsFmtOnType = $.extend({}, settings, { roundToDecimalPlace: -1, removeTrailingZerosOnDecimal: false }); $this.off('keyup.formatCurrency') .on('keyup.formatCurrency', function (ev) { if (keyAllowed(ev, settings.decPointCharCodes)) $(this).formatCurrency(settingsFmtOnType); }); } if (settings.formatOnBlur) $this.on('blur.formatCurrency', function (ev) { $(this).formatCurrency(settings); }); }); }; $.formatCurrency.setDefaults = function(settings) { $.extend(fcDefaults, settings); }; $.formatCurrency.setAllDefaults = function(settings) { for (var prop in settings) { if(fcDefaults.hasOwnProperty(prop)) fcDefaults[prop] = settings[prop]; if(fcLiveDefaults.hasOwnProperty(prop)) fcLiveDefaults[prop] = settings[prop]; if(toNumberDefaults.hasOwnProperty(prop)) toNumberDefaults[prop] = settings[prop]; if(asNumberDefaults.hasOwnProperty(prop)) asNumberDefaults[prop] = settings[prop]; } }; $.fn.formatCurrency = function (destination, settings) { if (arguments.length == 1 && typeof destination !== "string") { settings = destination; destination = false; } // if this element has settings associated with it by the live Formatter and no settings // was specified, use the stored settings. Else, build the settings object as normal settings = ($(this).data('formatCurrency') && !settings ? $(this).data('formatCurrency') : settings ); return this.each(function () { var $this = $(this); // get number var num = '0'; num = $this[$this.is('input, select, textarea') ? 'val' : 'html'](); var money = $.getFormattedCurrency(num, settings, true); settings = money[4]; $this.data('fcMetadata', { 'hasDecimals': money[1], 'originalDecimals': money[2], 'isPositive': money[3] }); if(!money) return; // setup destination var $destination = $([]); if (!destination) { $destination = $this; } else { $destination = $(destination); } // set destination $destination[$destination.is('input, select, textarea') ? 'val' : 'html'](money[0]); if (money[1] && settings.eventOnDecimalsEntered && money[2].length > 0) { $destination.trigger('decimalsEntered', money[2]); } // colorize if (settings.colorize) $destination.css('color', money[3] ? 'black' : 'red'); }); }; $.getFormattedCurrency = function (expr, settings, returnMetadata) { settings = buildSettingsObjGraph(settings, fcDefaults); if (settings.parseAsFloat) { var tryFloat = parseFloat(expr); expr = isNaN(tryFloat) ? expr : tryFloat; } expr = (typeof(expr) !== "string" ? expr.toString().replace('\.', settings.decimalSymbol) : expr); //identify '(123)' as a negative number if (expr.search('\\(') >= 0) expr = '-' + expr; if (expr === '' || (expr === '-' && settings.roundToDecimalPlace === -1)) return (returnMetadata ? [ expr, false, "", true, settings ] : ""); expr = expr.replace(settings.symbol, ''); // Remove currency symbol for arithmetic expr = expr.replace(settings.regexGroupDigit, ''); // Remove group digit for arithmetic if (settings.decimalSymbol != '.') expr = expr.replace(settings.decimalSymbol, '.'); // reset to US decimal for arithmetic // if the number is valid use it, otherwise clean it if (isNaN(expr)) { // clean number expr = expr.replace(settings.regexArithmetic, ''); if (expr === '' || (expr === '-' && settings.roundToDecimalPlace === -1)) expr = '0'; if (isNaN(expr)) expr = '0'; } var isPositive = (expr == Math.abs(expr)); if (!isPositive && settings.disableNegative === true) { expr = 0; isPositive = true; } // evalutate number input var numParts = String(expr).split('.'); var hasDecimals = (numParts.length > 1 && settings.roundToDecimalPlace > -2); var decimals = (hasDecimals ? numParts[1].toString() : '0'); var originalDecimals = hasDecimals || decimals != '0' ? decimals : ''; // format number expr = Math.abs(numParts[0]); expr = isNaN(expr) ? 0 : expr; if (settings.roundToDecimalPlace >= 0) { decimals = parseFloat('1.' + decimals); // prepend "0."; (IE does NOT round 0.50.toFixed(0) up, but (1+0.50).toFixed(0)-1 decimals = decimals.toFixed(settings.roundToDecimalPlace); // round if (decimals.substring(0, 1) == '2') { expr = Number(expr) + 1; } decimals = decimals.substring(2); // remove "0." } expr = String(expr); if (settings.groupDigits) { for (var i = 0; i < Math.floor((expr.length - (1 + i)) / 3); i++) { expr = expr.substring(0, expr.length - (4 * i + 3)) + settings.digitGroupSymbol + expr.substring(expr.length - (4 * i + 3)); } } if ((hasDecimals && settings.roundToDecimalPlace == -1) || settings.roundToDecimalPlace > 0) { if (settings.removeTrailingZerosOnDecimal) { decimals = decimals.replace(/0+$/, ''); if (decimals.length < settings.minimumDecimalPlaces) decimals = String(decimals + Array(settings.minimumDecimalPlaces+1).join('0')).slice(0,settings.minimumDecimalPlaces); } expr += (decimals.length > 0 ? settings.decimalSymbol + decimals : ""); } // format symbol/negative var format = isPositive ? settings.positiveFormat : settings.negativeFormat; var money = format; money = money.replace(/%s/g, settings.symbol); money = money.replace(/%n/g, expr); return (returnMetadata ? [ money, hasDecimals, originalDecimals, isPositive, settings ] : money); }; // Remove all non numbers from text $.toNumber.setDefaults = function(settings) { $.extend(toNumberDefaults, settings); }; $.fn.toNumber = function (settings) { return this.each(function () { var method = $(this).is('input, select, textarea') ? 'val' : 'html'; $(this)[method]($.toNumber($(this)[method](), settings)); }); }; $.toNumber = function(expr, settings) { settings = buildSettingsObjGraph(settings, toNumberDefaults); return expr.replace('(', '(-').replace(settings.regex, ''); }; // returns the value from the first element as a number $.asNumber.setDefaults = function(settings) { $.extend(asNumberDefaults, settings); }; $.fn.asNumber = function (settings) { var method = $(this).is('input, select, textarea') ? 'val' : 'html'; var num = $(this)[method](); return $.asNumber(num, settings) }; $.asNumber = function(expr, settings) { settings = buildSettingsObjGraph(settings, asNumberDefaults); expr = expr ? expr : ""; expr = expr.replace('(', '(-'); expr = expr.replace(settings.regex, ''); if (!settings.parse) return expr; if (expr.length == 0) expr = '0'; if (settings.decimalSymbol != '.') expr = expr.replace(settings.decimalSymbol, '.'); // reset to US decimal for arithmetic return window['parse' + settings.parseType](expr); }; function buildSettingsObjGraph(settings, defaults) { if (!settings) settings = {}; var bareSettings = settings; // build settings graph starting from the defaults and merging region settings if needed settings = $.extend( {}, defaults, $.formatCurrency.regions[''], settings ); if (settings.region !== '') $.extend(settings, getRegionOrCulture(settings.region), bareSettings); if (settings.suppressCurrencySymbol) { settings.symbol = ''; settings.positiveFormat = $.trim(settings.positiveFormat.replace('%s', '')); settings.negativeFormat = $.trim(settings.negativeFormat.replace('%s', '').replace(' %n', '%n')); } if (settings.roundToDecimalPlace >= -1) { settings.minimumDecimalPlaces = (settings.roundToDecimalPlace == -1 || settings.minimumDecimalPlaces > settings.roundToDecimalPlace ? settings.roundToDecimalPlace : settings.minimumDecimalPlaces); } else { settings.minimumDecimalPlaces = 0; } // validate parseType if it exists (for the 'asNumber' settings object graph) if (settings.hasOwnProperty('parseType')) settings.parseType = validateParseType(settings.parseType); // Generate regex for formatting settings.regexArithmetic = new RegExp("[^\\d\\.\\-]", "g"); if (settings.symbol === '') settings.regex = new RegExp("[^\\d" + settings.decimalSymbol + "-]", "g"); else { var symbol = settings.symbol.replace('$', '\\$').replace('.', '\\.'); settings.regex = new RegExp(symbol + "|[^\\d" + settings.decimalSymbol + "-]", "g"); } settings.regexGroupDigit = new RegExp("\\" + settings.digitGroupSymbol, "g"); return settings; } function keyAllowed(ev, decPointCodes) { if (ev.which >= 48 && ev.which <= 57) return true; else if (ev.type == 'keypress' && ev.which == decPointCodes[0]) return (ev.target.value.indexOf(String.fromCharCode(decPointCodes[0])) == -1); else if (ev.type == 'keypress') { return (ev.which < 32 || (ev.which >= 33 && ev.which <= 40) || (decPointCodes[0] != -1 && ev.which == 46)); } else if (ev.type == 'keyup') { if(ev.which < 32 || (ev.which >= 33 && ev.which <= 40)) return false; if(decPointCodes[0] != -1) { switch (ev.which) { case 78: break; // N (Opera 9.63+ maps the "." from the number key section to the "N" key too!) (See: http://unixpapa.com/js/key.html search for ". Del") case decPointCodes[2]: break; // . number block (Opera 9.63+ maps the "." from the number block to the "N" key (78) !!!) case decPointCodes[1]: break; // . default: return true; } } return false; } else return true; } function getRegionOrCulture(region) { var regionInfo = $.formatCurrency.regions[region]; if (regionInfo) { return regionInfo; } else { if (/(\w+)-(\w+)/g.test(region)) { var culture = region.replace(/(\w+)-(\w+)/g, "$1"); return $.formatCurrency.regions[culture]; } } // fallback to extend(null) (i.e. nothing) return null; } function validateParseType(parseType) { switch (parseType.toLowerCase()) { case 'int': return 'Int'; case 'float': return 'Float'; default: throw 'invalid parseType'; } } })(jQuery);