recurly/recurly-js

View on GitHub
lib/util/dom.js

Summary

Maintainability
A
1 hr
Test Coverage
/**
 * dependencies
 */

var slug = require('to-slug-case');
var each = require('component-each');
const Promise = require('promise');
const loadScriptPromise = require('./load-script-promise').default;

/**
 * expose
 */

module.exports = {
  createHiddenInput: createHiddenInput,
  data: data,
  element: element,
  findNodeInParents: findNodeInParents,
  value: value,
  loadLibs,
  loadScript,
  loadStyle,
};

/**
 * Detects whether an object is an html element.
 *
 * @param {HTMLElement|jQuery|String} node
 * @return {HTMLElement|Boolean} node
 */

function element (node) {
  var jQuery = window.jQuery;
  var isJQuery = jQuery && node instanceof jQuery;
  var isElem;

  if (isJQuery || Array.isArray(node)) node = node[0];
  if (typeof node === 'string') node = window.document.querySelector(node);

  if (!node) {
    return false;
  }

  if (typeof HTMLElement !== 'undefined') {
    isElem = node instanceof HTMLElement;
  } else {
    isElem = node.nodeType === 1;
  }

  return isElem && node;
}

/**
 * Gets or sets the value of a given HTML form element
 *
 * supports text inputs, radio inputs, and selects
 *
 * @param {HTMLElement} node
 * @return {String} val value of the element
 */

function value (node, val) {
  if (!element(node)) return null;
  if (typeof val !== 'undefined') {
    return valueSet(node, val);
  } else {
    return valueGet(node);
  }
}

/**
 * Gets an HTMLElement's value property in the context of a form
 *
 * @private
 * @param {HTMLElement} node
 * @return {String} node's value
 */

function valueGet (node) {
  node = element(node);

  var nodeType = node && node.type && node.type.toLowerCase();
  var value;

  if (!nodeType) {
    value = '';
  } else if ('options' in node) {
    let option = node.options[node.selectedIndex] || node.options[0] || { value: '' };
    value = option.value;
  } else if (nodeType === 'checkbox') {
    if (node.checked) value = node.value;
  } else if (nodeType === 'radio') {
    var radios = document.querySelectorAll('input[data-recurly="' + data(node, 'recurly') + '"]');
    each(radios, function (radio) {
      if (radio.checked) value = radio.value;
    });
  } else if ('value' in node) {
    value = node.value;
  }

  return value;
}

/**
 * Updates an element's value property if
 * one exists; else innerText if it exists
 *
 * @private
 * @param {Array[HTMLElement]} nodes
 * @param {Mixed} value
 */

function valueSet (nodes, value) {
  if (!Array.isArray(nodes)) nodes = [nodes];
  each(nodes, function (node) {
    if (!node) return;
    else if ('value' in node)
      node.value = value;
    else if ('textContent' in node)
      node.textContent = value;
    else if ('innerText' in node)
      node.innerText = value;
  });
}

/**
 * Gets or sets a node's data attribute
 *
 * @param {HTMLElement} node
 * @param {String} key
 * @param {Mixed} [value]
 * @return {String} value
 */

function data (node, key, value) {
  node = element(node);
  if (!node) return;
  if (typeof value !== 'undefined') {
    return dataSet(node, key, value);
  } else {
    return dataGet(node, key);
  }
}

/**
 * Gets a node's data attribute
 *
 * @private
 * @param {HTMLElement} node
 * @param {String} key
 * @return {String} attribute value
 */

function dataGet (node, key) {
  if (node.dataset) {
    return node.dataset[key];
  } else {
    return node.getAttribute('data-' + slug(key));
  }
}

/**
 * sets a node's data attribute
 *
 * @private
 * @param {HTMLElement} node
 * @param {String} key
 * @param {Mixed} value
 * @return {String} value
 */

function dataSet (node, key, value) {
  if (node.dataset) {
    node.dataset[key] = value;
  } else {
    node.setAttribute('data-' + slug(key), value);
  }
  return '' + value;
}


/**
 * take an html node and find a parent node with a target tag name
 * example tag names: 'div', 'form'
 *
 * @param  {HTMLElement} node
 * @param  {String} targetTagName the name of the target element type
 * @return {HTMLElement} the found node if available
 */
function findNodeInParents (node, targetTagName) {
  node = element(node);
  if (!node) return null;
  if (node.tagName.match(RegExp(targetTagName, 'i'))) return node;
  return findNodeInParents(node.parentNode, targetTagName);
}

/**
 * Create a hidden input element
 *
 * @return {InputElement}
 */
function createHiddenInput (attributes = {}) {
  let inputType = 'input';
  if (~['button', 'select'].indexOf(attributes.type)) {
    inputType = attributes.type;
    delete attributes.type;
  }
  let hidden = window.document.createElement(inputType);
  if (!('type' in attributes)) attributes.type = 'text';
  if (!('style' in attributes)) attributes.style = 'position: absolute; top: 0px; left: -1000px; opacity: 0;';
  attributes['aria-hidden'] = true;

  Object.keys(attributes).forEach(attr => hidden.setAttribute(attr, attributes[attr]));

  return hidden;
}

function loadLibs (...libUrls) {
  return Promise.all(libUrls.map(url => loadScriptPromise(url)));
}

function loadScript (url, opts) {
  return loadScriptPromise(url, opts);
}

function loadStyle (url, opts) {
  return new Promise((resolve, reject) => {
    const linkEle = document.createElement('link');
    linkEle.onload = () => resolve();
    linkEle.onerror = () => reject();

    opts = opts || {};
    opts.href = url;
    opts.type = 'text/css';
    opts.rel = 'stylesheet';

    Object.entries(opts).forEach(([attr, val]) => {
      linkEle.setAttribute(attr, val);
    });

    document.querySelector('head').appendChild(linkEle);
  });
}