|
| 1 | +/** |
| 2 | + * Javascript-number-formatter |
| 3 | + * Lightweight & Fast JavaScript Number Formatter |
| 4 | + * |
| 5 | + * @preserve IntegraXor Web SCADA - JavaScript Number Formatter (http://www.integraxor.com/) |
| 6 | + * @author KPL |
| 7 | + * @maintainer Rob Garrison |
| 8 | + * @copyright 2018 ecava |
| 9 | + * @license MIT |
| 10 | + * @link http://mottie.github.com/javascript-number-formatter/ |
| 11 | + * @version 2.0.0 |
| 12 | + */ |
| 13 | +const maskRegex = /[0-9\-+#]/; |
| 14 | +const notMaskRegex = /[^\d\-+#]/g; |
| 15 | + |
| 16 | +function getIndex(mask) { |
| 17 | + return mask.search(maskRegex); |
| 18 | +} |
| 19 | + |
| 20 | +function processMask(mask = "#.##") { |
| 21 | + const maskObj = {}; |
| 22 | + const len = mask.length; |
| 23 | + const start = getIndex(mask); |
| 24 | + maskObj.prefix = start > 0 ? mask.substring(0, start) : ""; |
| 25 | + |
| 26 | + // Reverse string: not an ideal method if there are surrogate pairs |
| 27 | + const end = getIndex(mask.split("").reverse().join("")); |
| 28 | + const offset = len - end; |
| 29 | + const substr = mask.substring(offset, offset + 1); |
| 30 | + // Add 1 to offset if mask has a trailing decimal/comma |
| 31 | + const indx = offset + ((substr === "." || (substr === ",")) ? 1 : 0); |
| 32 | + maskObj.suffix = end > 0 ? mask.substring(indx, len) : ""; |
| 33 | + |
| 34 | + maskObj.mask = mask.substring(start, indx); |
| 35 | + maskObj.maskHasNegativeSign = maskObj.mask.charAt(0) === "-"; |
| 36 | + maskObj.maskHasPositiveSign = maskObj.mask.charAt(0) === "+"; |
| 37 | + |
| 38 | + // Search for group separator & decimal; anything not digit, |
| 39 | + // not +/- sign, and not # |
| 40 | + let result = maskObj.mask.match(notMaskRegex); |
| 41 | + // Treat the right most symbol as decimal |
| 42 | + maskObj.decimal = (result && result[result.length - 1]) || "."; |
| 43 | + // Treat the left most symbol as group separator |
| 44 | + maskObj.separator = (result && result[1] && result[0]) || ","; |
| 45 | + |
| 46 | + // Split the decimal for the format string if any |
| 47 | + result = maskObj.mask.split(maskObj.decimal); |
| 48 | + maskObj.integer = result[0]; |
| 49 | + maskObj.fraction = result[1]; |
| 50 | + return maskObj; |
| 51 | +} |
| 52 | + |
| 53 | +function processValue(value, maskObj, options) { |
| 54 | + let isNegative = false; |
| 55 | + const valObj = { |
| 56 | + value |
| 57 | + }; |
| 58 | + if (value < 0) { |
| 59 | + isNegative = true; |
| 60 | + // Process only abs(), and turn on flag. |
| 61 | + valObj.value = -valObj.value; |
| 62 | + } |
| 63 | + valObj.sign = isNegative ? "-" : ""; |
| 64 | + |
| 65 | + // Fix the decimal first, toFixed will auto fill trailing zero. |
| 66 | + valObj.value = Number(valObj.value).toFixed(maskObj.fraction && maskObj.fraction.length); |
| 67 | + // Convert number to string to trim off *all* trailing decimal zero(es) |
| 68 | + valObj.value = valObj.value.toString(); |
| 69 | + |
| 70 | + // Fill back any trailing zero according to format |
| 71 | + // look for last zero in format |
| 72 | + const posTrailZero = maskObj.fraction && maskObj.fraction.lastIndexOf("0"); |
| 73 | + |
| 74 | + let [valInteger = "0", valFraction = ""] = valObj.value.split("."); |
| 75 | + if (!valFraction || (valFraction && valFraction.length <= posTrailZero)) { |
| 76 | + valFraction = (Number("0." + valFraction).toFixed(posTrailZero + 1)).replace("0.", ""); |
| 77 | + } |
| 78 | + valObj.integer = valInteger; |
| 79 | + valObj.fraction = valFraction; |
| 80 | + addSeparators(valObj, maskObj); |
| 81 | + |
| 82 | + // Remove negative sign if result is zero |
| 83 | + if (valObj.result === "0" || valObj.result === "") { |
| 84 | + // Remove negative sign if result is zero |
| 85 | + isNegative = false; |
| 86 | + valObj.sign = ""; |
| 87 | + } |
| 88 | + |
| 89 | + if (!isNegative && maskObj.maskHasPositiveSign) { |
| 90 | + valObj.sign = "+"; |
| 91 | + } else if (isNegative && maskObj.maskHasPositiveSign) { |
| 92 | + valObj.sign = "-"; |
| 93 | + } else if (isNegative) { |
| 94 | + valObj.sign = options && options.enforceMaskSign && !maskObj.maskHasNegativeSign ? "" : "-"; |
| 95 | + } |
| 96 | + return valObj; |
| 97 | +} |
| 98 | + |
| 99 | +function addSeparators(valObj, maskObj) { |
| 100 | + valObj.result = ""; |
| 101 | + // Look for separator |
| 102 | + const szSep = maskObj.integer.split(maskObj.separator); |
| 103 | + // Join back without separator for counting the pos of any leading 0 |
| 104 | + const maskInteger = szSep.join(""); |
| 105 | + |
| 106 | + const posLeadZero = maskInteger && maskInteger.indexOf("0"); |
| 107 | + if (posLeadZero > -1) { |
| 108 | + while (valObj.integer.length < (maskInteger.length - posLeadZero)) { |
| 109 | + valObj.integer = "0" + valObj.integer; |
| 110 | + } |
| 111 | + } else if (Number(valObj.integer) === 0) { |
| 112 | + valObj.integer = ""; |
| 113 | + } |
| 114 | + |
| 115 | + // Process the first group separator from decimal (.) only, the rest ignore. |
| 116 | + // get the length of the last slice of split result. |
| 117 | + const posSeparator = (szSep[1] && szSep[szSep.length - 1].length); |
| 118 | + if (posSeparator) { |
| 119 | + const len = valObj.integer.length; |
| 120 | + const offset = len % posSeparator; |
| 121 | + for (let indx = 0; indx < len; indx++) { |
| 122 | + valObj.result += valObj.integer.charAt(indx); |
| 123 | + // -posSeparator so that won't trail separator on full length |
| 124 | + if (!((indx - offset + 1) % posSeparator) && indx < len - posSeparator) { |
| 125 | + valObj.result += maskObj.separator; |
| 126 | + } |
| 127 | + } |
| 128 | + } else { |
| 129 | + valObj.result = valObj.integer; |
| 130 | + } |
| 131 | + valObj.result += (maskObj.fraction && valObj.fraction) ? maskObj.decimal + valObj.fraction : ""; |
| 132 | + return valObj; |
| 133 | +} |
| 134 | + |
| 135 | +var format = (mask, value, options = {}) => { |
| 136 | + if (!mask || isNaN(Number(value))) { |
| 137 | + // Invalid inputs |
| 138 | + return value; |
| 139 | + } |
| 140 | + const maskObj = processMask(mask); |
| 141 | + const valObj = processValue(value, maskObj, options); |
| 142 | + return maskObj.prefix + valObj.sign + valObj.result + maskObj.suffix; |
| 143 | +}; |
| 144 | + |
| 145 | +export default format; |
0 commit comments