【翻译】创建 HTML5 表单验证气泡的替代方案

在过去几年里,我发表了关于 HTML5 表单验证的文章演讲,其中最常见的问题就是关于气泡。这里的气泡是指浏览器显示验证错误信息的 UI 空间。下面分别是 Chrome、Firefox 和 IE 的实现:

无论什么原因,我们开发者(或更可能是设计师同学)都有很强的欲望想样式化这些东西。但不幸的是我们不做不到,因为没有一款浏览器提供指向这些气泡的样式。Chrome 曾经提供了一系列带供应商前缀的伪元素::-webkit-validation-bubble-*),但是它们在 Chrome 28 中被移除了

那么开发者可以做什么呢?虽然浏览器不允许你自定义他们的气泡,但 constraint validation spec 可以允许你阻止浏览器的气泡 UI 并让你自己创建。文章的余下部分将向你如何做到这一点。

警告:不要轻易的创建气泡替代方案。使用默认的气泡,可以免费获得一些非常复杂的功能,比如定位和可达性。使用自定义气泡你需要自己解决这些问题(或使用库替代)。

阻止默认气泡

创建自定义 UI 的第一步是阻止原生气泡。你可以通过监听表单控件的 invaild 事件并阻止其默认行为。举例来说,下面的表单没有验证气泡,因为在 invalid 事件处理函数中调用了 event.preventDefault()

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

invaild 事件并没有冒泡,所以如果你想阻止在多个元素上的验证气泡,你必须注册一个捕获阶段的事件监听。

如果你不了解 DOM 事件中冒泡和捕获阶段的区别,这篇 MDN 文章给出了不错的解释。

下面的代码通过在父元素 <form> 上单一的 invaild 事件监听程序阻止了两个文本框的气泡:

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

你可以通过这个方法移除浏览器的表单验证 UI,但是你一旦这样做,你必须建立一些自定义的来替代它。

建立替代 UI

显示表单验证错误的方法不计其数。没有一种是绝对的正确或者错误(好吧,有些错误的实现方法)。让我们来看看这几种你可能会采用的常见方法。对每一个,我们都会使用如下非常简单的名称和邮箱地址表单。我们还将使用一些简单的 CSS 让这份表单看起来还不错。

<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>

在开始之前有一个重要的点需要说明:所有这些 UI 只在 Internet Explorer 10 及以上版本可用,constraint validation API 在旧版中不存在。如果你需要支持旧版 IE 并且仍然希望使用 HTML5 表单验证,在这个幻灯片中我概括了一些可选方案。

替代 UI #1: 消息列表

一种显示验证错误消息的常见方式就是显示在屏幕顶部的一个框中,并且这种行为比较容易和 HTML5 表单验证 API 建立结合。在进入到代码部分之前,这是实际应用中的样子:

下面是你建立这个 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 ] );
}

这个示例为每个表单字段设定了一个对应的 <label>,并且 <label>for 属性设置成和表单字段的 id 属性一样。你也许想调整代码,建立消息以适配你自己的应用,但是其他的方法也许更容易达到目的。

替代 UI #2: 字段下面的消息

有时候,和在屏幕顶部显示消息列表相比,你更想把消息和他的对应字段管理起来。下面的 UI 实现了这点:

要建立这个 UI,大部分代码和第一种方法相同,只是在提交按钮的 click 事件处理函数上有些细小区别。

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 ] );
}

替代 UI #3: 替代气泡

最后一个 UI 介绍了一种通过 JavaScript 模仿浏览器验证气泡的方式,一种完全自定义(和可样式化)的气泡。以下是实际应用:

在这个示例中我使用了 Kendo UI tooltip,因为我不想考虑该如何处理气泡的定位逻辑。下面是我建立这个 UI 的代码。在这个实现中我选择了使用 jQuery 来清理 DOM 代码(Kendo UI 依赖于 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();
    });
});

虽然替换验证气泡需要的代码量不少,但是这带来了相当于浏览器实现的复刻版。不同之处在于 JavaScript 的实现有着更多的可定制性,实现你的心中所想。举例来说,如果你想添加一些粉色、绿色和 Comic Sans 到你的气泡中,你现在完全可以做到:

Kendo UI tooltip 小工具是 Kendo UI Core 中25+个小工具之一,来自 Kendo UI 免费和开源分发。

所以你可以在当下使用这份代码而无须担心授权限制 — 或付费。你可以直接下载 Kendo UI core 源码,或使用我们的 CDN,或者从 Bower 抓取库(bower install kendo-ui-core)

结束语

虽然你不能样式化浏览器的验证气泡,但你可以阻止他们并建立你自己的 UI。你可以随意尝试更改文章中的实现方式来迎合你自己的需求。如果你曾经使用过其他方式也欢迎留言分享。

原文:Building HTML5 Form Validation Bubble Replacements

翻译:小影

3 条评论

发表评论

*