JavaScript dialog utility demo: prompting user.
index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<template id="tmp-dialog-alert">
<dialog class="wg-Windog">
<!-- ^# tmp-dialog-alert -->
<div class="backdrop"></div>
<div class="wrapper">
<form method="dialog" data-ref="form">
<div data-slot="message"></div>
<div class="void void-2x"></div>
<div class="text-center">
<button>Ok</button>
</div>
</form>
</div>
</dialog>
</template>
<template id="tmp-dialog-prompt">
<dialog class="wg-Windog">
<!-- ^# tmp-dialog-prompt -->
<div class="backdrop"></div>
<div class="wrapper">
<form method="dialog" data-ref="form">
<div data-slot="message"></div>
<div class="void void-half"></div>
<input class="input-style-default" type="text" data-slot="input"/>
<div class="void void-2x"></div>
<div class="d-flex justify-content-end gap-full">
<button value="ok">Ok</button>
<button>Cancel</button>
</div>
</form>
</div>
</dialog>
</template>
<template id="tmp-dialog-confirm">
<dialog class="wg-Windog">
<!-- ^# tmp-dialog-confirm -->
<div class="backdrop"></div>
<div class="wrapper">
<form method="dialog" data-ref="form">
<div data-slot="message"></div>
<div class="void void-2x"></div>
<div class="d-flex justify-content-end gap-full">
<button value="ok">OK</button>
<button>Cancel</button>
</div>
</form>
</div>
</dialog>
</template>
<style>
.wg-Windog {
&::backdrop {
background: rgba(0, 0, 0, 0.4);
}
& {
padding: 0;
background: transparent;
}
.wrapper {
background: white;
position: relative;
padding: 1rem;
}
.backdrop {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
}
button:focus {
border-color: #4CAF50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.3);
}
}
</style>
<script src="windog.js"></script>
<script src="index.js"></script>
</body>
</html>
index.js
windog.prompt('Value').then(userVal => {
console.log(userVal)
});
windog.js
let windog = (function() {
let $ = document.querySelector.bind(document);
let SELF = {
alert,
confirm,
prompt,
showDialogAsync,
};
// # options
let dialogOptions = {
alert: {
templateId: 'tmp-dialog-alert',
},
confirm: {
templateId: 'tmp-dialog-confirm',
onClose: (dialogEl) => {
return (dialogEl.returnValue == 'ok');
},
},
prompt: {
defaultValue: null,
templateId: 'tmp-dialog-prompt',
onClose: (dialogEl) => {
if (dialogEl.returnValue == 'ok') {
return dialogEl.querySelector('[data-slot="input"]')?.value;
}
return null;
},
},
};
// # function
// # prompt
async function prompt(message='', defaultValue='') {
return await showDialogAsync(dialogOptions.prompt, onShowDefault, {
message,
defaultValue,
});
}
async function confirm(message='') {
return await showDialogAsync(dialogOptions.confirm, onShowDefault, {
message,
});
}
// # alert
async function alert(message='') {
return await showDialogAsync(dialogOptions.alert, onShowDefault, {
message,
});
}
function onShowDefault(dialogEl, extraData) {
let {message, defaultValue} = extraData;
dialogEl.querySelector('[data-slot="message"]')?.replaceChildren(message);
dialogEl.querySelector('[data-slot="input"]')?.setAttribute('value', defaultValue);
}
async function showDialogAsync(dialogOptions, onShow, extraData) {
return new Promise(async resolve => {
let persistentOptions = {
resolver: {
resolve,
},
};
let mixedOptions = Object.assign(dialogOptions, extraData, persistentOptions);
let {templateId, message, defaultValue} = mixedOptions;
// queue this dialog and wait for previous dialog to resolve
// await subscribeDialogAsync();
let el = $(`#${templateId}`).content.cloneNode(true);
let dialogEl = el.querySelector('dialog');
let dialogData = {
dialogItem: mixedOptions,
};
dialogEl.querySelector('.backdrop')?.addEventListener('click', async () => {
if (typeof(dialogOptions.onBeforeClose) == 'function') {
let isShouldClose = await dialogOptions.onBeforeClose?.(dialogEl);
if (!isShouldClose) return;
}
dialogEl.close();
});
dialogEl.addEventListener('close', onClose);
attachKeytrap(dialogEl, dialogData);
dialogEl._windogData = dialogData;
document.body.append(el);
dialogEl.showModal();
onShow?.(dialogEl, extraData);
});
}
async function onClose(evt) {
let dialogItem = evt.target._windogData.dialogItem;
let dialogEl = evt.target;
dialogEl.remove();
let dialogResult = await dialogItem.onClose?.(dialogEl);
dialogItem.resolver.resolve(dialogResult);
}
function attachKeytrap(dialogEl, dialogData) {
let focusableContent = dialogEl.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
dialogData.firstFocusableElement = focusableContent[0];
dialogData.lastFocusableElement = focusableContent[focusableContent.length - 1];
dialogEl.addEventListener('keydown', keyTrapper);
}
function keyTrapper(evt) {
let isTabPressed = (evt.key == 'Tab');
if (!isTabPressed) return;
let dialogEl = evt.target.closest('dialog');
let {firstFocusableElement, lastFocusableElement} = dialogEl._windogData;
if (evt.shiftKey) {
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus();
evt.preventDefault();
}
} else if (document.activeElement === lastFocusableElement) {
firstFocusableElement.focus();
evt.preventDefault();
}
}
return SELF;
})();
https://vanillawebdev.blogspot.com/2024/11/javascript-dialog-utility-code-demo.html
https://www.blogger.com/blog/post/edit/8166404610182826392/2407098883342648309
https://www.blogger.com/blog/page/edit/8166404610182826392/2407098883342648309
JavaScript Dialog Utility Code (Demo Video + Source)
November 02, 2024
JavaScript dialog utility demo: prompting user.
index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<template id="tmp-dialog-alert">
<dialog class="wg-Windog">
<!-- ^# tmp-dialog-alert -->
<div class="backdrop"></div>
<div class="wrapper">
<form method="dialog" data-ref="form">
<div data-slot="message"></div>
<div class="void void-2x"></div>
<div class="text-center">
<button>Ok</button>
</div>
</form>
</div>
</dialog>
</template>
<template id="tmp-dialog-prompt">
<dialog class="wg-Windog">
<!-- ^# tmp-dialog-prompt -->
<div class="backdrop"></div>
<div class="wrapper">
<form method="dialog" data-ref="form">
<div data-slot="message"></div>
<div class="void void-half"></div>
<input class="input-style-default" type="text" data-slot="input"/>
<div class="void void-2x"></div>
<div class="d-flex justify-content-end gap-full">
<button value="ok">Ok</button>
<button>Cancel</button>
</div>
</form>
</div>
</dialog>
</template>
<template id="tmp-dialog-confirm">
<dialog class="wg-Windog">
<!-- ^# tmp-dialog-confirm -->
<div class="backdrop"></div>
<div class="wrapper">
<form method="dialog" data-ref="form">
<div data-slot="message"></div>
<div class="void void-2x"></div>
<div class="d-flex justify-content-end gap-full">
<button value="ok">OK</button>
<button>Cancel</button>
</div>
</form>
</div>
</dialog>
</template>
<style>
.wg-Windog {
&::backdrop {
background: rgba(0, 0, 0, 0.4);
}
& {
padding: 0;
background: transparent;
}
.wrapper {
background: white;
position: relative;
padding: 1rem;
}
.backdrop {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
}
button:focus {
border-color: #4CAF50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.3);
}
}
</style>
<script src="windog.js"></script>
<script src="index.js"></script>
</body>
</html>
index.js
windog.prompt('Value').then(userVal => {
console.log(userVal)
});
windog.js
let windog = (function() {
let $ = document.querySelector.bind(document);
let SELF = {
alert,
confirm,
prompt,
showDialogAsync,
};
// # options
let dialogOptions = {
alert: {
templateId: 'tmp-dialog-alert',
},
confirm: {
templateId: 'tmp-dialog-confirm',
onClose: (dialogEl) => {
return (dialogEl.returnValue == 'ok');
},
},
prompt: {
defaultValue: null,
templateId: 'tmp-dialog-prompt',
onClose: (dialogEl) => {
if (dialogEl.returnValue == 'ok') {
return dialogEl.querySelector('[data-slot="input"]')?.value;
}
return null;
},
},
};
// # function
// # prompt
async function prompt(message='', defaultValue='') {
return await showDialogAsync(dialogOptions.prompt, onShowDefault, {
message,
defaultValue,
});
}
async function confirm(message='') {
return await showDialogAsync(dialogOptions.confirm, onShowDefault, {
message,
});
}
// # alert
async function alert(message='') {
return await showDialogAsync(dialogOptions.alert, onShowDefault, {
message,
});
}
function onShowDefault(dialogEl, extraData) {
let {message, defaultValue} = extraData;
dialogEl.querySelector('[data-slot="message"]')?.replaceChildren(message);
dialogEl.querySelector('[data-slot="input"]')?.setAttribute('value', defaultValue);
}
async function showDialogAsync(dialogOptions, onShow, extraData) {
return new Promise(async resolve => {
let persistentOptions = {
resolver: {
resolve,
},
};
let mixedOptions = Object.assign(dialogOptions, extraData, persistentOptions);
let {templateId, message, defaultValue} = mixedOptions;
// queue this dialog and wait for previous dialog to resolve
// await subscribeDialogAsync();
let el = $(`#${templateId}`).content.cloneNode(true);
let dialogEl = el.querySelector('dialog');
let dialogData = {
dialogItem: mixedOptions,
};
dialogEl.querySelector('.backdrop')?.addEventListener('click', async () => {
if (typeof(dialogOptions.onBeforeClose) == 'function') {
let isShouldClose = await dialogOptions.onBeforeClose?.(dialogEl);
if (!isShouldClose) return;
}
dialogEl.close();
});
dialogEl.addEventListener('close', onClose);
attachKeytrap(dialogEl, dialogData);
dialogEl._windogData = dialogData;
document.body.append(el);
dialogEl.showModal();
onShow?.(dialogEl, extraData);
});
}
async function onClose(evt) {
let dialogItem = evt.target._windogData.dialogItem;
let dialogEl = evt.target;
dialogEl.remove();
let dialogResult = await dialogItem.onClose?.(dialogEl);
dialogItem.resolver.resolve(dialogResult);
}
function attachKeytrap(dialogEl, dialogData) {
let focusableContent = dialogEl.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
dialogData.firstFocusableElement = focusableContent[0];
dialogData.lastFocusableElement = focusableContent[focusableContent.length - 1];
dialogEl.addEventListener('keydown', keyTrapper);
}
function keyTrapper(evt) {
let isTabPressed = (evt.key == 'Tab');
if (!isTabPressed) return;
let dialogEl = evt.target.closest('dialog');
let {firstFocusableElement, lastFocusableElement} = dialogEl._windogData;
if (evt.shiftKey) {
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus();
evt.preventDefault();
}
} else if (document.activeElement === lastFocusableElement) {
firstFocusableElement.focus();
evt.preventDefault();
}
}
return SELF;
})();
Comments
Post a Comment