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_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

View File

@ -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;
}
}

View File

@ -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:
* <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 = {
'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
});
}
})();

View File

@ -26,11 +26,11 @@
<li>
Knopf-Test:
^{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>
^{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>
Some icons: ^{isVisible False} ^{hasComment True}

View File

@ -12,7 +12,7 @@ $newline never
^{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 -->
<script>
document.body.classList.remove('no-js');

View File

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

View File

@ -1,5 +1,5 @@
$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
$of Right content
<div .modal__content>

View File

@ -7,7 +7,7 @@ $newline never
$of PageActionPrime
<div .pagenav__list-item>
$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}
$of _
$if hasSecondaryPageActions
@ -18,6 +18,6 @@ $newline never
$of PageActionSecondary
<div .pagenav__list-item.pagenav__list-item--secondary>
$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}
$of _