1 /* 2 * Currency.js - Currency definition 3 * 4 * Copyright © 2012-2015, 2018, 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("./ilib.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 } else { 109 options = {sync: true}; 110 } 111 112 if (typeof(options.sync) === 'undefined') { 113 options.sync = true; 114 } 115 116 this.locale = this.locale || new Locale(); 117 if (typeof(ilib.data.currency) === 'undefined') { 118 Utils.loadData({ 119 name: "currency.json", 120 object: "Currency", 121 locale: "-", 122 sync: this.sync, 123 loadParams: this.loadParams, 124 callback: ilib.bind(this, function(currency) { 125 ilib.data.currency = currency; 126 this._loadLocinfo(options); 127 }) 128 }); 129 } else { 130 this._loadLocinfo(options); 131 } 132 }; 133 134 /** 135 * Return an array of the ids for all ISO 4217 currencies that 136 * this copy of ilib knows about. 137 * 138 * @static 139 * @return {Array.<string>} an array of currency ids that this copy of ilib knows about. 140 */ 141 Currency.getAvailableCurrencies = function() { 142 var ret = [], 143 cur, 144 currencies = new ResBundle({ 145 name: "currency" 146 }).getResObj(); 147 148 for (cur in currencies) { 149 if (cur && currencies[cur]) { 150 ret.push(cur); 151 } 152 } 153 154 return ret; 155 }; 156 157 Currency.prototype = { 158 /** 159 * @private 160 */ 161 _loadLocinfo: function(options) { 162 new LocaleInfo(this.locale, { 163 sync: options.sync, 164 loadParams: options.loadParams, 165 onLoad: ilib.bind(this, function (li) { 166 var currInfo; 167 168 this.locinfo = li; 169 if (this.code) { 170 currInfo = ilib.data.currency[this.code]; 171 if (!currInfo) { 172 if (options.sync) { 173 throw "currency " + this.code + " is unknown"; 174 } else if (typeof(options.onLoad) === "function") { 175 options.onLoad(undefined); 176 return; 177 } 178 } 179 } else if (this.sign) { 180 currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... 181 if (typeof(currInfo) !== 'undefined') { 182 this.code = this.sign; 183 } else { 184 this.code = this.locinfo.getCurrency(); 185 currInfo = ilib.data.currency[this.code]; 186 if (currInfo.sign !== this.sign) { 187 // current locale does not use the sign, so search for it 188 for (var cur in ilib.data.currency) { 189 if (cur && ilib.data.currency[cur]) { 190 currInfo = ilib.data.currency[cur]; 191 if (currInfo.sign === this.sign) { 192 // currency data is already ordered so that the currency with the 193 // largest circulation is at the beginning, so all we have to do 194 // is take the first one in the list that matches 195 this.code = cur; 196 break; 197 } 198 } 199 } 200 } 201 } 202 } 203 204 if (!currInfo || !this.code) { 205 this.code = this.locinfo.getCurrency(); 206 currInfo = ilib.data.currency[this.code]; 207 } 208 209 this.name = currInfo.name; 210 this.fractionDigits = currInfo.decimals; 211 this.sign = currInfo.sign; 212 213 if (typeof(options.onLoad) === 'function') { 214 options.onLoad(this); 215 } 216 }) 217 }); 218 }, 219 220 /** 221 * Return the ISO 4217 currency code for this instance. 222 * @return {string} the ISO 4217 currency code for this instance 223 */ 224 getCode: function () { 225 return this.code; 226 }, 227 228 /** 229 * Return the default number of fraction digits that is typically used 230 * with this type of currency. 231 * @return {number} the number of fraction digits for this currency 232 */ 233 getFractionDigits: function () { 234 return this.fractionDigits; 235 }, 236 237 /** 238 * Return the sign commonly used to represent this currency. 239 * @return {string} the sign commonly used to represent this currency 240 */ 241 getSign: function () { 242 return this.sign; 243 }, 244 245 /** 246 * Return the name of the currency in English. 247 * @return {string} the name of the currency in English 248 */ 249 getName: function () { 250 return this.name; 251 }, 252 253 /** 254 * Return the locale for this currency. If the options to the constructor 255 * included a locale property in order to find the currency that is appropriate 256 * for that locale, then the locale is returned here. If the options did not 257 * include a locale, then this method returns undefined. 258 * @return {Locale} the locale used in the constructor of this instance, 259 * or undefined if no locale was given in the constructor 260 */ 261 getLocale: function () { 262 return this.locale; 263 } 264 }; 265 266 module.exports = Currency; 267