1 /* 2 * Currency.js - Currency definition 3 * 4 * Copyright © 2012-2015, 2018, 2022 JEDLSoft 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 // !data currency 21 22 var ilib = require("../index.js"); 23 var Utils = require("./Utils.js"); 24 var Locale = require("./Locale.js"); 25 var LocaleInfo = require("./LocaleInfo.js"); 26 var ResBundle = require("./ResBundle.js"); 27 28 /** 29 * @class 30 * Create a new currency information instance. Instances of this class encode 31 * information about a particular currency.<p> 32 * 33 * Note: that if you are looking to format currency for display, please see 34 * the number formatting class {NumFmt}. This class only gives information 35 * about currencies.<p> 36 * 37 * The options can contain any of the following properties: 38 * 39 * <ul> 40 * <li><i>locale</i> - specify the locale for this instance 41 * <li><i>code</i> - find info on a specific currency with the given ISO 4217 code 42 * <li><i>sign</i> - search for a currency that uses this sign 43 * <li><i>onLoad</i> - a callback function to call when the currency data is fully 44 * loaded. When the onLoad option is given, this class will attempt to 45 * load any missing locale data using the ilib loader callback. 46 * When the constructor is done (even if the data is already preassembled), the 47 * onLoad function is called with the current instance as a parameter, so this 48 * callback can be used with preassembled or dynamic loading or a mix of the two. 49 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 50 * asynchronously. If this option is given as "false", then the "onLoad" 51 * callback must be given, as the instance returned from this constructor will 52 * not be usable for a while. 53 * <li><i>loadParams</i> - an object containing parameters to pass to the 54 * loader callback function when locale data is missing. The parameters are not 55 * interpretted or modified in any way. They are simply passed along. The object 56 * may contain any property/value pairs as long as the calling code is in 57 * agreement with the loader callback function as to what those parameters mean. 58 * </ul> 59 * 60 * When searching for a currency by its sign, this class cannot guarantee 61 * that it will return info about a specific currency. The reason is that currency 62 * signs are sometimes shared between different currencies and the sign is 63 * therefore ambiguous. If you need a 64 * guarantee, find the currency using the code instead.<p> 65 * 66 * The way this class finds a currency by sign is the following. If the sign is 67 * unambiguous, then 68 * the currency is returned. If there are multiple currencies that use the same 69 * sign, and the current locale uses that sign, then the default currency for 70 * the current locale is returned. If there are multiple, but the current locale 71 * does not use that sign, then the currency with the largest circulation is 72 * returned. For example, if you are in the en-GB locale, and the sign is "$", 73 * then this class will notice that there are multiple currencies with that 74 * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will 75 * pick the one with the largest circulation, which in this case is the US Dollar 76 * (USD).<p> 77 * 78 * If neither the code or sign property is set, the currency that is most common 79 * for the locale 80 * will be used instead. If the locale is not set, the default locale will be used. 81 * If the code is given, but it is not found in the list of known currencies, this 82 * constructor will throw an exception. If the sign is given, but it is not found, 83 * this constructor will default to the currency for the current locale. If both 84 * the code and sign properties are given, then the sign property will be ignored 85 * and only the code property used. If the locale is given, but it is not a known 86 * locale, this class will default to the default locale instead.<p> 87 * 88 * 89 * @constructor 90 * @param options {Object} a set of properties to govern how this instance is constructed. 91 * @throws "currency xxx is unknown" when the given currency code is not in the list of 92 * known currencies. xxx is replaced with the requested code. 93 */ 94 var Currency = function (options) { 95 if (options) { 96 if (options.code) { 97 this.code = options.code; 98 } 99 if (options.locale) { 100 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 101 } 102 if (options.sign) { 103 this.sign = options.sign; 104 } 105 if (options.loadParams) { 106 this.loadParams = options.loadParams; 107 } 108 options.sync = (typeof(options.sync) !== 'undefined') ? options.sync : true; 109 } else { 110 options = {sync: true}; 111 } 112 113 this.locale = this.locale || new Locale(); 114 if (typeof(ilib.data.currency) === 'undefined') { 115 Utils.loadData({ 116 name: "currency.json", 117 object: "Currency", 118 locale: "-", 119 sync: options.sync, 120 loadParams: this.loadParams, 121 callback: ilib.bind(this, function(currency) { 122 ilib.data.currency = currency; 123 this._loadLocinfo(options); 124 }) 125 }); 126 } else { 127 this._loadLocinfo(options); 128 } 129 }; 130 131 /** 132 * Return an array of the ids for all ISO 4217 currencies that 133 * this copy of ilib knows about. 134 * 135 * @static 136 * @return {Array.<string>} an array of currency ids that this copy of ilib knows about. 137 */ 138 Currency.getAvailableCurrencies = function() { 139 var ret = [], 140 cur, 141 currencies = new ResBundle({ 142 name: "currency" 143 }).getResObj(); 144 145 for (cur in currencies) { 146 if (cur && currencies[cur]) { 147 ret.push(cur); 148 } 149 } 150 151 return ret; 152 }; 153 154 Currency.prototype = { 155 /** 156 * @private 157 */ 158 _loadLocinfo: function(options) { 159 new LocaleInfo(this.locale, { 160 sync: options.sync, 161 loadParams: options.loadParams, 162 onLoad: ilib.bind(this, function (li) { 163 var currInfo; 164 165 this.locinfo = li; 166 if (this.code) { 167 currInfo = ilib.data.currency[this.code]; 168 if (!currInfo) { 169 if (options.sync) { 170 throw "currency " + this.code + " is unknown"; 171 } else if (typeof(options.onLoad) === "function") { 172 options.onLoad(undefined); 173 return; 174 } 175 } 176 } else if (this.sign) { 177 currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... 178 if (typeof(currInfo) !== 'undefined') { 179 this.code = this.sign; 180 } else { 181 this.code = this.locinfo.getCurrency(); 182 currInfo = ilib.data.currency[this.code]; 183 if (currInfo.sign !== this.sign) { 184 // current locale does not use the sign, so search for it 185 for (var cur in ilib.data.currency) { 186 if (cur && ilib.data.currency[cur]) { 187 currInfo = ilib.data.currency[cur]; 188 if (currInfo.sign === this.sign) { 189 // currency data is already ordered so that the currency with the 190 // largest circulation is at the beginning, so all we have to do 191 // is take the first one in the list that matches 192 this.code = cur; 193 break; 194 } 195 } 196 } 197 } 198 } 199 } 200 201 if (!currInfo || !this.code) { 202 this.code = this.locinfo.getCurrency(); 203 currInfo = ilib.data.currency[this.code]; 204 } 205 206 this.name = currInfo.name; 207 this.fractionDigits = currInfo.decimals; 208 this.sign = currInfo.sign; 209 210 if (typeof(options.onLoad) === 'function') { 211 options.onLoad(this); 212 } 213 }) 214 }); 215 }, 216 217 /** 218 * Return the ISO 4217 currency code for this instance. 219 * @return {string} the ISO 4217 currency code for this instance 220 */ 221 getCode: function () { 222 return this.code; 223 }, 224 225 /** 226 * Return the default number of fraction digits that is typically used 227 * with this type of currency. 228 * @return {number} the number of fraction digits for this currency 229 */ 230 getFractionDigits: function () { 231 return this.fractionDigits; 232 }, 233 234 /** 235 * Return the sign commonly used to represent this currency. 236 * @return {string} the sign commonly used to represent this currency 237 */ 238 getSign: function () { 239 return this.sign; 240 }, 241 242 /** 243 * Return the name of the currency in English. 244 * @return {string} the name of the currency in English 245 */ 246 getName: function () { 247 return this.name; 248 }, 249 250 /** 251 * Return the locale for this currency. If the options to the constructor 252 * included a locale property in order to find the currency that is appropriate 253 * for that locale, then the locale is returned here. If the options did not 254 * include a locale, then this method returns undefined. 255 * @return {Locale} the locale used in the constructor of this instance, 256 * or undefined if no locale was given in the constructor 257 */ 258 getLocale: function () { 259 return this.locale; 260 } 261 }; 262 263 module.exports = Currency; 264