197 lines
6.9 KiB
JavaScript
197 lines
6.9 KiB
JavaScript
import {ModalProxy} from './modalProxy';
|
|
import {parseHTML} from './util';
|
|
import {remoteForm} from '@github/remote-form';
|
|
|
|
let lastTaskId = 0;
|
|
|
|
export const modalStackSettings = {
|
|
disposeOnHide: true,
|
|
dynamicModalContainerSelector: '#dynamic-modals',
|
|
eventClosed: 'hidden.bs.modal',
|
|
eventOpened: 'shown.bs.modal',
|
|
modalFunctionRef: {
|
|
open: 'show',
|
|
close: 'hide',
|
|
toggle: 'toggle'
|
|
},
|
|
modalImpl: {},
|
|
modalSelector: '.modal',
|
|
modalStackFormSelector: 'form[data-modal-stack-form]',
|
|
modalStackFormNoInit: false,
|
|
modalStackFormSubmitButtonSimulator: true,
|
|
unexpectedErrorModalSelector: '#unexpected-error-modal'
|
|
};
|
|
|
|
export default function initializeModalStack(options)
|
|
{
|
|
Object.assign(modalStackSettings, options);
|
|
return {
|
|
initializeModalOpener,
|
|
initializeAjaxModalForm,
|
|
createModalFromUri,
|
|
createModalFromHtml,
|
|
createModalFromElement
|
|
};
|
|
}
|
|
|
|
export function createModalFromElement(modalElement, options)
|
|
{
|
|
const {
|
|
disposeOnHide,
|
|
eventClosed,
|
|
eventOpened,
|
|
modalFunctionRef,
|
|
modalImpl,
|
|
modalStackFormSelector,
|
|
modalStackFormNoInit,
|
|
} = options = { ...modalStackSettings, ...modalElement.dataset, ...options };
|
|
if (!modalElement.matches('.modal')) {
|
|
throw new TypeError('Your element needs the ".modal" class');
|
|
}
|
|
modalElement.addEventListener(eventClosed, (e) => {
|
|
modalElement.dispatchEvent(new CustomEvent('modal-stack-modal-closed', {
|
|
detail: {originalEvent: e},
|
|
bubbles: true,
|
|
}));
|
|
});
|
|
modalElement.addEventListener(eventOpened, (e) => {
|
|
modalElement.dispatchEvent(new CustomEvent('modal-stack-modal-opened', {
|
|
detail: {originalEvent: e},
|
|
bubbles: true,
|
|
}));
|
|
});
|
|
if (!modalStackFormNoInit) {
|
|
Array.from(modalElement.querySelectorAll(modalStackFormSelector)).forEach(form => initializeAjaxModalForm(form, options));
|
|
}
|
|
const newModal = new ModalProxy(modalElement, new modalImpl(modalElement, options), modalFunctionRef);
|
|
modalElement.modalStackProxy = newModal;
|
|
if (disposeOnHide) {
|
|
modalElement.addEventListener('modal-stack-modal-closed', () => modalElement.remove());
|
|
}
|
|
return newModal;
|
|
}
|
|
|
|
export function createModalFromHtml(html, options)
|
|
{
|
|
const {
|
|
dynamicModalContainerSelector,
|
|
modalSelector
|
|
} = { ...modalStackSettings, ...options };
|
|
const modalContainer = document.querySelector(dynamicModalContainerSelector);
|
|
if (modalContainer === null) {
|
|
throw new TypeError(`Modal container not found. Selector was: "${dynamicModalContainerSelector}"`);
|
|
}
|
|
|
|
const element = parseHTML(html).querySelector(modalSelector);
|
|
if (element === null) {
|
|
throw new TypeError('There was no modal element');
|
|
}
|
|
modalContainer.append(element);
|
|
dispatchEvent('modal-stack-element-created', {element});
|
|
return createModalFromElement(element, options);
|
|
}
|
|
|
|
export async function createModalFromUri(href, options)
|
|
{
|
|
options = { ...modalStackSettings, ...options };
|
|
|
|
const taskId = ++lastTaskId;
|
|
dispatchEvent('modal-stack-fetching', {href, taskId});
|
|
|
|
const html = await fetch(href)
|
|
.then(response => {
|
|
if (response.status < 200 || response.status >= 300) {
|
|
throw new Error('Unexpetected http response');
|
|
}
|
|
dispatchEvent('modal-stack-fetching-finished', {href, taskId});
|
|
return response.text()
|
|
})
|
|
.then(text => text)
|
|
.catch((e) => {
|
|
const errorModalElement = document.querySelector(options.unexpectedErrorModalSelector);
|
|
if (errorModalElement === null) {
|
|
console.error('Unexpected modal missing during error handling');
|
|
return;
|
|
}
|
|
createModalFromElement(errorModalElement, {disposeOnHide: false}).open();
|
|
// throw the error along, as nobody can work with this result ;)
|
|
throw e;
|
|
});
|
|
|
|
return createModalFromHtml(html, options);
|
|
}
|
|
|
|
export function initializeModalOpener(element, options)
|
|
{
|
|
const {
|
|
ajaxModalUri
|
|
} = options = { ...modalStackSettings, ...element.dataset, ...options };
|
|
element.addEventListener('click', async () => {
|
|
if (element.dataset.ajaxModalUri === undefined) {
|
|
throw new TypeError('Modal opener requires data-ajax-modal-uri');
|
|
}
|
|
(await createModalFromUri(element.dataset.ajaxModalUri, options)).open();
|
|
});
|
|
}
|
|
|
|
export function initializeAjaxModalForm(form, options)
|
|
{
|
|
const {
|
|
modalSelector,
|
|
modalStackFormSubmitButtonSimulator
|
|
} = { ...modalStackSettings, ...form.dataset, ...options };
|
|
if (modalStackFormSubmitButtonSimulator) {
|
|
const hidden = document.createElement('input');
|
|
hidden.type = 'hidden';
|
|
form.append(hidden);
|
|
form.addEventListener('click', (event) => {
|
|
if (!event.target || !event.target.matches('[type="submit"]')) {
|
|
return;
|
|
}
|
|
hidden.value = event.target.value;
|
|
hidden.name = event.target.name;
|
|
});
|
|
}
|
|
remoteForm(form, async (form, wants) => {
|
|
form.dispatchEvent(new CustomEvent('modal-stack-form-submit-start', {bubbles: true}));
|
|
const parentModal = form.closest(modalSelector);
|
|
try {
|
|
const response = await wants.html();
|
|
form.dispatchEvent(new CustomEvent('modal-stack-form-submit-finished', {response, bubbles: true}));
|
|
const cancelResponseEvent = new CustomEvent('modal-stack-form-response', {response, bubbles: true, cancelable: true});
|
|
if (!form.dispatchEvent(cancelResponseEvent)) {
|
|
return;
|
|
};
|
|
if (response.status !== 204) {
|
|
parentModal.innerHTML = response.html.querySelector(modalSelector).innerHTML;
|
|
form.dispatchEvent(new CustomEvent('modal-stack-form-replaced', {response, bubbles: true}));
|
|
initializeAjaxModalForm(parentModal.querySelector('form', options));
|
|
return;
|
|
}
|
|
form.dispatchEvent(new CustomEvent('modal-stack-form-submit-success', {response, bubbles: true}));
|
|
parentModal.modalStackProxy.close();
|
|
} catch (e) {
|
|
parentModal.modalStackProxy.close();
|
|
const errorModalElement = document.querySelector(options.unexpectedErrorModalSelector);
|
|
if (errorModalElement === null) {
|
|
console.error('Unexpected modal missing during error handling');
|
|
return;
|
|
}
|
|
createModalFromElement(errorModalElement, {disposeOnHide: false}).open();
|
|
}
|
|
});
|
|
}
|
|
|
|
function dispatchEvent(name, details)
|
|
{
|
|
if (details === undefined) {
|
|
details = {};
|
|
}
|
|
console.debug('dispatching event: ' + name);
|
|
document.dispatchEvent(
|
|
new CustomEvent(name, {
|
|
detail: details,
|
|
bubbles: true,
|
|
})
|
|
);
|
|
}
|