refactor JS modal utility to work with new util registry

This commit is contained in:
Felix Hamann 2019-04-05 21:35:33 +02:00
parent 4161af4742
commit ffef0b94bc
8 changed files with 149 additions and 131 deletions

View File

@ -1076,7 +1076,8 @@ siteLayout' headingOverride widget = do
-- addScript $ StaticR js_utils_httpClient_js -- addScript $ StaticR js_utils_httpClient_js
-- addScript $ StaticR js_utils_form_js -- addScript $ StaticR js_utils_form_js
-- addScript $ StaticR js_utils_inputs_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_poc_js
addScript $ StaticR js_utils_showHide_js addScript $ StaticR js_utils_showHide_js
-- addScript $ StaticR js_utils_tabber_js -- addScript $ StaticR js_utils_tabber_js

View File

@ -14,9 +14,6 @@
padding: 0 65px 0 20px; padding: 0 65px 0 20px;
overflow: auto; overflow: auto;
overscroll-behavior: contain; overscroll-behavior: contain;
transition:
opacity .2s .1s ease-in-out,
transform .3s ease-in-out;
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
@ -25,6 +22,9 @@
pointer-events: auto; pointer-events: auto;
z-index: 200; z-index: 200;
transform: translate(-50%, -50%) scale(1, 1); transform: translate(-50%, -50%) scale(1, 1);
transition:
opacity .2s .1s ease-in-out,
transform .3s ease-in-out;
} }
} }

View File

