Source: components/widgets/modal-light/_modal-light.js

'use strict';

/**
 * Lightweight modal designed to be easily ported to SPA frameworks et al.
 * @namespace modal_light
 * @memberof UCASDesignFramework
 * @param {object} global - UCASDesignFramework object.
 * @param {object} _u - UCASUtilities object.
 */
(function (global, _u) {
  var focusTracker
  var attributePrefix = 'data-'
  /**
   * Initialise plugin
   * @function init
   * @memberof! UCASDesignFramework.modal_light
   * @public
  */
  function init () {
    var modalTriggers = document.querySelectorAll('[modal-light-trigger]')
    if (modalTriggers.length > 0) {
      attributePrefix = ''
    } else {
      modalTriggers = document.querySelectorAll('[' + attributePrefix + 'modal-light-trigger]')
    }

    _u.forEach(modalTriggers, function (i, el) {
      el.addEventListener('click', function (event) {
        event.preventDefault()
        showModal(event)
      }, false)
    })

    // Escape key should close modal for accessibility
    document.onkeydown = function (ev) {
      ev = ev || window.event
      var activeModal = document.querySelector('[' + attributePrefix + 'modal-light-state="visible"] aside')
      if (ev.keyCode === 27 && activeModal) {
        hideModal()
      }
    }
    // Trap the focus
    focusTrapper()
  }

  /**
   * Remove event listeners and close all open modals.
   * @function destroy
   * @memberof! UCASDesignFramework.modal_light
   * @public
   */
  function destroy () {
    var modalTriggers = document.querySelectorAll('[' + attributePrefix + 'modal-light-trigger]')
    _u.forEach(modalTriggers, function (i, el) {
      el.removeEventListener('click', showModal)
    })
    var targets = document.querySelectorAll('[' + attributePrefix + 'modal-light-close]')
    _u.forEach(targets, function (i, el) {
      el.removeEventListener('click', hideModal)
    })
    var openModal = document.querySelector('[' + attributePrefix + 'modal-light-state="visible"]')
    if (openModal) {
      openModal.setAttribute(attributePrefix + 'modal-light-state', 'hidden')
    }
  }

  /**
   * Works out which modal is at the top.
   * @returns {Node} - the topmost modal
   */
  function topModal () {
    return document.querySelectorAll('[' + attributePrefix + 'modal-light-state="visible"]')[document.querySelectorAll('[' + attributePrefix + 'modal-light-state="visible"]').length - 1]
  }

  /**
   * Displays a modal
   * @function showModal
   * @memberof! UCASDesignFramework.modal_light
   * @param {string|event} modalID ID of modal to show or click event who's target contains a data-modal-id attribute
   */
  function showModal (modalID) {
    focusTracker = modalID
    if (typeof modalID === 'object') {
      modalID = modalID.target.attributes[attributePrefix + 'modal-light-id'].value
    } else if (typeof modalID !== 'string') {
      console.error('Can\'t open modal!', modalID, 'should be a string or event!')
      // @todo handle errors globally see DF-526
      return
    }
    var targetModal = document.getElementById(modalID)
    if (!targetModal) {
      console.error('Modal with ID ' + modalID + ' does not exist.')
      return
    }
    targetModal.setAttribute(attributePrefix + 'modal-light-state', 'visible')
    targetModal.querySelector('aside').focus()
    attachCloseListener(targetModal)

    /**
     * @event modalLightShow
     * @memberof! UCASDesignFramework.modal_light
     */
    var event = document.createEvent('Event')
    event.initEvent('modalLightShow', true, true)
    targetModal.dispatchEvent(event)
  }

  /**
   * Attaches event listeners to close modal
   * @param {Node} targetModal - the modal
   */
  function attachCloseListener (targetModal) {
    var targets = targetModal.querySelectorAll('[' + attributePrefix + 'modal-light-close]')
    _u.forEach(targets, function (i, el) {
      el.addEventListener('click', hideModalListener, false)
    })
  }

  /**
   * Hide modal listener.
   * @param {object} event]
   */
  function hideModalListener (event) {
    event.preventDefault()
    hideModal()
  }

  /**
   * Hides all modals or specific modalID
   * @function hideModal
   * @memberof! UCASDesignFramework.modal_light
   * @param {string} modalID] ID of modal to hide (optional)
   */
  function hideModal (modalID) {
    var targetModal
    if (typeof modalID === 'string') {
      targetModal = document.getElementById(modalID)
      targetModal.setAttribute(attributePrefix + 'modal-light-state', 'hidden')
    } else {
      // As there is not a specific target, apply only to the top modal.
      targetModal = topModal()
      targetModal.setAttribute(attributePrefix + 'modal-light-state', 'hidden')
      if (typeof focusTracker !== 'string') {
        focusTracker.target.focus()
      }
    }

    /**
     * @event modalLightHide
     * @memberof! UCASDesignFramework.modal_light
     */
    var event = document.createEvent('Event')
    event.initEvent('modalLightHide', true, true)
    targetModal.dispatchEvent(event)
  }
  /**
   * Traps focus inside an active modal for accessibility
   */
  function focusTrapper () {
    document.addEventListener('focus', function (ev) {
      var activeModal = topModal()
      if (activeModal && !activeModal.contains(ev.target)) {
        ev.stopPropagation()
        activeModal.focus()
      }
    }, true)
  }

  /**
   * Provide a simple way to reference a modal and show/hide it.
   * @class
   * @memberof! UCASDesignFramework.modal_light
   * @public
   * @example
   * myLovelyModal = new UCASDesignFramework.modal_light.Modal("modal-1")
   * myLovelyModal.show()
   * myLovelyModal.hide()
   */
  var Modal = (function () {
    /**
     * @param {string} id - id of the modal
     */
    function Modal (id) {
      this.id = id
    }
    Modal.prototype.show = function () {
      showModal(this.id)
    }
    Modal.prototype.hide = function () {
      hideModal(this.id)
    }
    return Modal
  })()

  // Expose public methods
  global.modal_light = {
    init: init,
    destroy: destroy,
    showModal: showModal,
    hideModal: hideModal,
    Modal: Modal
  }
})(UCASDesignFramework, UCASUtilities)