PageRenderTime 74ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs//0.3.0/accounting.js

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