How to create a custom view for alert (), confirm () and prompt () for use in JavaScript

I have long been thinking about customizing the appearance of typical user interaction functions in JavaScript - alert (), confirm () and prompt () (hereinafter modal windows).
Indeed, they are very convenient to use, but different in different browsers and very unsightly in appearance.
Finally, the hands reached.
What is the problem? The usual means of displaying dialogs (for example, bootstrap) cannot be used as simply as alert, where the browser arranges for the JavaScript code to stop executing and waiting for the user to act (click on the close button). Modal in bootstrap will require a separate event handling - clicking on a button, closing a modal window ...
Nevertheless, I have already used customization of alerts in games to replace standard messages with those corresponding to the style of game design. This works well, including connection error messages and other system situations. But this will not work if the user needs to wait for a response!
image
With the advent of Promise in ECMAScript 6 (ES6), everything is possible!
I applied the approach of separating the design of modal windows and code (alert (), confirm () and prompt ()). But you can hide everything in the code. What attracts such an approach - the design can be changed in different projects, but simply on different pages or depending on the situation.
The bad point about this approach is the need to use markup names (id) in the code of modal windows, and even in the global scope. But this is just an example of the principle, so I will not focus on this.

Getting the code for alert


So, let's parse the markup (bootstrap and Font Awesome for font icons) and the alert code (I'm using jQuery):
    <div id="PromiseAlert" class="modal">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title"><i class="fas fa-exclamation-triangle text-warning"></i> <span>The app reports</span></h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">Γ—</span>
                    </button>
                </div>
                <div class="modal-body">
                    <p></p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
                </div>
            </div>
        </div>
    </div>

    window.alert = (message) => {
        $('#PromiseAlert .modal-body p').html(message);
        var PromiseAlert = $('#PromiseAlert').modal({
            keyboard: false,
            backdrop: 'static'
        }).modal('show');
        return new Promise(function (resolve, reject) {
            PromiseAlert.on('hidden.bs.modal', resolve);
        });
    };

As I said above, the global name PromiseAlert and the html markup classes are used for the code. In the first line of code, the alert function parameter is passed to the message body. After that, the bootstrap method displays a modal window with certain options (they make it closer to the native alert). Important! The modal window is remembered in a local variable, which is used below through closure.
Finally, it is created and returned as the result of alert Promise, in which, as a result of closing the modal window, resolve this Promise is executed.
Now let's see how this alert can be used:
    $('p a[href="#"]').on('click', async (e) => {
        e.preventDefault();
        await alert('Promise based alert sample');
    });

In this example, a message is displayed when clicking on empty links within paragraphs. Paying attention! To comply with the specification, the alert function must be preceded by the await keyword, and it can only be used inside the function with the async keyword. This allows you to expect in this place (the script will stop, as in the case with the native alert), the closing of the modal window.
What will happen if this is not done? Depends on the logic of your application (an example of such an approach in the figure above). If this is the end of the code or further actions of the code do not overload the page, then everything will probably be fine! The modal window sags until the user closes it. But if there are still modal windows or if the page reloads, there is a transition to another page, then the user simply will not see your modal window and the logic will be destroyed. I can say that from experience, messages about various server errors (states) or from code libraries work quite well with our new alert, although they do not use await.

We develop an approach for confirm


Let's go further. Without a doubt, confirm can only be used in the async / await binding, as he must tell the code the result of the user's choice. This also applies to prompt. So confirm:
    <div id="PromiseConfirm" class="modal">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title"><i class="fas fa-check-circle text-success"></i> <span>Confirm app request</span></h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">Γ—</span>
                    </button>
                </div>
                <div class="modal-body">
                    <p></p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-success" data-dismiss="modal">OK</button>
                    <button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
                </div>
            </div>
        </div>
    </div>

    window.confirm = (message) => {
        $('#PromiseConfirm .modal-body p').html(message);
        var PromiseConfirm = $('#PromiseConfirm').modal({
            keyboard: false,
            backdrop: 'static'
        }).modal('show');
        let confirm = false;
        $('#PromiseConfirm .btn-success').on('click', e => {
            confirm = true;
        });
        return new Promise(function (resolve, reject) {
            PromiseConfirm.on('hidden.bs.modal', (e) => {
                resolve(confirm);
            });
        });
    };

