recurly/recurly-js

View on GitHub
lib/recurly/pricing/subscription/attachment.js

Summary

Maintainability
D
2 days
Test Coverage
import Emitter from 'component-emitter';
import Promise from 'promise';
import find from 'component-find';
import dom from '../../../util/dom';

const debug = require('debug')('recurly:pricing:attachment');

const INIT_RUN = 'init-all';

/**
 * bind a dom element to pricing values
 *
 * @param {SubscriptionPricing} pricing
 * @param {HTMLElement} container Element on which to attach
 */

export default class Attachment extends Emitter {
  constructor (pricing, container) {
    super();
    this.pricing = pricing;
    this.container = dom.element(container);

    if (!this.container) throw new Error('invalid dom element');

    this.change = this.change.bind(this);
    this.update = this.update.bind(this);

    this.pricing.on('change', this.update);

    this.elements.all.forEach(elem => {
      elem.addEventListener('change', this.change);
      elem.addEventListener('propertychange', this.change);
    });

    this.change(INIT_RUN);
  }

  get elements () {
    if (this._elements) return this._elements;

    let elements = { all: [].slice.call(this.container.querySelectorAll('[data-recurly]')) };

    elements.all.forEach(node => {
      const name = dom.data(node, 'recurly');
      if (!(name in elements)) elements[name] = [];
      elements[name].push(node);
    });

    this._elements = elements;
    return elements;
  }

  /**
   * Handles input element changes
   */

  change (event) {
    debug('change');

    const elems = this.elements;
    const target = event.target || event.srcElement;
    const targetName = dom.data(target, 'recurly');
    const updating = name => name in elems && event === INIT_RUN || targetName === name;
    const updateAddon = elems.addon && updating('addon');
    const updateAddress = updating('country') || updating('postal_code');
    const updateCurrency = updating('currency');
    const updateCoupon = elems.coupon && (updating('coupon') || updating('plan'));
    const updateGiftcard = elems.gift_card && updating('gift_card');
    const updateShippingAddress = updating('shipping_address.country') || updating('shipping_address.postal_code');
    const updateTax = ['vat_number', 'tax_code', 'tax_amount.now', 'tax_amount.next'].some(updating);

    let pricing = this.pricing.plan(dom.value(elems.plan), { quantity: dom.value(elems.plan_quantity) });

    if (updateCurrency) {
      pricing = pricing.currency(dom.value(elems.currency));
    }

    if (updateAddon) {
      pricing = pricing.then(() => {
        return Promise.all(elems.addon.map(elem => {
          const plan = this.pricing.items.plan;
          const code = dom.data(elem, 'recurlyAddon');
          const quantity = dom.value(elem);

          if (plan.addons && find(plan.addons, { code })) {
            return this.pricing.addon(code, { quantity });
          }
        }));
      });
    }

    if (updateCoupon) {
      pricing = pricing.coupon(dom.value(elems.coupon).trim()).then(null, ignoreNotFound);
    }

    if (updateGiftcard) {
      pricing = pricing.giftcard(dom.value(elems.gift_card).trim()).then(null, ignoreNotFound);
    }

    if (updateAddress) {
      pricing = pricing.address({
        country: dom.value(elems.country),
        postal_code: dom.value(elems.postal_code)
      });
    }

    if (updateShippingAddress) {
      pricing = pricing.shippingAddress({
        country: dom.value(elems['shipping_address.country']),
        postal_code: dom.value(elems['shipping_address.postal_code'])
      });
    }

    if (updateTax) {
      let taxParams = {
        vatNumber: dom.value(elems.vat_number),
        taxCode: dom.value(elems.tax_code)
      };
      if (elems['tax_amount.now'] || elems['tax_amount.next']) {
        taxParams.amount = {
          now: (dom.value(elems['tax_amount.now']) || 0),
          next: (dom.value(elems['tax_amount.next']) || 0)
        };
      }
      pricing = pricing.tax(taxParams);
    }

    this.pricing = pricing.done(() => event === INIT_RUN && this.emit('ready'));
  }

  /**
   * Updates output elements
   */

  update (price) {
    const elems = this.elements;

    dom.value(elems.currency_code, price.currency.code);
    dom.value(elems.currency_symbol, price.currency.symbol);

    dom.value(elems.plan_base, price.base.plan.unit);
    dom.value(elems.plan_interval, price.base.plan.interval);

    ['plan', 'addons', 'discount', 'setup_fee', 'subtotal', 'tax', 'total', 'gift_card'].forEach(value => {
      dom.value(elems[value + '_now'], price.now[value]);
      dom.value(elems[value + '_next'], price.next[value]);
    });

    if (elems.addon_price) {
      elems.addon_price.forEach(elem => {
        const addonPrice = price.base.addons[dom.data(elem, 'recurlyAddon')];
        if (addonPrice) dom.value(elem, addonPrice);
      });
    }
  }

  detach () {
    this.pricing.off('change', this.update);

    this.elements.all.forEach(elem => {
      elem.removeEventListener('change', this.change);
      elem.removeEventListener('propertychange', this.change);
    });
  }
}

export function ignoreNotFound (err) {
  if (err.code === 'not-found') return;
  else throw err;
}