Home Manual Reference Source Repository

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;