UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

312 lines (257 loc) 8.4 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2006 STZ-IDA, Germany, http://www.stz-ida.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Til Schneider (til132) ************************************************************************ */ /** * A formatter and parser for numbers. * * NOTE: Instances of this class must be disposed of after use * */ qx.Class.define("qx.util.format.NumberFormat", { extend: qx.core.Object, implement: [qx.util.format.IFormat, qx.core.IDisposable], /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * @param locale {String} optional locale to be used * @throws {Error} If the argument is not a string. */ construct(locale) { super(); if (arguments.length > 0) { if (arguments.length === 1) { if (qx.lang.Type.isString(locale)) { this.setLocale(locale); } else { throw new Error("Wrong argument type. String is expected."); } } else { throw new Error("Wrong number of arguments."); } } if (!locale) { this.setLocale(qx.locale.Manager.getInstance().getLocale()); if (qx.core.Environment.get("qx.dynlocale")) { qx.locale.Manager.getInstance().bind("locale", this, "locale"); } } }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { /** * The minimum number of integer digits (digits before the decimal separator). * Missing digits will be filled up with 0 ("19" -> "0019"). */ minimumIntegerDigits: { check: "Number", init: 0 }, /** * The maximum number of integer digits (superfluous digits will be cut off * ("1923" -> "23"). */ maximumIntegerDigits: { check: "Number", nullable: true }, /** * The minimum number of fraction digits (digits after the decimal separator). * Missing digits will be filled up with 0 ("1.5" -> "1.500") */ minimumFractionDigits: { check: "Number", init: 0 }, /** * The maximum number of fraction digits (digits after the decimal separator). * Superfluous digits will cause rounding ("1.8277" -> "1.83") */ maximumFractionDigits: { check: "Number", nullable: true }, /** Whether thousand groupings should be used {e.g. "1,432,234.65"}. */ groupingUsed: { check: "Boolean", init: true }, /** The prefix to put before the number {"EUR " -> "EUR 12.31"}. */ prefix: { check: "String", init: "", event: "changeNumberFormat" }, /** Sets the postfix to put after the number {" %" -> "56.13 %"}. */ postfix: { check: "String", init: "", event: "changeNumberFormat" }, /** Locale used */ locale: { check: "String", init: null, event: "changeLocale" } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { /** * Formats a number. * * @param num {Number} the number to format. * @return {String} the formatted number as a string. */ format(num) { // handle special cases if (isNaN(num)) { return "NaN"; } switch (num) { case Infinity: return "Infinity"; case -Infinity: return "-Infinity"; } var negative = num < 0; if (negative) { num = -num; } if (this.getMaximumFractionDigits() != null) { // Do the rounding var mover = Math.pow(10, this.getMaximumFractionDigits()); num = Math.round(num * mover) / mover; } var integerDigits = String(Math.floor(num)).length; var numStr = "" + num; // Prepare the integer part var integerStr = numStr.substring(0, integerDigits); while (integerStr.length < this.getMinimumIntegerDigits()) { integerStr = "0" + integerStr; } if ( this.getMaximumIntegerDigits() != null && integerStr.length > this.getMaximumIntegerDigits() ) { // NOTE: We cut off even though we did rounding before, because there // may be rounding errors ("12.24000000000001" -> "12.24") integerStr = integerStr.substring( integerStr.length - this.getMaximumIntegerDigits() ); } // Prepare the fraction part var fractionStr = numStr.substring(integerDigits + 1); while (fractionStr.length < this.getMinimumFractionDigits()) { fractionStr += "0"; } if ( this.getMaximumFractionDigits() != null && fractionStr.length > this.getMaximumFractionDigits() ) { // We have already rounded -> Just cut off the rest fractionStr = fractionStr.substring(0, this.getMaximumFractionDigits()); } // Add the thousand groupings if (this.getGroupingUsed()) { var origIntegerStr = integerStr; integerStr = ""; var groupPos; for (groupPos = origIntegerStr.length; groupPos > 3; groupPos -= 3) { integerStr = "" + qx.locale.Number.getGroupSeparator(this.getLocale()) + origIntegerStr.substring(groupPos - 3, groupPos) + integerStr; } integerStr = origIntegerStr.substring(0, groupPos) + integerStr; } // Workaround: prefix and postfix are null even their defaultValue is "" and // allowNull is set to false?!? var prefix = this.getPrefix() ? this.getPrefix() : ""; var postfix = this.getPostfix() ? this.getPostfix() : ""; // Assemble the number var str = prefix + (negative ? "-" : "") + integerStr; if (fractionStr.length > 0) { str += "" + qx.locale.Number.getDecimalSeparator(this.getLocale()) + fractionStr; } str += postfix; return str; }, /** * Parses a number. * * @param str {String} the string to parse. * @return {Double} the number. * @throws {Error} If the number string does not match the number format. */ parse(str) { // use the escaped separators for regexp var groupSepEsc = qx.lang.String.escapeRegexpChars( qx.locale.Number.getGroupSeparator(this.getLocale()) + "" ); var decimalSepEsc = qx.lang.String.escapeRegexpChars( qx.locale.Number.getDecimalSeparator(this.getLocale()) + "" ); var regex = new RegExp( "^(" + qx.lang.String.escapeRegexpChars(this.getPrefix()) + ")?([-+]){0,1}" + "([0-9]{1,3}(?:" + groupSepEsc + "{0,1}[0-9]{3}){0,}){0,1}" + "(" + decimalSepEsc + "\\d+){0,1}(" + qx.lang.String.escapeRegexpChars(this.getPostfix()) + ")?$" ); var hit = regex.exec(str); if (hit == null) { throw new Error( "Number string '" + str + "' does not match the number format" ); } // hit[1] = potential prefix var negative = hit[2] == "-"; var integerStr = hit[3] || "0"; var fractionStr = hit[4]; // hit[5] = potential postfix // Remove the thousand groupings integerStr = integerStr.replace(new RegExp(groupSepEsc, "g"), ""); var asStr = (negative ? "-" : "") + integerStr; if (fractionStr != null && fractionStr.length != 0) { // Remove the leading decimal separator from the fractions string fractionStr = fractionStr.replace(new RegExp(decimalSepEsc), ""); asStr += "." + fractionStr; } return parseFloat(asStr); } }, destruct() { if (qx.core.Environment.get("qx.dynlocale")) { qx.locale.Manager.getInstance().removeRelatedBindings(this); } } });