Message Generator Utility
Registered members can download the FREE Get Started Apps. I initially implemented MySQL for this site and the Get Started App. The Get Started App is the project I used to compose articles about setting up VS Code and developing Node with Express and the Embedded JavaScript (EJS) view engine. I decided to migrate this site to PostgreSQL. Rather than replacing the MySQL Get Started App, I decided to add a PostgreSQL Get Started App. Both apps will run without a database but can integrate with either MySQL or PostgreSQL by configuring environment variables in the .env file.
The messaging from the client-js to the user is a critical function. I developed dynamic-modals.js to display a message modal. This Generator allows you to create and test your message modal. Edits to the Generator automatically updates the Generated code block with the fewest parameters needed. A dismiss button and/or a link is required. The message modal does not implement the close button in the head to force a redirection. Notice a modal created in the page's html can co-exists with dynamic-modals.js.
This series will focus on the utilities I use for this site which implement user security and communications crucial to user registration and user password storage.
I migrated the Message Generator utility from my ASP.NET Core websites. The dynamic-modals.js is a client-side utility which uses Bootstrap css classes and the modal component JavaScript provided by Bootstrap v5 js or Bootstrap-Native js. The dynamic-modals.js creates a basic customizable modal with JavaScript, no external html is required.
The dynamic-modals.js also provides AJAX Modal and Confirm Modal functions. The AJAX Modal function uses fetch to retrieve and display the properties of a server-side entity.
The Confirm Modal function requests confirmation before continuing with a unreversible action like deleting a server-side entity.
dynamic-modals.js
'use strict'
/*!
* Copyright © Ken Haggerty (https://kenhaggerty.com)
* Licensed under the MIT License.
* dynamic-modals.js - Version 2.4.0
* Bootstrap v5 Message Modal - Version 2.4.0
* Bootstrap v5 AJAX Modal - Version 2.4.0
* Bootstrap v5 Confirm Modal - Version 1.4.0
* Requires Bootstrap.css v5
* Depends on Bootstrap v5 js or Bootstrap Native
*/
let modalLibrary = '';
let modalInstance = null;
/*Bootstrap v5 Message Modal - Version 2.4.0 */
let messageModalDefaults = {
Message: 'Thank you for supporting ExpressWithUsers.Com.',
MessageClass: 'alert-primary',
MessageAllowDismiss: true,
MessageDismissText: 'OK',
MessageDismissClass: 'btn-primary',
MessageIncludeLink: false,
MessageLinkHref: 'https://expresswithusers.com',
MessageLinkTarget: '_self',
MessageLinkText: 'ExpressWithUsers.Com',
MessageLinkClass: 'btn-primary',
MessageHideBackdrop: false
}
let modalBodyDiv = document.createElement('div');
modalBodyDiv.classList.add('modal-body');
modalBodyDiv.classList.add('text-center');
let messageElement = document.createElement('h4');
messageElement.classList.add('text-break');
let messageClassDiv = document.createElement('div');
messageClassDiv.appendChild(messageElement);
modalBodyDiv.appendChild(messageClassDiv);
let messageLinkButton = document.createElement('a', { href: '/' });
modalBodyDiv.appendChild(messageLinkButton);
let messageDismissButton = document.createElement('button', { type: 'button' });
messageDismissButton.dataset.bsDismiss = 'modal';
modalBodyDiv.appendChild(messageDismissButton);
let modalContentDiv = document.createElement('div');
modalContentDiv.classList.add('modal-content');
modalContentDiv.appendChild(modalBodyDiv);
let modalDialogDiv = document.createElement('div', { role: 'document' });
modalDialogDiv.classList.add('modal-dialog');
modalDialogDiv.classList.add('top-5');
modalDialogDiv.appendChild(modalContentDiv);
let modalDiv = document.createElement('div', { tabindex: '-1', role: 'dialog' });
modalDiv.classList.add('modal');
modalDiv.classList.add('message-modal');
modalDiv.classList.add('fade');
modalDiv.appendChild(modalDialogDiv);
document.body.appendChild(modalDiv);
const showMessageModal = (
message = messageModalDefaults.Message,
messageClass = messageModalDefaults.MessageClass,
allowDismiss = messageModalDefaults.MessageAllowDismiss,
dismissText = messageModalDefaults.MessageDismissText,
dismissClass = messageModalDefaults.MessageDismissClass,
includeLink = messageModalDefaults.MessageIncludeLink,
linkHref = messageModalDefaults.MessageLinkHref,
linkTarget = messageModalDefaults.MessageLinkTarget,
linkText = messageModalDefaults.MessageLinkText,
linkClass = messageModalDefaults.MessageLinkClass,
hideBackdrop = messageModalDefaults.MessageHideBackdrop) => {
if (modalLibrary.length == 0)
return;
if (message.length === 0) message = messageModalDefaults.Message;
let messageParse = message.toString();
if (messageParse.indexOf('DOMException') !== -1) message = messageParse.substring(14);
if (messageParse.indexOf('NotAllowedError') !== -1) message = messageParse.substring(17);
if (messageClass.length === 0) messageClass = messageModalDefaults.MessageClass;
if (dismissText.length === 0) dismissText = messageModalDefaults.MessageDismissText;
if (dismissClass.length === 0) dismissClass = messageModalDefaults.MessageDismissClass;
if (linkHref.length === 0) linkHref = messageModalDefaults.MessageLinkHref;
if (linkTarget.length === 0) linkTarget = messageModalDefaults.MessageLinkTarget;
if (linkText.length === 0) linkText = messageModalDefaults.MessageLinkText;
if (linkClass.length === 0) linkClass = messageModalDefaults.MessageLinkClass;
messageElement.innerText = message;
messageClassDiv.classList.remove(...messageClassDiv.classList);
messageClassDiv.classList.add('alert', messageClass, 'px-2', 'py-1', 'mb-3');
messageDismissButton.textContent = dismissText;
messageDismissButton.classList.remove(...messageDismissButton.classList);
messageDismissButton.classList.add('btn');
messageDismissButton.classList.add(dismissClass);
messageLinkButton.classList.add('d-none');
if (includeLink) {
messageLinkButton.setAttribute('href', linkHref);
messageLinkButton.setAttribute('target', linkTarget);
messageLinkButton.textContent = linkText;
messageLinkButton.classList.remove(...messageLinkButton.classList);
messageLinkButton.classList.add('btn');
messageLinkButton.classList.add(linkClass);
if (allowDismiss) {
messageLinkButton.classList.add('me-2');
} else {
messageDismissButton.classList.add('d-none');
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalDiv, { backdrop: 'static', keyboard: false });
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalDiv, { backdrop: 'static', keyboard: false });
modalInstance.show();
}
return;
}
}
if (hideBackdrop) {
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalDiv, { backdrop: false, keyboard: false });
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalDiv, { backdrop: false, keyboard: false });
modalInstance.show();
}
} else {
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalDiv);
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalDiv);
modalInstance.show();
}
}
}
/*Bootstrap v5 AJAX Modal - Version 2.3.0 */
let defaultLoadingAjaxTitle = 'Details';
let defaultUpdateAjaxTitle = 'Edit';
let modalAjaxCloseButton = document.createElement('button');
modalAjaxCloseButton.type = 'button';
modalAjaxCloseButton.classList.add('btn-close');
modalAjaxCloseButton.dataset.bsDismiss = 'modal';
let modalAjaxTitle = document.createElement('h5');
modalAjaxTitle.classList.add('modal-title');
modalAjaxTitle.textContent = defaultLoadingAjaxTitle;
let modalHeader = document.createElement('div');
modalHeader.classList.add('modal-header');
modalHeader.appendChild(modalAjaxTitle);
modalHeader.appendChild(modalAjaxCloseButton);
let modalAjaxBody = document.createElement('div');
modalAjaxBody.classList.add('modal-body');
let modalAjaxUpdateButton = document.createElement('button');
modalAjaxUpdateButton.type = 'button';
modalAjaxUpdateButton.classList.add('btn', 'btn-success', 'ajax-btn-update', 'd-none');
modalAjaxUpdateButton.innerText = 'Update';
let modalAjaxFooterCloseButton = document.createElement('button');
modalAjaxFooterCloseButton.type = 'button';
modalAjaxFooterCloseButton.classList.add('btn', 'btn-primary');
modalAjaxFooterCloseButton.dataset.bsDismiss = 'modal';
modalAjaxFooterCloseButton.innerText = 'Close';
let modalAjaxFooter = document.createElement('div');
modalAjaxFooter.classList.add('modal-footer');
modalAjaxFooter.appendChild(modalAjaxUpdateButton);
modalAjaxFooter.appendChild(modalAjaxFooterCloseButton);
let modalAjaxContentDiv = document.createElement('div');
modalAjaxContentDiv.classList.add('modal-content');
modalAjaxContentDiv.appendChild(modalHeader);
modalAjaxContentDiv.appendChild(modalAjaxBody);
modalAjaxContentDiv.appendChild(modalAjaxFooter);
let modalAjaxDialogDiv = document.createElement('div', { role: 'document' });
modalAjaxDialogDiv.classList.add('modal-dialog');
modalAjaxDialogDiv.classList.add('top-5');
modalAjaxDialogDiv.appendChild(modalAjaxContentDiv);
let modalAjaxDiv = document.createElement('div', { tabindex: '-1', role: 'dialog' });
modalAjaxDiv.classList.add('modal', 'ajax-modal', 'fade');
modalAjaxDiv.appendChild(modalAjaxDialogDiv);
document.body.appendChild(modalAjaxDiv);
let spinnerAjaxDiv = document.createElement('div');
spinnerAjaxDiv.classList.add('spinner-border', 'text-alert', 'd-flex', 'mt-2', 'mx-auto');
spinnerAjaxDiv.setAttribute('role', 'status');
let modalAjaxSpan = document.createElement('span');
modalAjaxSpan.classList.add('visually-hidden');
modalAjaxSpan.innerText = 'Loading...';
spinnerAjaxDiv.appendChild(modalAjaxSpan);
let modalAjaxUpdateHiddenInput = document.createElement('input');
modalAjaxUpdateHiddenInput.setAttribute('type', 'hidden');
modalAjaxUpdateHiddenInput.setAttribute('id', 'AjaxModalHiddenId');
let modalAjaxUpdateHeader = document.createElement('h6');
let modalAjaxUpdateInput = document.createElement('textarea');
modalAjaxUpdateInput.setAttribute('id', 'AjaxModalUpdateInputId');
modalAjaxUpdateInput.classList.add('form-control');
const showLoadingModal = (title) => {
if (modalLibrary.length == 0)
return;
modalAjaxUpdateButton.classList.add('d-none');
modalAjaxFooterCloseButton.innerText = 'Close';
modalAjaxFooterCloseButton.classList.remove('btn-secondary');
modalAjaxFooterCloseButton.classList.add('btn-primary');
if (title !== '') modalAjaxTitle.textContent = title;
else modalAjaxTitle.textContent = defaultLoadingAjaxTitle;
modalAjaxBody.innerHTML = '';
modalAjaxBody.appendChild(spinnerAjaxDiv);
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalAjaxDiv);
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalAjaxDiv);
modalInstance.show();
}
}
const showUpdateModal = (id, value, header = '', title = '', rows = 1) => {
modalAjaxUpdateButton.classList.remove('d-none');
modalAjaxFooterCloseButton.innerText = 'Cancel';
modalAjaxFooterCloseButton.classList.remove('btn-primary');
modalAjaxFooterCloseButton.classList.add('btn-secondary');
if (title !== '') modalAjaxTitle.textContent = title;
else modalAjaxTitle.textContent = defaultUpdateAjaxTitle;
modalAjaxBody.innerHTML = '';
modalAjaxUpdateHiddenInput.value = id;
modalAjaxBody.appendChild(modalAjaxUpdateHiddenInput);
modalAjaxUpdateHeader.textContent = header === '' ? value : header;
modalAjaxBody.appendChild(modalAjaxUpdateHeader);
modalAjaxUpdateInput.rows = rows;
modalAjaxUpdateInput.value = value;
modalAjaxBody.appendChild(modalAjaxUpdateInput);
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalAjaxDiv);
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalAjaxDiv);
modalInstance.show();
}
}
const showResponseModal = (responseJSON) => {
modalAjaxUpdateButton.classList.add('d-none');
modalAjaxFooterCloseButton.innerText = 'Close';
modalAjaxFooterCloseButton.classList.remove('btn-secondary');
modalAjaxFooterCloseButton.classList.add('btn-primary');
let dl, dt, dd, prop;
modalAjaxBody.innerHTML = '';
let status = '';
if (responseJSON.Status !== undefined)
status = responseJSON.Status;
if (responseJSON.status !== undefined)
status = responseJSON.status;
if (status === 'Success') {
dl = document.createElement('dl');
for (prop in responseJSON) {
if (responseJSON[prop] == null) responseJSON[prop] = 'NULL';
dt = document.createElement('dt');
dt.textContent = prop;
dl.appendChild(dt);
dd = document.createElement('dd');
dd.classList.add('text-break');
dd.textContent = responseJSON[prop].length === 0 ? 'Not Found' : responseJSON[prop];
dl.appendChild(dd);
}
let successDiv = document.createElement('div');
successDiv.classList.add('alert', 'alert-success');
successDiv.appendChild(dl);
modalAjaxBody.appendChild(successDiv);
}
else {
dl = document.createElement('dl');
for (prop in responseJSON) {
if (responseJSON[prop] == null) responseJSON[prop] = 'NULL';
dt = document.createElement('dt');
dt.textContent = prop;
dl.appendChild(dt);
dd = document.createElement('dd');
dd.classList.add('text-break');
dd.textContent = responseJSON[prop].length === 0 ? 'Not Found' : responseJSON[prop];
dl.appendChild(dd);
}
let alertDiv = document.createElement('div');
alertDiv.classList.add('alert', 'alert-danger');
alertDiv.appendChild(dl);
modalAjaxBody.appendChild(alertDiv);
}
}
const showErrorModal = (error) => {
modalAjaxUpdateButton.classList.add('d-none');
modalAjaxFooterCloseButton.innerText = 'Close';
modalAjaxFooterCloseButton.classList.remove('btn-secondary');
modalAjaxFooterCloseButton.classList.add('btn-primary');
modalAjaxTitle.textContent = 'An Error Has Occurred';
modalAjaxBody.innerHTML = '';
let alertDiv = document.createElement('div');
alertDiv.classList.add('alert', 'alert-danger');
alertDiv.textContent = error;
modalAjaxBody.appendChild(alertDiv);
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalAjaxDiv);
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalAjaxDiv);
modalInstance.show();
}
}
/*Bootstrap v5 Confirm Modal - Version 1.3.0 */
let confirmBodyDiv = document.createElement('div');
confirmBodyDiv.classList.add('modal-body', 'text-center');
let confirmElement = document.createElement('h4');
let confirmClassDiv = document.createElement('div');
confirmClassDiv.appendChild(confirmElement);
confirmBodyDiv.appendChild(confirmClassDiv);
let confirmButton = document.createElement('button', { type: 'button' });
confirmBodyDiv.appendChild(confirmButton);
let cancelButton = document.createElement('button', { type: 'button' });
cancelButton.dataset.bsDismiss = 'modal';
confirmBodyDiv.appendChild(cancelButton);
let confirmContentDiv = document.createElement('div');
confirmContentDiv.classList.add('modal-content');
confirmContentDiv.appendChild(confirmBodyDiv);
let confirmDialogDiv = document.createElement('div', { role: 'document' });
confirmDialogDiv.classList.add('modal-dialog', 'top-5');
confirmDialogDiv.appendChild(confirmContentDiv);
let modalConfirmDiv = document.createElement('div', { tabindex: '-1', role: 'dialog' });
modalConfirmDiv.classList.add('modal', 'fade', 'confirm-modal');
modalConfirmDiv.appendChild(confirmDialogDiv);
document.body.appendChild(modalConfirmDiv);
let defaultConfirmMessage = 'Are you sure?';
let defaultConfirmedCallback = () => { alert('Callback is not set.') };
let defaultConfirmMessageClass = 'alert-primary';
let defaultConfirmButtonText = 'Delete';
let defaultConfirmButtonClass = 'btn-danger';
let defaultCancelButtonText = 'Cancel';
let defaultCancelButtonClass = 'btn-primary';
const showConfirmModal = (
confirmMessage = defaultConfirmMessage,
confirmedCallback = defaultConfirmedCallback,
confirmMessageClass = defaultConfirmMessageClass,
confirmButtonText = defaultConfirmButtonText,
confirmButtonClass = defaultConfirmButtonClass,
cancelButtonText = defaultCancelButtonText,
cancelButtonClass = defaultCancelButtonClass) => {
if (modalLibrary.length == 0)
return;
confirmElement.innerText = confirmMessage;
confirmClassDiv.classList.remove(...confirmClassDiv.classList);
confirmClassDiv.classList.add('alert', confirmMessageClass, 'px-2', 'py-1', 'mb-3');
confirmButton.textContent = confirmButtonText;
confirmButton.classList.remove(...confirmButton.classList);
confirmButton.classList.add('btn', 'me-2', confirmButtonClass);
confirmButton.addEventListener('click', confirmedCallback);
cancelButton.textContent = cancelButtonText;
cancelButton.classList.remove(...cancelButton.classList);
cancelButton.classList.add('btn', cancelButtonClass);
if (modalLibrary == 'BV5') {
modalInstance = new bootstrap.Modal(modalConfirmDiv);
modalInstance.show();
} else if (modalLibrary == 'BSN') {
modalInstance = new BSN.Modal(modalConfirmDiv);
modalInstance.show();
}
}
document.addEventListener('DOMContentLoaded', () => {
if (window.BSN) {
if (window.BSN.Modal) modalLibrary = 'BSN';
else alert('BootstrapNative Modal was not found.');
} else if (window.bootstrap) {
if (window.bootstrap.Modal) modalLibrary = 'BV5';
else alert('Bootstrap v5 Modal was not found.');
} else alert('Bootstrap v5 js, BootstrapNative, or jQuery is required by dynamic-modals.js.');
// V2.2.0 added
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('hide.bs.modal', () => {
document.activeElement.blur();
});
});
});