There is only one difference - we need to inform about the choice of the user. This is done using another local variable in the closure - confirm. If the confirmation button is pressed, the variable is set to true, and by default its value is false. Well, when processing the closing of a modal window, resolve returns this variable.
Here is the usage (required with async / await):
    $('p a[href="#"]').on('click', async (e) => {
        e.preventDefault();
        if (await confirm('Want to test the Prompt?')) {
            let prmpt = await prompt('Entered value:');
            if (prmpt) await alert(`entered: Β«${prmpt}Β»`);
            else await alert('Do not enter a value');
        }
        else await alert('Promise based alert sample');
    });

Moving on - an approach for prompt


The logic above is also implemented with the test prompt. And its markup and logic are as follows:
    <div id="PromisePrompt" class="modal">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title"><i class="fas fa-question-circle text-primary"></i> <span>Prompt request</span></h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">Γ—</span>
                    </button>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label for="PromisePromptInput"></label>
                        <input type="text" class="form-control" id="PromisePromptInput">
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-success" data-dismiss="modal">OK</button>
                    <button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
                </div>
            </div>
        </div>
    </div>

    window.prompt = (message) => {
        $('#PromisePrompt .modal-body label').html(message);
        var PromisePrompt = $('#PromisePrompt').modal({
            keyboard: false,
            backdrop: 'static'
        }).modal('show');
        $('#PromisePromptInput').focus();
        let prmpt = null;
        $('#PromisePrompt .btn-success').on('click', e => {
            prmpt = $('#PromisePrompt .modal-body input').val();
        });
        return new Promise(function (resolve, reject) {
            PromisePrompt.on('hidden.bs.modal', (e) => {
                resolve(prmpt);
            });
        });
    };

The difference between logic and confirm is minimal. An additional local variable in the closure is prmpt. And it does not have a logical value, but a string that the user enters. Through closure, its value resolves. And a value is assigned to it only when the confirmation button is pressed (from the input field). By the way, here I squandered another global variable, PromisePromptInput, just for shorthand and alternative code. With it, I set the input focus (although it can be done in a single approach - either in the same way or as in getting the value).
You can try this approach in action here . The code is located at the link .
It looks something like this (although the link above is more diverse):
image

Aids


They do not relate directly to the topic of the article, but they reveal the full flexibility of the approach.
This includes bootstrap themes. I took free themes here .
Switch language using automatic installation according to browser language. There are three modes - automatic (via browser), Russian or English (forced). The machine is installed by default.
Cookies ( from here ) I used to memorize the theme and language switch.
Themes switch simply by installing the href css segment from the above site:
    $('#themes a.dropdown-item').on('click', (e) => {
        e.preventDefault();
        $('#themes a.dropdown-item').removeClass('active');
        e.currentTarget.classList.add('active');
        var cur = e.currentTarget.getAttribute('href');
        document.head.children[4].href = 'https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/' + cur + 'bootstrap.min.css';
        var ed = new Date();
        ed.setFullYear(ed.getFullYear() + 1);
        setCookie('WebApplicationPromiseAlertTheme', cur, ed);
    });

Well, I remember in Cookies for recovery at boot:
    var cookie = getCookie('WebApplicationPromiseAlertTheme');
    if (cookie) {
        $('#themes a.dropdown-item').removeClass('active');
        $('#themes a.dropdown-item[href="' + cookie + '"]').addClass('active');
        document.head.children[4].href = 'https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/' + cookie + 'bootstrap.min.css';
    }

For localization, I used the localization.json file in which I created a dictionary of keys in English and their values ​​in Russian. For simplicity (although the markup has become more complicated in some places), I only check purely text nodes when translating, replacing a key from a value.
    var translate = () => {
        $('#cultures .dropdown-toggle samp').text({ ru: '  ', en: ' English ' }[culture]);
        if (culture == 'ru') {
            let int;
            if (localization) {
                for (let el of document.all)
                    if (el.childElementCount == 0 && el.textContent) {
                        let text = localization[el.textContent];
                        if (text) el.textContent = text;
                    }
            }
            else int = setInterval(() => {
                if (localization) {
                    translate();
                    clearInterval(int);
                }
            }, 100);
        }
        else location.reload();
    };
    if (culture == 'ru') translate();

so it’s hardly good to do in production (better on the server), but here I can demonstrate everything on the client. I access the server only when changing from Russian to English - I just overload the original markup (location.reload).
The latter, as expected, the message in onbeforeunload is issued according to the browser algorithm and our confirm does not affect this. At the end of the code there is a commented version of such a message - you can try it when you transfer it yourself.
    //window.onbeforeunload = function (e) {
    //    e.returnValue = 'Do you really want to finish the job?';
    //    return e.returnValue;
    //};

All Articles