ASP.NET Core 8.0 - Message Generator

This article will describe the implementation of a global Bootstrap message modal. You should review the earlier articles of the Cookies And Claims Project series. Registered users can download the ASP.NET Core 8.0 - Cookies And Claims Project for free.

Cookies And Claims Project and Article Series

Free project download for registered users!

I developed the Cookies And Claims Project (CACP) to demonstrate a simple cookie authentication scheme and claim-based authorization with a clear and modifiable design. The CACP is developed with Visual Studio 2022 and the MS Long Term Support (LTS) version .NET 8.0 framework. All Errors, Warnings, and Messages from Code Analysis have been mitigated. The CACP implements utilities like an AES Cipher, Password Hasher, native JavaScript client form validation, password requirements UI, Bootstrap Native message modal generator, loading/spinner generator, SignalR online user count, and an automatic idle logout for users with administration permissions.

The CACP implements a group of utilities: AES Cipher, Password Hasher, Password Validation, Message Generator, and Spinner Generator. This article describes the Message Generator. I developed a Bootstrap message modal for ASP.NET Core v3.1 over 3 years ago. See ASP.NET Core 3.1 - Message Modal.

A message to inform the user something went wrong, and they need to start over is the most important. This oops message needs to prevent the user from continuing on the current page and provide a link to the start over page. The first oops message in the Users Without Passwords Project was a Bootstrap-jQuery modal with no dismiss button, a link to start over, a 'static' backdrop and keyboard=false. I liked it enough to develop a success message to present before automatically navigating to the goal. I liked that enough to develop message-modal.js which dynamically creates the html. Variables reference the created modal components which allow the message modal to co-exist with other modals. It can be loaded in _Layout. cshtml for global access or used locally by loading from the page. The showMessageModal() function has defaults and parameters. Since then I developed an AJAX Modal and a Confirm Modal. I bundled all 3 into a dynamic-modals.js library. This library supports Bootstrap v4 and v5 css and works with component libraries like jQuery, Bootstrap.js, and Bootstrap Native.

dynamic-modals.js
'use strict'
/*!
* Copyright © Ken Haggerty (https://kenhaggerty.com)
* Licensed under the MIT License.
* dynamic-modals.js - Version 2.0.0
* Bootstrap v5 and v4 Message Modal - Version 2.2.3
* Bootstrap v5 and v4 AJAX Modal - Version 2.2.4
* Bootstrap v5 and v4 Confirm Modal - Version 1.2.2
* Depends on Bootstrap v5 js, Bootstrap Native, or jQuery with Bootstrap js
*/

let modalLibrary = '';
let modalInstance = null;

/*Bootstrap v5 and v4 Message Modal - Version 2.2.3 */
let messageModalDefaults = {
    Message: 'Thank you for supporting KenHaggerty.Com.',
    MessageClass: 'alert-primary',
    MessageAllowDismiss: true,
    MessageDismissText: 'OK',
    MessageDismissClass: 'btn-primary',
    MessageIncludeLink: false,
    MessageLinkHref: 'https://kenhaggerty.com',
    MessageLinkTarget: '_self',
    MessageLinkText: 'KenHaggerty.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.dismiss = 'modal'; // v4
messageDismissButton.dataset.bsDismiss = 'modal'; // v5
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', ariaHidden: "true" });
modalDiv.classList.add('modal');
modalDiv.classList.add('message-modal');
modalDiv.classList.add('fade');
modalDiv.appendChild(modalDialogDiv);

document.body.appendChild(modalDiv);

function 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('mr-2', 'me-2'); // v4 & v5
        } 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();
            } else if (modalLibrary == 'JQ$') {
                $('.message-modal').modal('dispose');
                $('.message-modal').modal({ backdrop: 'static', keyboard: false });
                $('.message-modal').modal('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 == 'JQ$') {
            $('.message-modal').modal('dispose');
            $('.message-modal').modal({ backdrop: false, keyboard: false });
            $('.message-modal').modal('show');
        }
    } else {
        if (modalLibrary == 'BV5') {
            modalInstance = new bootstrap.Modal(modalDiv);
            modalInstance.show();
        } else if (modalLibrary == 'BSN') {
            modalInstance = new BSN.Modal(modalDiv);
            modalInstance.show();
        } else if (modalLibrary == 'JQ$') {
            $('.message-modal').modal('dispose');
            $('.message-modal').modal('show');
        }
    }
}

/*Bootstrap v5 and v4 AJAX Modal - Version 2.2.4 */
let defaultLoadingAjaxTitle = 'Details';
let defaultUpdateAjaxTitle = 'Edit';

let modalAjaxCloseButton = document.createElement('button');
modalAjaxCloseButton.type = 'button';
modalAjaxCloseButton.ariaHidden = 'Close';
modalAjaxCloseButton.classList.add('btn-close');
modalAjaxCloseButton.dataset.dismiss = 'modal'; // v4
modalAjaxCloseButton.dataset.bsDismiss = 'modal'; // v5

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', ariaHidden: 'true' });
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');

function 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();
    } else if (modalLibrary == 'JQ$') {
        $('.ajax-modal').modal('dispose');
        $('.ajax-modal').modal('show');
    }
}

function 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();
    } else if (modalLibrary == 'JQ$') {
        $('.ajax-modal').modal('dispose');
        $('.ajax-modal').modal('show');
    }
}

function 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) continue;
            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) {
            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);
    }
}

function 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();
    } else if (modalLibrary == 'JQ$') {
        $('.ajax-modal').modal('dispose');
        $('.ajax-modal').modal('show');
    }
}

/*Bootstrap v5 and v4 Confirm Modal - Version 1.2.2 */
/* 1.2.2 Added alert class to confirmClassDiv. */
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.dismiss = 'modal'; // v4
cancelButton.dataset.bsDismiss = 'modal'; // v5
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', ariaHidden: "true" });
modalConfirmDiv.classList.add('modal', 'fade', 'confirm-modal');
modalConfirmDiv.appendChild(confirmDialogDiv);

document.body.appendChild(modalConfirmDiv);

let defaultConfirmMessage = 'Are you sure?';
let defaultConfirmedCallback = function () { alert('Callback is not set.') };
let defaultConfirmMessageClass = 'alert-primary';
let defaultConfirmButtonText = 'Delete';
let defaultConfirmButtonClass = 'btn-danger';
let defaultCancelButtonText = 'Cancel';
let defaultCancelButtonClass = 'btn-primary';

function 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', 'mr-2', 'me-2', confirmButtonClass); // v4 & v5

    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();
    } else if (modalLibrary == 'JQ$') {
        $('.confirm-modal').modal('dispose');
        $('.confirm-modal').modal('show');
    }
}

document.addEventListener('DOMContentLoaded', function () {

    if (window.jQuery) {
        if ($.fn.modal) modalLibrary = 'JQ$';
        else alert('Bootstrap-jQuery modal was not found.');
    } else 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.');

});

The CACP includes a Modal Message Generator which dynamically creates the minimal signature for the showMessageModal function with examples.

Message Modal Generator.

The CACP includes a sample of the AJAX Modal and Confirm Modal on the Admin page.

Ajax Modal Message.
Confirm Modal Message.
Ken Haggerty
Created 10/24/24
Updated 10/24/24 20:44 GMT

Log In or Reset Quota to read more.

Successfully completed. Thank you for contributing.
Processing...
Something went wrong. Please try again.
Contribute to enjoy content without advertisments.
You can contribute without registering.

Comments(0)

Loading...
Loading...

Not accepting new comments.

Submit your comment. Comments are moderated.

User Image.
DisplayedName - Member Since ?