Como criar uma exibição personalizada para alert (), confirm () e prompt () para uso em JavaScript

Há muito tempo penso em personalizar a aparência de funções típicas de interação do usuário em JavaScript - alert (), confirm () e prompt () (a seguir janelas modais).
Na verdade, eles são muito convenientes de usar, mas diferentes em navegadores diferentes e com aparência muito feia.
Finalmente, as mãos alcançaram.
Qual é o problema? Os meios habituais de exibir caixas de diálogo (por exemplo, autoinicialização) não podem ser usados ​​apenas como alerta, onde o navegador solicita que o código JavaScript pare de executar e aguarde a ação do usuário (clique no botão Fechar). O modal no bootstrap exigirá uma manipulação de eventos separada - clicando em um botão, fechando uma janela modal ...
No entanto, eu já usei a personalização de alertas nos jogos para substituir as mensagens padrão pelas correspondentes ao estilo de design do jogo. Isso funciona bem, incluindo mensagens de erro de conexão e outras situações do sistema. Mas isso não funcionará se o usuário precisar esperar por uma resposta!
imagem
Com o advento da Promise no ECMAScript 6 (ES6), tudo é possível!
Eu apliquei a abordagem de separar o design de janelas modais e código (alert (), confirm () e prompt ()). Mas você pode ocultar tudo no código. O que atrai essa abordagem - o design pode ser alterado em diferentes projetos, mas simplesmente em páginas diferentes ou dependendo da situação.
O ponto negativo dessa abordagem é a necessidade de usar nomes de marcação (id) no código das janelas modais e até no escopo global. Mas este é apenas um exemplo do princípio, então não vou me concentrar nisso.

Obtendo o código para alerta


Então, vamos analisar a marcação (bootstrap e Font Awesome para ícones de fonte) e o código de alerta (estou usando o 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);
        });
    };

Como eu disse acima, o nome global PromiseAlert e as classes de marcação html são usadas para o código. Na primeira linha do código, o parâmetro da função de alerta é passado para o corpo da mensagem. Depois disso, o método de auto-inicialização exibe uma janela modal com certas opções (elas o aproximam do alerta nativo). Importante! A janela modal é lembrada em uma variável local, que é usada abaixo através do fechamento.
Por fim, ele é criado e retornado como resultado do alerta Promise, no qual, como resultado do fechamento da janela modal, a resolução da Promessa é executada.
Agora vamos ver como esse alerta pode ser usado:
    $('p a[href="#"]').on('click', async (e) => {
        e.preventDefault();
        await alert('Promise based alert sample');
    });

Neste exemplo, uma mensagem é exibida ao clicar em links vazios dentro de parágrafos. Prestando atenção! Para estar em conformidade com a especificação, a função de alerta deve ser precedida pela palavra-chave wait e só pode ser usada dentro da função com a palavra-chave async. Isso permite que você espere neste local (o script interromperá, como no caso do alerta nativo), o fechamento da janela modal.
O que acontecerá se isso não for feito? Depende da lógica do seu aplicativo (um exemplo dessa abordagem na figura acima). Se este é o fim do código ou outras ações do código não sobrecarregam a página, tudo ficará bem! A janela modal afunda até que o usuário a feche. Mas se ainda houver janelas modais ou se a página for recarregada, houver uma transição para outra página, o usuário simplesmente não verá sua janela modal e a lógica será destruída. Posso dizer que por experiência própria, mensagens sobre vários erros (estados) do servidor ou de bibliotecas de código funcionam muito bem com nosso novo alerta, embora não usem o wait.

Desenvolvemos uma abordagem para confirmar


Vamos mais longe. Sem dúvida, a confirmação só pode ser usada na ligação assíncrona / aguardada, como ele deve informar ao código o resultado da escolha do usuário. Isso também se aplica ao prompt. Então confirme:
    <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);
            });
        });
    };

Há apenas uma diferença - precisamos informar sobre a escolha do usuário. Isso é feito usando outra variável local no fechamento - confirme. Se o botão de confirmação for pressionado, a variável será configurada como true e, por padrão, seu valor será false. Bem, ao processar o fechamento de uma janela modal, resolve retorna essa variável.
Aqui está o uso (necessário com async / waitit):
    $('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');
    });

Seguindo em frente - uma abordagem para pronta


A lógica acima também é implementada com o prompt de teste. E sua marcação e lógica são as seguintes:
    <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);
            });
        });
    };

A diferença entre lógica e confirmação é mínima. Uma variável local adicional no fechamento é prmpt. E não possui um valor lógico, mas uma sequência que o usuário digita. Através do fechamento, seu valor é resolvido. E um valor é atribuído a ele somente quando o botão de confirmação é pressionado (no campo de entrada). A propósito, aqui esbanjei outra variável global, PromisePromptInput, apenas para códigos abreviados e alternativos. Com isso, defino o foco de entrada (embora possa ser feito em uma única abordagem - da mesma maneira ou na obtenção do valor).
Você pode tentar esta abordagem em ação aqui . O código está localizado no link .
Parece algo assim (embora o link acima seja mais diversificado):
imagem

Aids


Eles não se relacionam diretamente ao tópico do artigo, mas revelam toda a flexibilidade da abordagem.
Isso inclui temas de inicialização. Eu peguei temas gratuitos aqui .
Alterne o idioma usando a instalação automática de acordo com o idioma do navegador. Existem três modos - automático (via navegador), russo ou inglês (forçado). A máquina está instalada por padrão.
Cookies ( aqui ) Eu costumava memorizar a mudança de tema e idioma.
Os temas mudam simplesmente instalando o segmento href css no site acima:
    $('#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);
    });

Bem, eu lembro nos Cookies para recuperação na inicialização:
    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';
    }

Para localização, usei o arquivo localization.json no qual criei um dicionário de chaves em inglês e seus valores em russo. Para simplificar (embora a marcação tenha se tornado mais complicada em alguns lugares), apenas verifico apenas nós de texto ao traduzir, substituindo uma chave por um valor.
    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();

portanto, dificilmente é bom fazer na produção (melhor no servidor), mas aqui eu posso demonstrar tudo no cliente. Eu acesso o servidor apenas ao mudar de russo para inglês - apenas sobrecarrego a marcação original (location.reload).
Este último, como esperado, a mensagem em onbeforeunload é emitida de acordo com o algoritmo do navegador e nossa confirmação não afeta isso. No final do código, há uma versão comentada dessa mensagem - você pode experimentá-la quando a transferir.
    //window.onbeforeunload = function (e) {
    //    e.returnValue = 'Do you really want to finish the job?';
    //    return e.returnValue;
    //};

All Articles