UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

319 lines (251 loc) 8.33 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 : function(locale) { this.base(arguments); 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 : function(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 : function(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: function() { if (qx.core.Environment.get("qx.dynlocale")) { qx.locale.Manager.getInstance().removeRelatedBindings(this); } } });