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