diff --git a/src/Foundation.hs b/src/Foundation.hs index 75921e36f..fe0df0b26 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1076,7 +1076,8 @@ siteLayout' headingOverride widget = do -- addScript $ StaticR js_utils_httpClient_js -- addScript $ StaticR js_utils_form_js -- addScript $ StaticR js_utils_inputs_js - -- addScript $ StaticR js_utils_modal_js + -- JavaScript utils + addScript $ StaticR js_utils_modal_js addScript $ StaticR js_utils_poc_js addScript $ StaticR js_utils_showHide_js -- addScript $ StaticR js_utils_tabber_js diff --git a/static/css/utils/modal.scss b/static/css/utils/modal.scss index 2f5d0e168..d47b8c727 100644 --- a/static/css/utils/modal.scss +++ b/static/css/utils/modal.scss @@ -14,9 +14,6 @@ padding: 0 65px 0 20px; overflow: auto; overscroll-behavior: contain; - transition: - opacity .2s .1s ease-in-out, - transform .3s ease-in-out; pointer-events: none; opacity: 0; @@ -25,6 +22,9 @@ pointer-events: auto; z-index: 200; transform: translate(-50%, -50%) scale(1, 1); + transition: + opacity .2s .1s ease-in-out, + transform .3s ease-in-out; } } diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 50307f3db..3f46300a6 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -1,169 +1,192 @@ (function() { 'use strict'; - window.utils = window.utils || {}; - // ######################## - // TODO: make use of selector - // or think of a different way to dynamically initialize widgets - // with selectors with specific ids like #modal-hident69 - // - // Idee: - // Alles wegschmeißen zu dynamischen IDs. Util init rein über Selector '[uw-...]' - // bedarf Änderung in Templates. - // Ausserdem müssen sich Utils bei Event 'util-setup-ready' registrieren als Util - // utils.setup wird dann überflüssig, bzw. wird zu einer Registry / Controller - // der die utils bei DomCOntentLoaded intialisiert. - // - // ######################## - var SELECTOR = '[uw-modal]'; + /** + * + * Modal Utility + * + * Attribute: uw-modal + * + * Params: + * data-modal-trigger: string + * Selector for the element that toggles the modal. + * If trigger element has "href" attribute the modal will be dynamically loaded from the referenced page + * data-modal-closeable: boolean property + * If the param is present the modal will have a close-icon and can also be closed by clicking anywhere on the overlay + * + * Example usage: + *
This is the modal content + *
Click me to open the modal + */ + + var MODAL_UTIL_NAME = 'modal'; + var MODAL_UTIL_SELECTOR = '[uw-modal]'; - var JS_INITIALIZED_CLASS = 'js-modal-initialized'; - var MODAL_OPEN_CLASS = 'modal--open'; - var MODAL_TRIGGER_CLASS = 'modal__trigger'; - var MODAL_CONTENT_CLASS = 'modal__content'; - var MAIN_CONTENT_CLASS = 'main__content-body' - var MODAL_CLOSABLE_FLAG = 'closeable'; - var MODAL_DYNAMIC_FLAG = 'dynamic'; var MODAL_HEADERS = { 'Is-Modal': 'True', }; - var OVERLAY_CLASS = 'modal__overlay'; - var OVERLAY_OPEN_CLASS = 'modal__overlay--open'; - var CLOSER_CLASS = 'modal__closer'; - window.utils.modal = function(scope, options) { + var MODAL_INITIALIZED_CLASS = 'modal--initialized'; + var MODAL_CLASS = 'modal'; + var MODAL_OPEN_CLASS = 'modal--open'; + var MODAL_TRIGGER_CLASS = 'modal__trigger'; + var MODAL_CONTENT_CLASS = 'modal__content'; + var MODAL_OVERLAY_CLASS = 'modal__overlay'; + var MODAL_OVERLAY_OPEN_CLASS = 'modal__overlay--open'; + var MODAL_CLOSER_CLASS = 'modal__closer'; - if (!modalElement || modalElement.classList.contains(JS_INITIALIZED_CLASS)) { - return; - } + var MAIN_CONTENT_CLASS = 'main__content-body' - var utilInstances = []; + var modalUtil = function(element) { var overlayElement = document.createElement('div'); - var closerElement = document.createElement('div'); - var triggerElement = document.querySelector('#' + modalElement.dataset.trigger); + var modalUrl; - function setup() { - document.body.insertBefore(modalElement, null); + function _init() { + if (!element) { + throw new Error('Modal utility cannot be setup without an element!'); + } - setupForm(); - setupCloser(); + if (element.classList.contains(MODAL_INITIALIZED_CLASS)) { + throw new Error('Modal utility already initialized!'); + } - triggerElement.classList.add(MODAL_TRIGGER_CLASS); - triggerElement.addEventListener('click', openHandler, false); + // param modalTrigger + if (!element.dataset.modalTrigger) { + throw new Error('Modal utility cannot be setup without a trigger element!'); + } else { + setupTrigger(); + } - modalElement.classList.add(JS_INITIALIZED_CLASS); + // param modalCloseable + if (element.dataset.modalCloseable !== undefined) { + setupCloser(); + } + + // setupForm(); + + // mark as initialized and add modal class for styling + element.classList.add(MODAL_INITIALIZED_CLASS, MODAL_CLASS); + + return { + name: MODAL_UTIL_NAME, + element: element, + destroy: function() {} + }; } - function openHandler(event) { + function setupTrigger() { + var triggerElement = document.querySelector(element.dataset.modalTrigger); + + if (!triggerElement) { + throw new Error('Trigger element for Modal not found: "', + element.dataset.modalTrigger + '"'); + } + + triggerElement.classList.add(MODAL_TRIGGER_CLASS); + triggerElement.addEventListener('click', onTriggerClicked, false); + modalUrl = triggerElement.getAttribute('href'); + } + + function setupCloser() { + var closerElement = document.createElement('div'); + element.insertBefore(closerElement, null); + closerElement.classList.add(MODAL_CLOSER_CLASS); + closerElement.addEventListener('click', onCloseClicked, false); + overlayElement.addEventListener('click', onCloseClicked, false); + } + + function onTriggerClicked(event) { event.preventDefault(); open(); } - function open() { - modalElement.classList.add(MODAL_OPEN_CLASS); - overlayElement.classList.add(OVERLAY_CLASS); - document.body.insertBefore(overlayElement, modalElement); - overlayElement.classList.add(OVERLAY_OPEN_CLASS); - - var modalUrl = triggerElement.getAttribute('href'); - if (modalUrl && MODAL_DYNAMIC_FLAG in modalElement.dataset) { - fillModal(modalUrl); - } - - document.addEventListener('keyup', keyupHandler); - } - - function closeHandler(event) { + function onCloseClicked(event) { event.preventDefault(); close(); } - function close() { - overlayElement.classList.remove(OVERLAY_OPEN_CLASS); - modalElement.classList.remove(MODAL_OPEN_CLASS); + function onKeyUp(event) { + if (event.key === 'Escape') { + close(); + } + } - document.removeEventListener('keyup', keyupHandler); + function open() { + document.body.insertBefore(element, null); + element.classList.add(MODAL_OPEN_CLASS); + overlayElement.classList.add(MODAL_OVERLAY_CLASS); + document.body.insertBefore(overlayElement, element); + overlayElement.classList.add(MODAL_OVERLAY_OPEN_CLASS); + + if (modalUrl) { + fillModal(modalUrl); + } + + document.addEventListener('keyup', onKeyUp); + } + + function close() { + overlayElement.classList.remove(MODAL_OVERLAY_OPEN_CLASS); + element.classList.remove(MODAL_OPEN_CLASS); + + document.removeEventListener('keyup', onKeyUp); }; - function setupForm() { - var form = modalElement.querySelector('form'); - if (form) { - utilInstances.push(window.utils.setup('form', form, { headers: MODAL_HEADERS, force: true })); - } - } - - function setupCloser() { - if (MODAL_CLOSABLE_FLAG in modalElement.dataset) { - modalElement.insertBefore(closerElement, null); - closerElement.classList.add(CLOSER_CLASS); - closerElement.addEventListener('click', closeHandler, false); - overlayElement.addEventListener('click', closeHandler, false); - } - } - function fillModal(url) { - if (!window.utils.httpClient) { - throw new Error('httpClient not found! Can\' fetch modal content from ' + url); + if (!HttpClient) { + throw new Error('HttpClient not found! Can\'t fetch modal content from ' + url); } - window.utils.httpClient.get(url, MODAL_HEADERS) + HttpClient.get(url, MODAL_HEADERS) .then(function(response) { response.text().then(processResponse); }); } - function processResponse(reponseBody) { + function processResponse(responseBody) { + var responseElement = document.createElement('div'); + responseElement.innerHTML = responseBody; + var modalContent = document.createElement('div'); modalContent.classList.add(MODAL_CONTENT_CLASS); - modalContent.innerHTML = reponseBody; - var contentBody = modalContent.querySelector('.' + MAIN_CONTENT_CLASS); + var contentBody = responseElement.querySelector('.' + MAIN_CONTENT_CLASS); if (contentBody) { modalContent.innerHTML = contentBody.innerHTML; } - var previousModalContent = modalElement.querySelector('.' + MODAL_CONTENT_CLASS); + var previousModalContent = element.querySelector('.' + MODAL_CONTENT_CLASS); if (previousModalContent) { previousModalContent.remove(); } modalContent = withPrefixedInputIDs(modalContent); - modalElement.insertBefore(modalContent, null); - setupForm(); + element.insertBefore(modalContent, null); + + // setup any newly arrived utils + UtilRegistry.setupAll(); } function withPrefixedInputIDs(modalContent) { var idAttrs = ['id', 'for', 'data-conditional-id']; idAttrs.forEach(function(attr) { modalContent.querySelectorAll('[' + attr + ']').forEach(function(input) { - var value = modalElement.id + '__' + input.getAttribute(attr); + var value = element.id + '__' + input.getAttribute(attr); input.setAttribute(attr, value); }); }); return modalContent; } - function keyupHandler(event) { - if (event.key === 'Escape') { - close(); - } - } - - setup(); - - function destroyUtils() { - utilInstances.filter(function(utilInstance) { - return !!utilInstance; - }).forEach(function(utilInstance) { - utilInstance.destroy(); - }); - } - - return { - scope: modalElement, - destroy: destroyUtils, - }; + return _init(); }; + + if (UtilRegistry) { + UtilRegistry.register({ + name: MODAL_UTIL_NAME, + selector: MODAL_UTIL_SELECTOR, + setup: modalUtil + }); + } })(); diff --git a/templates/adminTest.hamlet b/templates/adminTest.hamlet index b595fe813..9c90a8994 100644 --- a/templates/adminTest.hamlet +++ b/templates/adminTest.hamlet @@ -26,11 +26,11 @@
  • Knopf-Test: ^{btnForm} -

  • - Modals: - ^{modal "Klick mich für Ajax-Test" (Left $ SomeRoute UsersR)} - ^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")}
  • - ^{modal "Email-Test" (Right emailWidget')} + Modals: +
      +
    • ^{modal "Klick mich für Ajax-Test" (Left $ SomeRoute UsersR)} +
    • ^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")} +
    • ^{modal "Email-Test" (Right emailWidget')}
    • Some icons: ^{isVisible False} ^{hasComment True} diff --git a/templates/default-layout-wrapper.hamlet b/templates/default-layout-wrapper.hamlet index c293b607a..014edc30f 100644 --- a/templates/default-layout-wrapper.hamlet +++ b/templates/default-layout-wrapper.hamlet @@ -12,7 +12,7 @@ $newline never ^{pageHead pc} - +