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