PageRenderTime 25ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs//0.2/accounting.js

https://gitlab.com/Blueprint-Marketing/cdnjs
JavaScript | 361 lines | 171 code | 56 blank | 134 comment | 30 complexity | 15bf36d11987858a657bedb256b2e792 MD5 | raw file
  1. /*!
  2. * accounting.js javascript library v0.2
  3. * https://josscrowcroft.github.com/accounting.js/
  4. *
  5. * Copyright 2011 by Joss Crowcroft
  6. * Licensed under GPL v3 | http://www.gnu.org/licenses/gpl-3.0.txt
  7. */
  8. var accounting = (function () {
  9. /* ===== Library Settings ===== */
  10. /**
  11. * The library's settings configuration object
  12. *
  13. * Contains default parameters for currency and number formatting
  14. */
  15. var settings = {
  16. currency: {
  17. symbol : "$", // default currency symbol is '$'
  18. format: "%s%v", // controls output: %s = symbol, %v = value (can be object, see docs)
  19. decimal : ".", // decimal point separator
  20. thousand: ",", // thousands separator
  21. precision : 2, // decimal places
  22. grouping : 3 // digit grouping (not implemented yet)
  23. },
  24. number: {
  25. precision : 0, // default precision on numbers is 0
  26. grouping : 3, // digit grouping (not implemented yet)
  27. thousand: ",",
  28. decimal : "."
  29. }
  30. };
  31. /* ===== Internal Helper Methods ===== */
  32. // Store reference to possibly-available ECMAScript 5 methods for later:
  33. var nativeMap = Array.prototype.map,
  34. nativeIsArray = Array.isArray,
  35. toString = Object.prototype.toString;
  36. /**
  37. * Tests whether supplied parameter is a string
  38. * from underscore.js
  39. */
  40. function isString(obj) {
  41. return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
  42. }
  43. /**
  44. * Tests whether supplied parameter is a string
  45. * from underscore.js, delegates to ECMA5's native Array.isArray
  46. */
  47. function isArray(obj) {
  48. return nativeIsArray ? nativeIsArray(obj) : toString.call(obj) === '[object Array]';
  49. }
  50. /**
  51. * Tests whether supplied parameter is a true object
  52. * from underscore.js, delegates to ECMA5's native Array.isArray
  53. */
  54. function isObject(obj) {
  55. return toString.call(obj) === '[object Object]';
  56. }
  57. /**
  58. * Extends an object with a defaults object, similar to underscore's _.defaults
  59. *
  60. * Used for abstracting parameter handling from API methods
  61. */
  62. function defaults(object, defs) {
  63. var key;
  64. // Iterate over object non-prototype properties:
  65. for (key in defs) {
  66. if (defs.hasOwnProperty(key)) {
  67. // Replace values with defaults only if undefined (allow empty/zero values):
  68. if (object[key] == null) object[key] = defs[key];
  69. }
  70. }
  71. return object;
  72. }
  73. /**
  74. * Implementation of `Array.map()` for iteration loops
  75. *
  76. * Returns a new Array as a result of calling `iterator` on each array value.
  77. * Defers to native Array.map if available
  78. */
  79. function map(obj, iterator, context) {
  80. var results = [], i, j;
  81. if (!obj) return results;
  82. // Use native .map method if it exists:
  83. if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
  84. // Fallback for native .map:
  85. for (i = 0, j = obj.length; i < j; i++ ) {
  86. results[i] = iterator.call(context, obj[i], i, obj);
  87. }
  88. return results;
  89. }
  90. /**
  91. * Check and normalise the value of precision (must be positive integer):
  92. */
  93. function checkPrecision(val, base) {
  94. val = Math.round(Math.abs(val));
  95. return isNaN(val)? base : val;
  96. }
  97. /**
  98. * Parses a format string or object and returns format obj for use in rendering
  99. *
  100. * `format` is either a string with the default (positive) format, or object
  101. * containing `pos` (required), `neg` and `zero` values (or a function returning
  102. * either a string or object)
  103. *
  104. * Either string or format.pos must contain "%v" (value) to be valid
  105. */
  106. function checkCurrencyFormat(format) {
  107. var defaults = settings.currency.format;
  108. // Allow function as format parameter (should return string or object):
  109. if ( typeof format === "function" ) format = format();
  110. // Format can be a string, in which case `value` ("%v") must be present:
  111. if ( isString( format ) && format.match("%v") ) {
  112. // Create and return positive, negative and zero formats:
  113. return {
  114. pos: format,
  115. neg: format.replace("-", "").replace("%v", "-%v"),
  116. zero:format
  117. };
  118. // If no format, or object is missing valid positive value, use defaults:
  119. } else if ( !format || !format.pos || !format.pos.match("%v") ) {
  120. // If defaults is a string, casts it to an object for faster checking next time:
  121. return ( !isString( defaults ) ) ? defaults : settings.currency.format = {
  122. pos: defaults,
  123. neg: defaults.replace("%v", "-%v"),
  124. zero:defaults
  125. };
  126. }
  127. // Otherwise, assume format was fine:
  128. return format;
  129. }
  130. /* ===== API Methods ===== */
  131. /**
  132. * Removes currency formatting from a number/array of numbers, returning numeric values
  133. *
  134. * Decimal must be included in the regular expression to match floats (default: ".")
  135. * To do: rewrite this to be a little more elegant and maybe throw useful errors.
  136. */
  137. function unformat(number, decimal) {
  138. // Recursively unformat arrays:
  139. if (isArray(number)) {
  140. return map(number, function(val) {
  141. return unformat(val, decimal);
  142. });
  143. }
  144. // Fails silently (need decent errors):
  145. number = number || 0;
  146. // Default decimal point is "." but could be set to eg. "," in opts:
  147. decimal = decimal || ".";
  148. // Build regex to strip out everything except digits, decimal point and minus sign:
  149. var regex = new RegExp("[^0-9-" + decimal + "]", ["g"]),
  150. unformatted = parseFloat(("" + number).replace(regex, '').replace(decimal, '.'));
  151. // This will fail silently which may cause trouble, let's wait and see:
  152. return !isNaN(unformatted) ? unformatted : 0;
  153. }
  154. /**
  155. * Implementation of toFixed() that treats floats more like decimals
  156. *
  157. * Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present
  158. * problems for accounting- and finance-related software.
  159. */
  160. function toFixed(value, precision) {
  161. precision = checkPrecision(precision, settings.number.precision);
  162. var power = Math.pow(10, precision);
  163. // Multiply up by precision, round accurately, then divide and use native toFixed():
  164. return (Math.round(value * power) / power).toFixed(precision);
  165. }
  166. /**
  167. * Format a number, with comma-separated thousands and custom precision/decimal places
  168. *
  169. * Localise by overriding the precision and thousand / decimal separators
  170. * 2nd parameter `precision` can be an object matching `settings.number`
  171. */
  172. function formatNumber(number, precision, thousand, decimal) {
  173. // Resursively format arrays:
  174. if (isArray(number)) {
  175. return map(number, function(val) {
  176. return formatNumber(val, precision, thousand, decimal);
  177. });
  178. }
  179. // Number isn't an array - do the formatting:
  180. var result, opts;
  181. // Second param precision can be an object matching settings.number:
  182. opts = isObject(precision) ? precision : {
  183. precision: precision,
  184. thousand : thousand,
  185. decimal : decimal
  186. };
  187. // Extend opts with the default values in settings.number:
  188. opts = defaults(opts, settings.number);
  189. // Clean up number and precision:
  190. number = unformat(number);
  191. opts.precision = checkPrecision(opts.precision);
  192. // Do some calc:
  193. var negative = number < 0 ? "-" : "",
  194. base = parseInt(toFixed(Math.abs(number || 0), opts.precision), 10) + "",
  195. mod = base.length > 3 ? base.length % 3 : 0;
  196. // Format the number:
  197. return negative + (mod ? base.substr(0, mod) + opts.thousand : "") + base.substr(mod).replace(/(\d{3})(?=\d)/g, "$1" + opts.thousand) + (opts.precision ? opts.decimal + toFixed(Math.abs(number), opts.precision).split('.')[1] : "");
  198. }
  199. /**
  200. * Format a number into currency
  201. *
  202. * Usage: accounting.formatMoney(number, precision, symbol, thousandsSep, decimalSep, format)
  203. * defaults: (0, 2, "$", ",", ".", "%s%v")
  204. *
  205. * Localise by overriding the symbol, precision, thousand / decimal separators and format
  206. * Second param can be an object matching `settings.currency` which is the easiest way.
  207. *
  208. * To do: tidy up the parameters
  209. */
  210. function formatMoney(number, symbol, precision, thousand, decimal, format) {
  211. // Resursively format arrays:
  212. if (isArray(number)) {
  213. return map(number, function(val){
  214. return formatMoney(val, symbol, precision, thousand, decimal, format);
  215. });
  216. }
  217. // Build options object from second param (if object) or all params, extending defaults:
  218. var opts = defaults(
  219. (isObject(symbol) ? symbol : {
  220. symbol : symbol,
  221. precision : precision,
  222. thousand : thousand,
  223. decimal : decimal,
  224. format : format
  225. }),
  226. settings.currency
  227. );
  228. // Clean up number and precision:
  229. number = unformat(number);
  230. opts.precision = checkPrecision(opts.precision);
  231. // Check format (returns object with `pos`, `neg` and `zero`):
  232. opts.format = checkCurrencyFormat(opts.format);
  233. // Choose which format to use for this value (pos, neg or zero):
  234. opts.format = number > 0 ? opts.format.pos : number < 0 ? opts.format.neg : opts.format.zero;
  235. // Return with currency symbol added:
  236. return opts.format.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(number), opts.precision, opts.thousand, opts.decimal));
  237. }
  238. /**
  239. * Format a list of numbers into an accounting column, padding with whitespace
  240. * to line up currency symbols, thousand separators and decimals places
  241. *
  242. * List should be an array of numbers
  243. * Second parameter can be an object containing keys that match the params
  244. *
  245. * Returns array of accouting-formatted number strings of same length
  246. *
  247. * NB: `white-space:pre` CSS rule is required on the list container to prevent
  248. * browsers from collapsing the whitespace in the output strings.
  249. */
  250. function formatColumn(list, symbol, precision, thousand, decimal, format) {
  251. if (!list) return [];
  252. // Build options object from second param (if object) or all params, extending defaults:
  253. var opts = defaults(
  254. (isObject(symbol) ? symbol : {
  255. symbol : symbol,
  256. precision : precision,
  257. thousand : thousand,
  258. decimal : decimal,
  259. format : format
  260. }),
  261. settings.currency
  262. );
  263. // Check format (returns object with `pos`, `neg` and `zero`), only need 'pos' for now:
  264. opts.format = checkCurrencyFormat(opts.format);
  265. // Clean up precision:
  266. opts.precision = checkPrecision(opts.precision);
  267. var maxLength = 0,
  268. // Whether to pad at start of string or after currency symbol:
  269. padAfterSymbol = opts.format.pos.indexOf("%s") < opts.format.pos.indexOf("%v") ? true : false,
  270. // Format the list according to options, store the length of the longest string:
  271. formatted = map(list, function(val, i) {
  272. if (isArray(val)) {
  273. // Recursively format columns if list is a multi-dimensional array:
  274. return formatColumn(val, opts);
  275. } else {
  276. // Clean up the value
  277. val = unformat(val);
  278. // Choose which format to use for this value (pos, neg or zero):
  279. var format = val > 0 ? opts.format.pos : val < 0 ? opts.format.neg : opts.format.zero,
  280. // Format this value, push into formatted list and save the length:
  281. fVal = format.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(val), opts.precision, opts.thousand, opts.decimal));
  282. if (fVal.length > maxLength) maxLength = fVal.length;
  283. return fVal;
  284. }
  285. });
  286. // Pad each number in the list and send back the column of numbers:
  287. return map(formatted, function(val, i) {
  288. // Only if this is a string (not a nested array, which would have already been padded):
  289. if (isString(val) && val.length < maxLength) {
  290. // Depending on symbol position, pad after symbol or at index 0:
  291. return padAfterSymbol ? val.replace(opts.symbol, opts.symbol+(new Array(maxLength - val.length + 1).join(" "))) : (new Array(maxLength - val.length + 1).join(" ")) + val;
  292. }
  293. return val;
  294. });
  295. }
  296. // Return the library's API:
  297. return {
  298. settings: settings,
  299. formatMoney: formatMoney,
  300. formatNumber: formatNumber,
  301. formatColumn: formatColumn,
  302. toFixed: toFixed,
  303. unformat: unformat
  304. };
  305. }());