src/plugins/easycurrency/EasyCurrency.js
import Evee from 'evee';
import Money from 'js-money';
import config from './config';
import state from './state';
import log from 'log';
import MoneySpanSet from './MoneySpanSet';
import MoneySpan from './MoneySpan';
import GeoserviceResolver from './resolvers/GeoserviceResolver';
import DefaultFormatter from './formatters/DefaultFormatter';
import Formatter from './formatters/Formatter';
import CurrencyResolver from './resolvers/CurrencyResolver';
import SelectHelper from './helpers/SelectHelper';
import Session from './Session';
import GeoService from '~/plugins/geoservice/GeoService';
/** @ignore */
const TAG = 'EasyCurrency';
/** @ignore */
var instanceCount = 0;
/**
* Plugin for automatic currency conversion, tax handling, etc.
* @extends {evee}
* @private
*/
class EasyCurrency extends Evee {
/**
* Creates a new instance of EasyCurrency.
* This class is intended to be used as a singleton, and has global
* side-effects - do not create multiple instances.
*/
constructor() {
super();
instanceCount++;
var styles = [];
if (instanceCount > 1) {
log.warn(TAG, 'More than one instance of EasyCurrency has been created. This may cause unexpected behavior.');
}
/** @type {src/plugins/easycurrency/config.js~Config} */
this._config = config;
/** @type {src/plugins/easycurrency/resolvers/CurrencyResolver.js~CurrencyResolver} */
this._resolver = null;
/** @type {src/plugins/easycurrency/state.js~State} */
this._state = state;
/** @type {src/plugins/easycurrency/MoneySpanSet.js~MoneySpanSet} */
this._moneySpanSet = new MoneySpanSet;
/** @type {src/plugins/easycurrency/formatters/Formatter.js~Formatter} */
this._formatter = new DefaultFormatter;
/** @type {src/plugins/easycurrency/Session.js~Session} */
this._session = new Session;
/** @type {src/plugins/easycurrency/helpers/SelectHelper.js~SelectHelper} */
this.selectHelper = new SelectHelper(this);
}
/**
* Uses a default {@link GeoserviceResolver} as the currency resolver.
*/
useGeoserviceResolver() {
this.setCurrencyResolver(new GeoserviceResolver);
}
/**
* Start running EasyCurrency on the current page.
* @param {Object|src/plugins/easycurrency/config.js~Config} config
* @emits {Object|src/plugins/easycurrency/config.js~Config} beforeInitialize(config)
* @emits {Object|src/plugins/easycurrency/config.js~Config} initialize(config)
*/
initialize(config) {
this.emit('beforeInitialize', config);
log.debug(TAG, 'Initialized with config', config);
if (config) {
this._config = Object.assign({}, this._config, config);
}
var currency = this._session.getCurrency();
var fromSession = currency;
currency = currency || this._config.defaultCurrency;
this.setActiveCurrency(this._session.getCurrency() || this._config.defaultCurrency);
this.emit('initialize', config);
if (!fromSession && this._config.useGeoForCurrency) {
var geoService = new GeoService;
geoService.lookupGeo().then((result) => {
log.debug(TAG, 'Got currency from geoservice: ' + result.currency);
this.setActiveCurrency(result.currency);
});
}
}
/**
* Sets the active currency, and refreshs any money spans.
* @param {String} currency Currency code
* @emits {String} beforeCurrencyChanged(currency)
* @emits {String} currencyChanged(currency)
*/
setActiveCurrency(currency) {
this.emit('beforeCurrencyChanged', currency);
log.debug(TAG, 'Set active currency', currency);
if (!currency ||
(this._config.allowedCurrencies != 'any'
&& this._config.allowedCurrencies.indexOf(currency) == -1)) {
log.debug(TAG, 'Currency not in whitelist, switching to base currency.');
currency = this._config.defaultCurrency;
}
this._state.currency = currency;
this._session.saveCurrency(currency);
this.parse();
this.render();
this.emit('currencyChanged', currency);
}
/**
* Returns the active currency.
* @return {String}
*/
getActiveCurrency() {
return this._state.currency;
}
/**
* Returns the available currency codes.
* @return {String[]}
*/
async getAvailableCurrencies() {
return this._resolver.listCurrencyCodes();
}
/**
* Attempts to find new money spans on the page.
* Change moneySpanSelectors / moneySpanParser config options to modify
* how money spans are parsed.
* @emits {null} beforeParse
* @emits {null} parse
*/
parse() {
this.emit('beforeParse');
log.debug(TAG, 'Parsing');
var els = [];
for (let selector of this._config.moneySpanSelectors) {
var els = document.querySelectorAll(selector);
for (let i = 0; i < els.length; i++) {
var el = els[i];
this._processElement(el);
}
}
this.emit('parse');
}
/**
* @param {DOMNode} el - Element to process
*/
_processElement(el) {
var money = this._config.moneySpanParser(el, this);
var moneySpan = new MoneySpan(money, this);
moneySpan.setElement(el);
this._moneySpanSet.add(moneySpan);
}
/**
* Forces all money spans to re-render.
* @emits {null} beforeRender
* @emits {null} render
*/
render() {
this.emit('beforeRender');
log.debug(TAG, 'Rendering');
for (let moneySpan of this._moneySpanSet.list()) {
moneySpan.render();
}
this.emit('render');
}
/**
* Returns the current state.
* @return {src/plugins/easycurrency/state.js~State}
*/
getState() {
return this._state;
}
/**
* Format a value as a currency
* @param {js-money|Number} amount
* @param {String|null} [currency] If null, uses current currency.
* @return {String}
*/
format(amount, currency) {
if (typeof amount == 'number') {
amount = new Money(amount, currency || this._state.currency);
}
var str = this._formatter.format(amount.amount, amount.currency);
return str;
}
/**
* Convert an amount to another currency.
* @param {js-money|Number} amount
* @param {String} [to] - If null, uses current currency
* @param {null|String} [from] Ignored if amount is a Money object.
* @return {Promise<js-money>}
*/
async convert(amount, to, from) {
if (typeof amount == 'number') {
amount = new Money(amount, from || this._state.currency);
}
var rate = await this._resolver.getConversionRate(amount.currency, to || this._state.currency);
return new Money(amount.multiply(rate).amount, Money[to || this._state.currency]);
}
/**
* Adds the current tax rate to the given amount
* @param {js-money|Number} amount
* @return {Promise<js-money>}
*/
async addTax(amount) {
if (typeof amount == 'number') {
amount = new Money(amount, this._state.currency);
}
return amount.multiply(1 + this._state.taxRate);
}
/**
* Sets the current tax rate.
* @param {type} taxRate - Tax rate as a decimal percentage (i.e. 20% = 0.2)
* @emits {null} beforeChangeTaxRate
* @emits {null} taxRateChanged
*/
setTaxRate(taxRate) {
this.emit('beforeChangeTaxRate');
this._state.taxRate = taxRate;
this.parse();
this.render();
this.emit('taxRateChanged');
}
/**
* Overrides the default currency resolver, which provides information
* about the available currencies.
* @param {src/plugins/easycurrency/resolvers/CurrencyResolver.js~CurrencyResolver} resolver
* @emits {src/plugins/easycurrency/resolvers/CurrencyResolver.js~CurrencyResolver} currencyResolverChanged(resolver)
*/
setCurrencyResolver(resolver) {
this._resolver = resolver;
this.emit('currencyResolverChanged', resolver);
}
/**
* Overrides the default formatter.
* @param {src/plugins/easycurrency/formatters/Formatter.js~Formatter} formatter
* @emits {src/plugins/easycurrency/formatters/Formatter.js~Formatter} formatterChanged(formatter)
*/
setFormatter(formatter) {
this._formatter = formatter;
this.emit('formatterChanged', resolver);
}
}
EasyCurrency.formatters = {
DefaultFormatter,
Formatter
};
EasyCurrency.helpers = {
SelectHelper,
};
EasyCurrency.resolvers = {
CurrencyResolver,
GeoserviceResolver
};
export default EasyCurrency;