Cómo crear una vista personalizada para alert (), confirm () y prompt () para usar en JavaScript

Durante mucho tiempo he estado pensando en personalizar la apariencia de las funciones típicas de interacción del usuario en JavaScript: alert (), confirm () y prompt () (en adelante ventanas modales).
De hecho, son muy convenientes de usar, pero diferentes en diferentes navegadores y de aspecto muy desagradable.
Finalmente, las manos llegaron.
¿Cuál es el problema? Los medios habituales para emitir diálogos (por ejemplo, bootstrap) no se pueden usar tan fácilmente como una alerta, donde el navegador organiza el código JavaScript para dejar de ejecutarse y esperar la acción del usuario (haciendo clic en el botón Cerrar). Modal en bootstrap requerirá un manejo de eventos por separado: hacer clic en un botón, cerrar una ventana modal ...
Sin embargo, ya he usado la personalización de alertas en los juegos para reemplazar los mensajes estándar con los correspondientes al estilo del diseño del juego. Esto funciona bien, incluidos los mensajes de error de conexión y otras situaciones del sistema. ¡Pero esto no funcionará si el usuario necesita esperar una respuesta!
imagen
Con el advenimiento de Promise en ECMAScript 6 (ES6), ¡todo es posible!
Apliqué el enfoque de separar el diseño de ventanas modales y código (alert (), confirm () y prompt ()). Pero puedes ocultar todo en el código. Lo que atrae este enfoque: el diseño se puede cambiar en diferentes proyectos, pero simplemente en diferentes páginas o dependiendo de la situación.
El punto negativo de este enfoque es la necesidad de usar nombres de marcado (id) en el código de las ventanas modales, e incluso en el ámbito global. Pero este es solo un ejemplo del principio, por lo que no me enfocaré en esto.

Obtener el código de alerta


Entonces, analicemos el marcado (bootstrap y Font Awesome para los iconos de fuente) y el código de alerta (estoy usando 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 dije anteriormente, el nombre global PromiseAlert y las clases de marcado html se usan para el código. En la primera línea de código, el parámetro de la función de alerta se pasa al cuerpo del mensaje. Después de eso, el método bootstrap muestra una ventana modal con ciertas opciones (lo acercan más a la alerta nativa). ¡Importante! La ventana modal se recuerda en una variable local, que se utiliza a continuación hasta el cierre.
Finalmente, se crea y devuelve como resultado de la Promesa de alerta, en la cual, como resultado del cierre de la ventana modal, se resuelve esta Promesa.
Ahora veamos cómo se puede usar esta alerta:
    $('p a[href="#"]').on('click', async (e) => {
        e.preventDefault();
        await alert('Promise based alert sample');
    });

En este ejemplo, se muestra un mensaje al hacer clic en los enlaces vacíos dentro de los párrafos. ¡Prestar atención! Para cumplir con la especificación, la función de alerta debe estar precedida por la palabra clave de espera, y solo se puede usar dentro de la función con la palabra clave asíncrona. Esto le permite esperar en este lugar (el script detendrá, como en el caso de la alerta nativa), el cierre de la ventana modal.
¿Qué pasará si esto no se hace? Depende de la lógica de su aplicación (un ejemplo de este enfoque en la figura anterior). Si este es el final del código o acciones adicionales del código no sobrecarguen la página, ¡entonces todo probablemente estará bien! La ventana modal se hunde hasta que el usuario la cierra. Pero si todavía hay ventanas modales o si la página se vuelve a cargar, hay una transición a otra página, entonces el usuario simplemente no verá su ventana modal y la lógica será destruida. Puedo decir que, por experiencia, los mensajes sobre varios errores de servidor (estados) o de bibliotecas de códigos funcionan bastante bien con nuestra nueva alerta, aunque no se usan en espera.

Desarrollamos un enfoque para confirmar


Vamos más allá. Sin duda, confirmar solo se puede utilizar en el enlace asíncrono / espera, como debe decirle al código el resultado de la elección del usuario. Esto también se aplica a prompt. Entonces confirma:
    <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);
            });
        });
    };

Solo hay una diferencia: necesitamos informar sobre la elección del usuario. Esto se hace usando otra variable local en el cierre - confirme. Si se presiona el botón de confirmación, la variable se establece en verdadero y, por defecto, su valor es falso. Bueno, al procesar el cierre de una ventana modal, la resolución devuelve esta variable.
Aquí está el uso (requerido con 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');
    });

Continuando - un enfoque para la pronta


La lógica anterior también se implementa con el indicador de prueba. Y su marcado y lógica son los siguientes:
    <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 diferencia entre lógica y confirmación es mínima. Una variable local adicional en el cierre es prmpt. Y no tiene un valor lógico, sino una cadena que el usuario ingresa. A través del cierre, su valor se resuelve. Y se le asigna un valor solo cuando se presiona el botón de confirmación (desde el campo de entrada). Por cierto, aquí desperdicié otra variable global, PromisePromptInput, solo por taquigrafía y código alternativo. Con él, configuro el enfoque de entrada (aunque se puede hacer en un solo enfoque, ya sea de la misma manera o como para obtener el valor).
Puede probar este enfoque en acción aquí . El código se encuentra en el enlace .
Se parece a esto (aunque el enlace de arriba es más diverso):
imagen

SIDA


No se relacionan directamente con el tema del artículo, pero revelan la flexibilidad total del enfoque.
Esto incluye temas de arranque. Tomé temas gratis aquí .
Cambie el idioma utilizando la instalación automática según el idioma del navegador. Hay tres modos: automático (a través del navegador), ruso o inglés (forzado). La máquina está instalada por defecto.
Cookies ( desde aquí ) Solía ​​memorizar el tema y el cambio de idioma.
Los temas cambian simplemente instalando el segmento href css del sitio anterior:
    $('#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);
    });

Bueno, recuerdo en Cookies para recuperación en el arranque:
    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 la localización, utilicé el archivo localization.json en el que creé un diccionario de claves en inglés y sus valores en ruso. Por simplicidad (aunque el marcado se ha vuelto más complicado en algunos lugares), solo verifico los nodos puramente de texto al traducir, reemplazando una clave de un 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();

así que no es bueno hacerlo en producción (mejor en el servidor), pero aquí puedo demostrar todo en el cliente. Accedo al servidor solo cuando cambio de ruso a inglés; simplemente sobrecargo el marcado original (location.reload).
Este último, como se esperaba, emite el mensaje en onbeforeunload de acuerdo con el algoritmo del navegador y nuestra confirmación no afecta esto. Al final del código hay una versión comentada de dicho mensaje: puede probarlo cuando lo transfiere usted mismo.
    //window.onbeforeunload = function (e) {
    //    e.returnValue = 'Do you really want to finish the job?';
    //    return e.returnValue;
    //};

All Articles