import { Utility } from '../../core/utility'; import { EventManager, EventWrapper, EVENT_TYPE } from '../../lib/event-manager/event-manager'; import './modal.sass'; const MODAL_HEADERS = { 'Is-Modal': 'True', }; const MODAL_INITIALIZED_CLASS = 'modal--initialized'; const MODAL_CLASS = 'modal'; const MODAL_OPEN_CLASS = 'modal--open'; const MODAL_TRIGGER_CLASS = 'modal__trigger'; const MODAL_CONTENT_CLASS = 'modal__content'; const MODAL_OVERLAY_CLASS = 'modal__overlay'; const MODAL_OVERLAY_OPEN_CLASS = 'modal__overlay--open'; const MODAL_CLOSER_CLASS = 'modal__closer'; const MAIN_CONTENT_CLASS = 'main__content-body'; // one singleton wrapper to keep all the modals to avoid CSS bug // with blurry text due to `transform: translate(-50%, -50%)` // will be created (and reused) for the first modal that gets initialized const MODALS_WRAPPER_CLASS = 'modals-wrapper'; const MODALS_WRAPPER_SELECTOR = '.' + MODALS_WRAPPER_CLASS; const MODALS_WRAPPER_OPEN_CLASS = 'modals-wrapper--open'; @Utility({ selector: '[uw-modal]', }) export class Modal { _eventManager _element; _app; _modalsWrapper; _modalOverlay; _modalUrl; _triggerElement; _closerElement; constructor(element, app) { if (!element) { throw new Error('Modal utility cannot be setup without an element!'); } this._element = element; this._app = app; this._eventManager = new EventManager(); if (this._element.classList.contains(MODAL_INITIALIZED_CLASS)) { return false; } this._ensureModalWrapper(); // param modalTrigger if (!this._element.dataset.modalTrigger) { throw new Error('Modal utility cannot be setup without a trigger element!'); } else { this._setupTrigger(); } // param modalCloseable if (this._element.dataset.modalCloseable !== undefined) { this._setupCloser(); } // mark as initialized and add modal class for styling this._element.classList.add(MODAL_INITIALIZED_CLASS, MODAL_CLASS); } destroy() { throw new Error('Destroying modals is not possible.'); } _ensureModalWrapper() { this._modalsWrapper = document.querySelector(MODALS_WRAPPER_SELECTOR); if (!this._modalsWrapper) { // create modal wrapper this._modalsWrapper = document.createElement('div'); this._modalsWrapper.classList.add(MODALS_WRAPPER_CLASS); document.body.appendChild(this._modalsWrapper); } this._modalOverlay = this._modalsWrapper.querySelector('.' + MODAL_OVERLAY_CLASS); if (!this._modalOverlay) { // create modal overlay this._modalOverlay = document.createElement('div'); this._modalOverlay.classList.add(MODAL_OVERLAY_CLASS); this._modalsWrapper.appendChild(this._modalOverlay); } } _setupTrigger() { let triggerSelector = this._element.dataset.modalTrigger; if (!triggerSelector.startsWith('#')) { triggerSelector = '#' + triggerSelector; } this._triggerElement = document.querySelector(triggerSelector); if (!this._triggerElement) { throw new Error('Trigger element for Modal not found: "' + triggerSelector + '"'); } this._triggerElement.classList.add(MODAL_TRIGGER_CLASS); const triggerEvent = new EventWrapper(EVENT_TYPE.CLICK, this._onTriggerClicked.bind(this), this._triggerElement, false); this._eventManager.registerNewListener(triggerEvent); this._modalUrl = this._triggerElement.getAttribute('href'); } _setupCloser() { this._closerElement = document.createElement('div'); this._element.insertBefore(this._closerElement, null); this._closerElement.classList.add(MODAL_CLOSER_CLASS); const closerElEvent = new EventWrapper(EVENT_TYPE.CLICK, this._onCloseClicked.bind(this), this._closerElement, false); this._eventManager.registerNewListener(closerElEvent); const overlayClose = new EventWrapper(EVENT_TYPE.CLICK, this._onCloseClicked.bind(this), this._modalOverlay, false); this._eventManager.registerNewListener(overlayClose); } _onTriggerClicked = (event) => { event.preventDefault(); this._open(); } _onCloseClicked = (event) => { event.preventDefault(); this._close(); } _onKeyUp = (event) => { if (event.key === 'Escape') { this._close(); } } _open() { this._element.classList.add(MODAL_OPEN_CLASS); this._modalOverlay.classList.add(MODAL_OVERLAY_OPEN_CLASS); this._modalsWrapper.classList.add(MODALS_WRAPPER_OPEN_CLASS); this._modalsWrapper.appendChild(this._element); if (this._modalUrl) { this._fillModal(this._modalUrl); } document.addEventListener('keyup', this._onKeyUp); } _close() { this._modalOverlay.classList.remove(MODAL_OVERLAY_OPEN_CLASS); this._element.classList.remove(MODAL_OPEN_CLASS); this._modalsWrapper.classList.remove(MODALS_WRAPPER_OPEN_CLASS); document.removeEventListener('keyup', this._onKeyUp); }; _fillModal(url) { this._app.httpClient.get({ url: url, headers: MODAL_HEADERS, }).then( (response) => this._app.htmlHelpers.parseResponse(response) ).then( (response) => this._processResponse(response.element) ); } _processResponse(responseElement) { const modalContent = document.createElement('div'); modalContent.classList.add(MODAL_CONTENT_CLASS); const contentBody = responseElement.querySelector('.' + MAIN_CONTENT_CLASS); if (contentBody) { modalContent.innerHTML = contentBody.innerHTML; } const previousModalContent = this._element.querySelector('.' + MODAL_CONTENT_CLASS); if (previousModalContent) { previousModalContent.remove(); } this._element.insertBefore(modalContent, null); // setup any newly arrived utils this._app.utilRegistry.initAll(this._element); } }