Статьи

Создание заменителей пузыря проверки формы HTML5

Автор TJ VanToll

Я  писал  и  говорил  о проверке формы HTML5 в течение последних нескольких лет, и один из самых распространенных вопросов, которые я получаю, касается пузырей. Под пузырями я подразумеваю, что браузеры контролируют пользовательский интерфейс и отображают ошибки проверки. Ниже вы можете увидеть реализации Chrome, Firefox и IE:

пузырьки

По какой-то причине мы, разработчики (или, скорее, наши коллеги-дизайнеры), имеем глубокое желание стилизовать эти вещи. Но, к сожалению, мы не можем, так как нулевые браузеры предоставляют хуки для стиля, предназначенные для этих пузырей. Chrome использовался для  предоставления  ряда псевдоэлементов с префиксом производителя  ( ::-webkit-validation-bubble-*), но они были удалены в Chrome 28 .

Так что же делать разработчику? Ну, хотя браузеры не позволяют вам настраивать их пузыри,  спецификация проверки ограничений  позволяет вам отключить пузырьковый интерфейс браузера и создать свой собственный. В остальной части статьи показано, как это сделать.

Предупреждение: не идите по пути создания замены пузыря слегка. С пузырьками по умолчанию вы получаете довольно сложные функции, такие как позиционирование и доступность. С помощью пользовательских пузырей вы должны решить эти проблемы самостоятельно (или использовать библиотеку, которая это делает).

Подавление пузырьков по умолчанию

Первым шагом к созданию пользовательского интерфейса является подавление собственных пузырей. Вы делаете это, слушая invalid событие каждого элемента управления формы  и предотвращая его поведение по умолчанию. Например, следующая форма не использует пузырьки проверки из-за  event.preventDefault() вызова в  invalid обработчике событий.

<form>
    <input required>
    <button>Submit</button>
</form>
<script>
    document.querySelector( "input" ).addEventListener( "invalid",
        function( event ) {
            event.preventDefault();
        });
</script>

invalid Событие не пузырь (не каламбур), поэтому если вы хотите , чтобы предотвратить пузыри проверки на нескольких элементов , которые вы должны приложить захват фазы слушателя.

Если вы не понимаете различий между фазами всплытия и захвата событий DOM, ознакомьтесь с  этой статьей MDN  для довольно хорошего объяснения.

Следующий код предотвращает появление пузырьков на обоих входах с помощью одного  invalid прослушивателя событий для родительского  <form>элемента:

<form>
    <input required>
    <input required>
    <button>Submit</button>
</form>
<script>
    document.querySelector( "form" )
        .addEventListener( "invalid", function( event ) {
            event.preventDefault();
        }, true );
</script>

You can use this approach to remove the browser’s UI for form validation, but once you do so, you have build something custom to replace it.

Building alternative UIs

There are countless ways of displaying form validation errors, and none of them are necessarily right or wrong. (Ok, there are some wrong ways of doing this.) Let’s look at a few common approaches that you may want to take. For each, we’ll use the very simple name and email address form below. We’ll also use some simple CSS to make this form look halfway decent.

<form>
    <div>
        <label for="name">Name:</label>
        <input id="name" name="name" required>
    </div>
    <div>
        <label for="email">Email:</label>
        <input id="email" name="email" type="email" required>
    </div>
    <div>
        <button>Submit</button>
    </div>
</form>

One important note before we get started: all of these UIs only work in Internet Explorer version 10 and up, as theconstraint validation API is not available in older versions. If you need to support old IE and still want to use HTML5 form validation, check out this slide deck in which I outline some options for doing so.

Alternative UI #1: List of messages

A common way of showing validation errors is in a box on the top of the screen, and this behavior is relatively easy to build with the HTML5 form validation APIs. Before we get into the code, here’s what it looks like in action:

Here’s the code you need to build that UI:

function replaceValidationUI( form ) {
    // Suppress the default bubbles
    form.addEventListener( "invalid", function( event ) {
        event.preventDefault();
    }, true );

    // Support Safari, iOS Safari, and the Android browser—each of which do not prevent
    // form submissions by default
    form.addEventListener( "submit", function( event ) {
        if ( !this.checkValidity() ) {
            event.preventDefault();
        }
    });

    // Add a container to hold error messages
    form.insertAdjacentHTML( "afterbegin", "<ul class='error-messages'></ul>" );

    var submitButton = form.querySelector( "button:not([type=button]), input[type=submit]" );
    submitButton.addEventListener( "click", function( event ) {
        var invalidFields = form.querySelectorAll( ":invalid" ),
            listHtml = "",
            errorMessages = form.querySelector( ".error-messages" ),
            label;

        for ( var i = 0; i < invalidFields.length; i++ ) {
            label = form.querySelector( "label[for=" + invalidFields[ i ].id + "]" );
            listHtml += "<li>" + 
                label.innerHTML +
                " " +
                invalidFields[ i ].validationMessage +
                "</li>";
        }

        // Update the list with the new error messages
        errorMessages.innerHTML = listHtml;

        // If there are errors, give focus to the first invalid field and show
        // the error messages container
        if ( invalidFields.length > 0 ) {
            invalidFields[ 0 ].focus();
            errorMessages.style.display = "block";
        }
    });
}