@ -1,169 +1,192 @@
(function() { (function() {
'use strict'; 'use strict';
window.utils = window.utils || {}; /**
// ######################## *
// TODO: make use of selector * Modal Utility
// or think of a different way to dynamically initialize widgets *
// with selectors with specific ids like #modal-hident69 * Attribute: uw-modal
// *
// Idee: * Params:
// Alles wegschmeißen zu dynamischen IDs. Util init rein über Selector '[uw-...]' * data-modal-trigger: string
// bedarf Änderung in Templates. * Selector for the element that toggles the modal.
// Ausserdem müssen sich Utils bei Event 'util-setup-ready' registrieren als Util * If trigger element has "href" attribute the modal will be dynamically loaded from the referenced page
// utils.setup wird dann überflüssig, bzw. wird zu einer Registry / Controller * data-modal-closeable: boolean property
// der die utils bei DomCOntentLoaded intialisiert. * 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:
var SELECTOR = '[uw-modal]'; * <div uw-modal data-modal-trigger='#trigger' data-modal-closeable>This is the modal content
* <div id='trigger'>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 = { var MODAL_HEADERS = {
'Is-Modal': 'True', '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)) { var MAIN_CONTENT_CLASS = 'main__content-body'
return;
}
var utilInstances = []; var modalUtil = function(element) {
var overlayElement = document.createElement('div'); var overlayElement = document.createElement('div');
var closerElement = document.createElement('div'); var modalUrl;
var triggerElement = document.querySelector('#' + modalElement.dataset.trigger);
function setup() { function _init() {
document.body.insertBefore(modalElement, null); if (!element) {
throw new Error('Modal utility cannot be setup without an element!');
}
setupForm(); if (element.classList.contains(MODAL_INITIALIZED_CLASS)) {
setupCloser(); throw new Error('Modal utility already initialized!');
}
triggerElement.classList.add(MODAL_TRIGGER_CLASS); // param modalTrigger
triggerElement.addEventListener('click', openHandler, false); 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(); event.preventDefault();
open(); open();
} }
function open() { function onCloseClicked(event) {
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) {
event.preventDefault(); event.preventDefault();
close(); close();
} }
function close() { function onKeyUp(event) {
overlayElement.classList.remove(OVERLAY_OPEN_CLASS); if (event.key === 'Escape') {
modalElement.classList.remove(MODAL_OPEN_CLASS); 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) { function fillModal(url) {
if (!window.utils.httpClient) { if (!HttpClient) {
throw new Error('httpClient not found! Can\' fetch modal content from ' + url); 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) { .then(function(response) {
response.text().then(processResponse); response.text().then(processResponse);
}); });
} }
function processResponse(reponseBody) { function processResponse(responseBody) {
var responseElement = document.createElement('div');
responseElement.innerHTML = responseBody;
var modalContent = document.createElement('div'); var modalContent = document.createElement('div');
modalContent.classList.add(MODAL_CONTENT_CLASS); 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) { if (contentBody) {
modalContent.innerHTML = contentBody.innerHTML; modalContent.innerHTML = contentBody.innerHTML;
} }
var previousModalContent = modalElement.querySelector('.' + MODAL_CONTENT_CLASS); var previousModalContent = element.querySelector('.' + MODAL_CONTENT_CLASS);
if (previousModalContent) { if (previousModalContent) {
previousModalContent.remove(); previousModalContent.remove();
} }
modalContent = withPrefixedInputIDs(modalContent); modalContent = withPrefixedInputIDs(modalContent);
modalElement.insertBefore(modalContent, null); element.insertBefore(modalContent, null);
setupForm();
// setup any newly arrived utils
UtilRegistry.setupAll();
} }
function withPrefixedInputIDs(modalContent) { function withPrefixedInputIDs(modalContent) {
var idAttrs = ['id', 'for', 'data-conditional-id']; var idAttrs = ['id', 'for', 'data-conditional-id'];
idAttrs.forEach(function(attr) { idAttrs.forEach(function(attr) {
modalContent.querySelectorAll('[' + attr + ']').forEach(function(input) { modalContent.querySelectorAll('[' + attr + ']').forEach(function(input) {
var value = modalElement.id + '__' + input.getAttribute(attr); var value = element.id + '__' + input.getAttribute(attr);
input.setAttribute(attr, value); input.setAttribute(attr, value);
}); });
}); });
return modalContent; return modalContent;
} }
function keyupHandler(event) { return _init();
if (event.key === 'Escape') {
close();
}
}
setup();
function destroyUtils() {
utilInstances.filter(function(utilInstance) {
return !!utilInstance;
}).forEach(function(utilInstance) {
utilInstance.destroy();
});
}
return {
scope: modalElement,
destroy: destroyUtils,
};
}; };
if (UtilRegistry) {
UtilRegistry.register({
name: MODAL_UTIL_NAME,
selector: MODAL_UTIL_SELECTOR,
setup: modalUtil
});
}
})(); })();

View File

@ -26,11 +26,11 @@
<li> <li>
Knopf-Test: Knopf-Test:
^{btnForm} ^{btnForm}
<li><br>
Modals:
^{modal "Klick mich für Ajax-Test" (Left $ SomeRoute UsersR)}
^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")}
<li> <li>
^{modal "Email-Test" (Right emailWidget')} Modals:
<ul>
<li>^{modal "Klick mich für Ajax-Test" (Left $ SomeRoute UsersR)}
<li>^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")}
<li>^{modal "Email-Test" (Right emailWidget')}
<li> <li>
Some icons: ^{isVisible False} ^{hasComment True} Some icons: ^{isVisible False} ^{hasComment True}

View File

@ -12,7 +12,7 @@ $newline never
^{pageHead pc} ^{pageHead pc}
<body .no-js .theme--#{toPathPiece currentTheme} :isAuth:.logged-in :isModal:.modal> <body .no-js .theme--#{toPathPiece currentTheme} :isAuth:.logged-in>
<!-- removes no-js class from body if client supports javascript --> <!-- removes no-js class from body if client supports javascript -->
<script> <script>
document.body.classList.remove('no-js'); document.body.classList.remove('no-js');

View File

@ -189,27 +189,21 @@ h4 {
} }
@media (min-width: 426px) { @media (min-width: 426px) {
:not(.modal) { .main__content {
.main__content { margin-left: var(--asidenav-width-md, 50px);
margin-left: var(--asidenav-width-md, 50px);
}
} }
} }
@media (min-width: 769px) { @media (min-width: 769px) {
:not(.modal) { .main__content {
.main__content { margin-left: var(--asidenav-width-lg, 20%);
margin-left: var(--asidenav-width-lg, 20%); margin-top: var(--header-height);
margin-top: var(--header-height);
}
} }
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
:not(.modal) { .main__content {
.main__content { margin-left: var(--asidenav-width-xl, 250px);
margin-left: var(--asidenav-width-xl, 250px);
}
} }
} }

View File

@ -1,5 +1,5 @@
$newline never $newline never
<div .modal.js-modal #modal-#{modalId'} data-trigger=#{triggerId'} data-closeable :isDynamic:data-dynamic> <div uw-modal data-modal-trigger=##{triggerId'} data-modal-closeable :isDynamic:.not-used>
$case modalContent $case modalContent
$of Right content $of Right content
<div .modal__content> <div .modal__content>

View File

@ -7,7 +7,7 @@ $newline never
$of PageActionPrime $of PageActionPrime
<div .pagenav__list-item> <div .pagenav__list-item>
$if menuItemModal $if menuItemModal
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic> <div uw-modal data-modal-trigger=#{menuIdent} data-modal-closeable>
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel} <a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
$of _ $of _
$if hasSecondaryPageActions $if hasSecondaryPageActions
@ -18,6 +18,6 @@ $newline never
$of PageActionSecondary $of PageActionSecondary
<div .pagenav__list-item.pagenav__list-item--secondary> <div .pagenav__list-item.pagenav__list-item--secondary>
$if menuItemModal $if menuItemModal
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic> <div uw-modal data-modal-trigger=#{menuIdent} data-modal-closeable>
<a .pagenav__link-wrapper.pagenav__link-wrapper--secondary href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel} <a .pagenav__link-wrapper.pagenav__link-wrapper--secondary href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
$of _ $of _