Comment créer une vue personnalisée pour alert (), confirm () et prompt () pour une utilisation en JavaScript

Je pense depuis longtemps à personnaliser l'apparence des fonctions d'interaction utilisateur typiques dans JavaScript - alert (), confirm () et prompt () (ci-après fenêtres modales).
En effet, ils sont très pratiques à utiliser, mais différents selon les navigateurs et d'apparence très disgracieuse.
Enfin, les mains se tendirent.
Quel est le problème? Les moyens habituels d'affichage des boîtes de dialogue (par exemple, bootstrap) ne peuvent pas être utilisés aussi simplement que l'alerte, où le navigateur s'arrête pour que le code JavaScript cesse de s'exécuter et attend que l'utilisateur agisse (cliquez sur le bouton de fermeture). Le modal dans le bootstrap nécessitera une gestion des événements distincte - en cliquant sur un bouton, en fermant une fenêtre modale ...
Néanmoins, j'ai déjà utilisé la personnalisation des alertes dans les jeux pour remplacer les messages standard par ceux correspondant au style de conception du jeu. Cela fonctionne bien, y compris les messages d'erreur de connexion et d'autres situations système. Mais cela ne fonctionnera pas si l'utilisateur doit attendre une réponse!
image
Avec l'avènement de Promise dans ECMAScript 6 (ES6), tout est possible!
J'ai appliqué l'approche de séparation de la conception des fenêtres modales et du code (alert (), confirm () et prompt ()). Mais vous pouvez tout cacher dans le code. Ce qui attire une telle approche - la conception peut être modifiée dans différents projets, mais simplement sur différentes pages ou en fonction de la situation.
Le mauvais point de cette approche est la nécessité d'utiliser des noms de balisage (id) dans le code des fenêtres modales, et même dans la portée globale. Mais ce n'est qu'un exemple du principe, donc je ne me concentrerai pas là-dessus.

Obtention du code d'alerte


Jetons donc un œil au balisage (bootstrap et Font Awesome pour les icônes de polices) et au code d'alerte (j'utilise 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);
        });
    };

Comme je l'ai dit ci-dessus, le nom global PromiseAlert et les classes de balisage html sont utilisés pour le code. Dans la première ligne de code, le paramètre de la fonction d'alerte est transmis au corps du message. Après cela, la méthode bootstrap affiche une fenêtre modale avec certaines options (elles la rapprochent de l'alerte native). Important! La fenêtre modale est mémorisée dans une variable locale, qui est utilisée ci-dessous pendant la fermeture.
Enfin, il est créé et renvoyé à la suite d'une promesse d'alerte, dans laquelle, à la suite de la fermeture de la fenêtre modale, la résolution de cette promesse est exécutée.
Voyons maintenant comment cette alerte peut être utilisée:
    $('p a[href="#"]').on('click', async (e) => {
        e.preventDefault();
        await alert('Promise based alert sample');
    });

Dans cet exemple, un message s'affiche lorsque vous cliquez sur des liens vides dans les paragraphes. Prêter attention! Pour se conformer à la spécification, la fonction d'alerte doit être précédée du mot-clé wait, et elle ne peut être utilisée qu'à l'intérieur de la fonction avec le mot-clé async. Cela vous permet d'attendre à cet endroit (le script arrêtera, comme dans le cas de l'alerte native), la fermeture de la fenêtre modale.
Que se passera-t-il si cela n'est pas fait? Dépend de la logique de votre application (un exemple d'une telle approche dans la figure ci-dessus). Si c'est la fin du code ou que d'autres actions du code ne surchargent pas la page, alors tout ira probablement bien! La fenêtre modale s'affaisse jusqu'à ce que l'utilisateur la ferme. Mais s'il y a encore des fenêtres modales ou si la page se recharge, il y a une transition vers une autre page, alors l'utilisateur ne verra tout simplement pas votre fenêtre modale et la logique sera détruite. Je peux dire que par expérience, les messages sur diverses erreurs de serveur (états) ou provenant des bibliothèques de code fonctionnent assez bien avec notre nouvelle alerte, bien qu'ils n'utilisent pas attendre.

Nous développons une approche pour confirmer


Allons plus loin. Sans aucun doute, confirm ne peut être utilisé que dans la liaison asynchrone / wait, car il doit indiquer au code le résultat du choix de l'utilisateur. Cela s'applique également à l'invite. Alors confirmez:
    <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);
            });
        });
    };

Il n'y a qu'une seule différence - nous devons informer sur le choix de l'utilisateur. Cela se fait en utilisant une autre variable locale dans la fermeture - confirmez. Si vous appuyez sur le bouton de confirmation, la variable est définie sur true et, par défaut, sa valeur est false. Eh bien, lors du traitement de la fermeture d'une fenêtre modale, la résolution renvoie cette variable.
Voici l'utilisation (requise avec async / wait):
    $('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');
    });

Aller de l'avant - une approche rapide


La logique ci-dessus est également implémentée avec l'invite de test. Et son balisage et sa logique sont les suivants:
    <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);
            });
        });
    };

La différence entre la logique et la confirmation est minime. Une variable locale supplémentaire dans la fermeture est prmpt. Et il n'a pas de valeur logique, mais une chaîne que l'utilisateur entre. Grâce à la fermeture, sa valeur disparaît. Et une valeur ne lui est affectée que lorsque le bouton de confirmation est enfoncé (depuis le champ de saisie). Soit dit en passant, j'ai gaspillé ici une autre variable globale, PromisePromptInput, juste pour le raccourci et le code alternatif. Avec lui, je règle le focus d'entrée (même si cela peut être fait en une seule approche - soit de la même manière que pour obtenir la valeur).
Vous pouvez essayer cette approche en action ici . Le code est situé sur le lien .
Cela ressemble à ceci (bien que le lien ci-dessus soit plus diversifié):
image

Sida


Ils ne se rapportent pas directement au sujet de l'article, mais ils révèlent toute la flexibilité de l'approche.
Cela inclut les thèmes d'amorçage. J'ai pris des thèmes gratuits ici .
Changez de langue en utilisant l'installation automatique en fonction de la langue du navigateur. Il existe trois modes - automatique (via navigateur), russe ou anglais (forcé). La machine est installée par défaut.
Cookies ( d'ici ) J'avais l'habitude de mémoriser le changement de thème et de langue.
Les thèmes changent simplement en installant le segment href css à partir du site ci-dessus:
    $('#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);
    });

Eh bien, je me souviens dans les cookies pour la récupération au démarrage:
    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';
    }

Pour la localisation, j'ai utilisé le fichier localization.json dans lequel j'ai créé un dictionnaire de clés en anglais et leurs valeurs en russe. Par souci de simplicité (bien que le balisage soit devenu plus compliqué à certains endroits), je vérifie uniquement les nœuds purement textuels lors de la traduction, en remplaçant une clé d'une valeur.
    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();

donc ce n'est pas bien de faire en production (mieux sur le serveur), mais ici je peux tout montrer sur le client. J'accède au serveur uniquement lorsque je passe du russe à l'anglais - je surcharge simplement le balisage d'origine (location.reload).
Ce dernier, comme prévu, le message dans onbeforeunload est émis selon l'algorithme du navigateur et notre confirmation n'affecte pas cela. À la fin du code, il existe une version commentée d'un tel message - vous pouvez l'essayer lorsque vous le transférez vous-même.
    //window.onbeforeunload = function (e) {
    //    e.returnValue = 'Do you really want to finish the job?';
    //    return e.returnValue;
    //};

All Articles