// Replace the validation UI for all forms
var forms = document.querySelectorAll( "form" );
for ( var i = 0; i < forms.length; i++ ) {
    replaceValidationUI( forms[ i ] );
}

This example assumes that each form field has a corresponding <label>, where the id attribute of the form field matches the for attribute of the <label>. You may want to tweak the code that builds the messages themselves to match your applications, but other than that this should be something can simply drop in.

Alternative UI #2: Messages under fields

Sometimes, instead of showing a list of messages on the top of the screen you want to associate a message with its corresponding field. Here’s a UI that does that:

To build this UI, most of the code remains the same as the first approach, with a few subtle differences in the submit button’s click event handler.

function replaceValidationUI( form ) {
    // Suppress the default bubbles
    form.addEventListener( "invalid", function( event ) {
        event.preventDefault();
    }, true );

    // Support Safari, iOS Safari, and the Android browser—each of which do not prevent
    // form submissions by default
    form.addEventListener( "submit", function( event ) {
        if ( !this.checkValidity() ) {
            event.preventDefault();
        }
    });

    var submitButton = form.querySelector( "button:not([type=button]), input[type=submit]" );
    submitButton.addEventListener( "click", function( event ) {
        var invalidFields = form.querySelectorAll( ":invalid" ),
            errorMessages = form.querySelectorAll( ".error-message" ),
            parent;

        // Remove any existing messages
        for ( var i = 0; i < errorMessages.length; i++ ) {
            errorMessages[ i ].parentNode.removeChild( errorMessages[ i ] );
        }

        for ( var i = 0; i < invalidFields.length; i++ ) {
            parent = invalidFields[ i ].parentNode;
            parent.insertAdjacentHTML( "beforeend", "<div class='error-message'>" + 
                invalidFields[ i ].validationMessage +
                "</div>" );
        }

        // If there are errors, give focus to the first invalid field
        if ( invalidFields.length > 0 ) {
            invalidFields[ 0 ].focus();
        }
    });
}

// Replace the validation UI for all forms
var forms = document.querySelectorAll( "form" );
for ( var i = 0; i < forms.length; i++ ) {
    replaceValidationUI( forms[ i ] );
}

Alternative UI #3: Replacement bubbles

The last UI I’ll present is a way of mimicking the browser’s validation bubble with a completely custom (and styleable) bubble built with JavaScript. Here’s the implementation in action:

In this example I’m using a Kendo UI tooltip because I don’t want to worry about handling the positioning logic of the bubbles myself. The code I’m using to build this UI is below. For this implementation I chose to use jQuery to clean up the DOM code (as Kendo UI depends on jQuery).

$( "form" ).each(function() {
    var form = this;

    // Suppress the default bubbles
    form.addEventListener( "invalid", function( event ) {
        event.preventDefault();
    }, true );

    // Support Safari, iOS Safari, and the Android browser—each of which do not prevent
    // form submissions by default
    $( form ).on( "submit", function( event ) {
        if ( !this.checkValidity() ) {
            event.preventDefault();
        }
    });

    $( "input, select, textarea", form )
        // Destroy the tooltip on blur if the field contains valid data
        .on( "blur", function() {
            var field = $( this );
            if ( field.data( "kendoTooltip" ) ) {
                if ( this.validity.valid ) {
                    field.kendoTooltip( "destroy" );
                } else {
                    field.kendoTooltip( "hide" );
                }
            }
        })
        // Show the tooltip on focus
        .on( "focus", function() {
            var field = $( this );
            if ( field.data( "kendoTooltip" ) ) {
                field.kendoTooltip( "show" );
            }
        });

    $( "button:not([type=button]), input[type=submit]", form ).on( "click", function( event ) {
        // Destroy any tooltips from previous runs
        $( "input, select, textarea", form ).each( function() {
            var field = $( this );
            if ( field.data( "kendoTooltip" ) ) {
                field.kendoTooltip( "destroy" );
            }
        });

        // Add a tooltip to each invalid field
        var invalidFields = $( ":invalid", form ).each(function() {
            var field = $( this ).kendoTooltip({
                content: function() {
                    return field[ 0 ].validationMessage;
                }
            });
        });

        // If there are errors, give focus to the first invalid field
        invalidFields.first().trigger( "focus" ).eq( 0 ).focus();
    });
});

Although replacing the validation bubbles requires an unfortunate amount of code, this does come fairly close to replicating the browser’s implementation. The difference being that the JavaScript implementation is far more customizable, as you can change it to your heart’s desire. For instance, if you need to add some pink, green, and Comic Sans to your bubbles, you totally can now:

The Kendo UI tooltip widget is one of the 25+ widgets available in Kendo UI Core, the free and open source distribution of Kendo UI. So you can use this code today without worrying about licensing restrictions — or paying money. You candownload the Kendo UI core source directlyuse our CDN, or grab the library from Bower (bower install kendo-ui-core).

Wrapping Up

Although you cannot style the browser’s validation bubbles, you can suppress them and build whatever UI you’d like. Feel free to try and alter the approaches shown in this article to meet your needs. If you have any other approaches you’ve used in the past feel free to share them in the comments.