表单验证第四部分: MailChimp订阅表单中的验证

发布于 大漠

本文转载自:众成翻译 译者:cherryjin 链接:http://www.zcfy.cc/article/3680 原文:https://css-tricks.com/form-validation-part-4-validating-mailchimp-subscribe-form/

在这个系列的上几篇文章中, 我们已经学会了如何使用输入类型和验证属性本地进行表单验证。

我们也学会了如何使用约束验证 API 来提高浏览器原生的验证能力以提供更好的用户体验。 并且我们写了一个 polyfill 将支持一直延伸到IE9(并在一些较新版本中添加了几个功能).

现在, 让我们把学到的知识运用到一个真实的例子中:MailChimp注册表单。

文章系列:

一个简单但臃肿的表单

当你在自己的网站上嵌入一个 MailChimp 注册表单时 , 它附带一个名为 mc-validate.js的JavaScript验证脚本。

这个文件有 140kb (缩小后), 并且引入了整个jQuery库, 两个第三方的插件,和一些原生的 MailChimp 代码。然而我们可以做的更好!

查看 Chris Ferdinandi的演示Form Validation: The MailChimp Standard Signup Form(@cferdinandi)在CodePen上。

去除臃肿

首先, 让我们先得到一个没有任何多余修饰的 MailChimp 表单。

在MailChimp, 点击"Naked"标签,你可以获取嵌入式表单的代码。此版本不包含任何MailChimp CSS或JavaScript。

<div id="mc_embed_signup">
    <form action="//us1.list-manage.com/subscribe/post?u=12345abcdef&id=abc123" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
        <div id="mc_embed_signup_scroll">
            <h2>Subscribe to our mailing list</h2>
            <div class="indicates-required"><span class="asterisk">*</span> indicates required</div>
            <div class="mc-field-group">
                <label for="mce-FNAME">First Name </label>
                <input type="text" value="" name="FNAME" class="" id="mce-FNAME">
            </div>
            <div class="mc-field-group">
                <label for="mce-EMAIL">Email Address  <span class="asterisk">*</span></label>
                <input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
            </div>
                <div id="mce-responses" class="clear">
                    <div class="response" id="mce-error-response" style="display:none"></div>
                    <div class="response" id="mce-success-response" style="display:none"></div>
                </div>    <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
                <div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_f2d244c0df42a0431bd08ddea_aeaa9dd034" tabindex="-1" value=""></div>
                <div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
        </div>
    </form>
</div>

这样虽然很好,但它仍然包含了一些我们不需要的标记。下面,让我们尽可能的减少这个。

  • 我们可以删除包裹这个表达的 div#mc_embed_signup
  • 同样的, 我们可以删除包裹在表单域外围的 div#mc_embed_signup_scroll
  • 我们可以删除 "* indicates required."的信息提示。
  • 让我们删除表单域周围的.mc-field-group 类 ,以及在字段本身的空 class 属性 。
  • 我们也应该删除email字段的 .required.email类 , 因为它们仅用作MailChimp验证脚本的钩子。
  • 继续从 email label中删除 * 。这完全取决于你想要标注自段的方式。
  • 我们可以删除只用于MailChimp JavaScript文件的.div#mce-responses 容器。
  • 我们也可以从提交按钮周围的div删除 .clear 类。
  • 让我们删除所有的空的 value 属性。
  • 最后, 我们应该删除form 元素的 novalidate属性 。 我们会在脚本加载时再添加此属性。

所有的这一切会让我们拥有一个更整洁的表单代码。 因为 MailChimp CSS 已经被移除了, 它将继承你网站的默认的表单样式。

<form action="//us1.list-manage.com/subscribe/post?u=12345abcdef&id=abc123" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank">
    <h2>Subscribe to our mailing list</h2>
    <div>
        <label for="mce-FNAME">First Name</label>
        <input type="text" name="FNAME" id="mce-FNAME">
    </div>
    <div>
        <label for="mce-EMAIL">Email Address</label>
        <input type="email" name="EMAIL" id="mce-EMAIL">
    </div>
    <div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_f2d244c0df42a0431bd08ddea_aeaa9dd034" tabindex="-1" value=""></div>
    <div><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</form>

查看 Chris Ferdinandi的演示 Form Validation: The MailChimp Simple Signup Form(@cferdinandi) 在 CodePen上。

添加约束验证

现在, 让我们添加一些输入类型和验证属性以至于浏览器可以原生地验证我们的表单。

email字段的 type设置为 email, 这很棒。让我们添加 required 属性,以及一个 pattern 来强制输入的email包含一个TLD (即email地址中的 .com)。我们也应该包含一个 title 来提示用户他们需要拥有一个TLD。

<form action="//us1.list-manage.com/subscribe/post?u=12345abcdef&id=abc123" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank">
    <h2>Subscribe to our mailing list</h2>
    <div>
        <label for="mce-FNAME">First Name</label>
        <input type="text" name="FNAME" id="mce-FNAME">
    </div>
    <div>
        <label for="mce-EMAIL">Email Address</label>
        <input type="email" name="EMAIL" id="mce-EMAIL" title="The domain portion of the email address is invalid (the portion after the @)." pattern="^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$" required>
    </div>
    <div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_f2d244c0df42a0431bd08ddea_aeaa9dd034" tabindex="-1" value=""></div>
    <div><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</form>

使用约束验证API来增强

这是一个很好的起点,但是我们可以通过添加本系列前面写过的表单验证脚本来优化用户体验。

查看Chris Ferdinandi的演示Form Validation: MailChimp with the Constraint Validation API (@cferdinandi) 在 CodePen上.

我们的验证脚本在压缩前只有 6.7kb , 比MailChimp提供的格式小20倍。 如果我们想要支持IE9以下版本的浏览器, 我们可以包含我们的 Validity State polyfill和 Eli Grey的 classList.js polyfill.

查看 Chris Ferdinandi 的演示Form Validation: MailChimp with the API Script and Polyfills (@cferdinandi) 在 CodePen上.

这会使我们的文件大小达到 15.5kb 非压缩情况下仍然比 MailChimp 验证脚本小9倍。

通过Ajax提交表单

MailChimp提供的 mc-validate.js 脚本并不仅仅是用于验证表单。 它还用于使用Ajax来提交表单并显示状态信息。

当你在我们修改后的表单上点击提交时, 它会将访问者重定向到 MailChimp 网站。 这是一个非常有效的处理方式。

但是, 我们也可以重新创建没有jQuery的MailChimp的Ajax表单提交,以获得更好的用户体验。

我们需要做的第一件事就是阻止表单默认情况下通过页面重新加载。 在我们的 submit 事件监听器里, 如果有错误,我们调用 event.preventDefault 。 相反的, 我们可以调用任何方法。

// 在提交时检查所有的字段
document.addEventListener('submit', function (event) {

    // 只运行在标记为'validate'的表单上
    if (!event.target.classList.contains('validate')) return;

    //阻止表单提交
    event.preventDefault();

    ...

}, false);

查看 Chris Ferdinandi的演示 Form Validation: MailChimp and Prevent Default on Submit (@cferdinandi) 在 CodePen上.

使用 JSONP

mc-validate.js 脚本使用 JSONP来绕过跨域时的安全性问题。

JSONP通过将返回的数据作为脚本元素加载到document中,然后将该数据传递到一个回调函数中,回调函数执行所有复杂的操作。

设置我们提交的URL

首先,我们设置一个在表单准备提交时运行的函数 , 并且在 submit 事件监听器里调用它。

// 提交表单
var submitMailChimpForm = function (form) {
    // 在这里写代码...
};

// 在提交时检查所有的字段
document.addEventListener('submit', function (event) {

    ...

    // 否则, 使表单正常提交
    // 您也可以在此处填入Ajax表单提交过程
    submitMailChimpForm(event.target);

}, false);

我们需要做的第一件事是从表单的action 属性中获取 URL。

// 提交表单
var submitMailChimpForm = function (form) {

    // 获取提交的URL
    var url = form.getAttribute('action');

};

mc-validate.js 脚本中, URL中的 /post?u=' 会被 /post-json?u=替代。我们可以用 replace()方法很容易的做到这一点。

// 提交表单
var submitMailChimpForm = function (form) {

    // 获取提交时的URL
    var url = form.getAttribute('action');
    url = url.replace('/post?u=', '/post-json?u=');

};

序列化我们的表单数据

接下来,我们要抓取所有表单域数据,并创建一个键/值对的查询字符串。例如, FNAME=Freddie%20Chimp&EMAIL=freddie@mailchimp.com.

让我们创建另一个函数来处理这个问题。

// 序列化表单数据为一个查询字符串
var serialize = function (form) {
    // 在这里编写代码...
};

现在, 我们循环所有的表单域并且创建键/值对。我将为 Simon Steinberger 所做的工作奠定基础。

首先,创建一个 serialized变量并设置为一个空字符串。

// 序列化表单数据为一个字符串
// Forked and modified from https://stackoverflow.com/a/30153391/1293256
var serialize = function (form) {

    // 存放我们序列化的数据
    var serialized = '';

};

现在让我们使用 form.elements来获取所有的字段并且遍历它们。

如果这个字段没有name,是提交或者按钮,被禁用,或者是文件,重置输入,我们将略过它。

如果它不是一个checkbox 或者radio ( select, textarea,以及各种 input 类型的一个很好的接口) 或者它是并且已经被检测过, 我们将它转换成一个键/值对时,在开头添加一个 & , 并附加到我们的 serialized字符串。我们还将确保对URL中使用的键和值进行编码。

最后, 我们会返回这个serialized 字符串。

// 序列化表单数据为一个字符串
// 从https://stackoverflow.com/a/30153391/1293256转载并修改
var serialize = function (form) {

    // 设置我们的序列化数据
    var serialized = '';

    //  遍历表单中的每个字段
    for (i = 0; i < form.elements.length; i++) {

        var field = form.elements[i];

        // 不要序列化没有名称,提交,按钮,文件和重置输入和禁用字段的字段
        if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue;

        // 将字段数据转换为查询字符串
        if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
            serialized += '&' + encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value);
        }
    }

    return serialized;

};

查看 Chris Ferdinandi的演示 Form Validation: MailChimp with Ajax Submit - Serialized Form Data (@cferdinandi) 在 CodePen上.

现在我们拥有了序列化的表单数据, 我们可以将其添加到URL中。

// 提交表单
var submitMailChimpForm = function (form) {

    // 获取提交的 URL
    var url = form.getAttribute('action');
    url = url.replace('/post?u=', '/post-json?u=');
    url += serialize(form);

};

添加回调

JSONP工作的关键部分就是回调.

传统的Ajax请求只是将数据返回给你。而JSONP则是将数据传入一个回调函数。这个函数必须是全局的 (即, 附于 window 而不是其他的函数)。

让我们创建一个回调函数,并将返回的数据记录在控制台中,以便我们可以查看MailChimp返回的内容。

// 显示表单的状态
var displayMailChimpStatus = function (data) {
    console.log(data);
};

现在我们可以将这个回调添加到我们的URL。 大多数JSONP使用callback作为查询字符串键, d但是 MailChimp 使用 c.

// 提交表单
var submitMailChimpForm = function (form) {

    //获取提交的 URL
    var url = form.getAttribute('action');
    url = url.replace('/post?u=', '/post-json?u=');
    url += serialize(form) + '&c=displayMailChimpStatus';

};

将我们的脚本注入到DOM中

现在我们已经准备好将脚本注入到DOM中。首先, 我们会创建一个新的 script 元素 并且把我们的 URL指定为它的 src

// 提交表单
var submitMailChimpForm = function (form) {

    // 获取提价URL
    var url = form.getAttribute('action');
    url = url.replace('/post?u=', '/post-json?u=');
    url += serialize(form) + '&c=displayMailChimpStatus';

    // 使用url和回调创建脚本 (如果指定的话)
    var script = window.document.createElement( 'script' );
    script.src = url;

};

接下来,我们将获取我们在DOM中找到的第一个元素,并使用insertBefore()方法插入新的元素。

// 提交表单
var submitMailChimpForm = function (form) {

    // 获取表单 URL
    var url = form.getAttribute('action');
    url = url.replace('/post?u=', '/post-json?u=');
    url += serialize(form) + '&c=displayMailChimpStatus';

    // 使用url和回调创建脚本 (如果指定的话)
    var script = window.document.createElement( 'script' );
    script.src = url;

    // 插入script标签到 DOM (附加到<head>)
    var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
    ref.parentNode.insertBefore( script, ref );

};

最后, 在脚本加载成功后我们把它从DOM中移除。

// 提交表单
var submitMailChimpForm = function (form) {

    // 获取提交 URL
    var url = form.getAttribute('action');
    url = url.replace('/post?u=', '/post-json?u=');
    url += serialize(form) + '&c=displayMailChimpStatus';

    //使用url和回调创建脚本 (如果指定的话)
    var script = window.document.createElement( 'script' );
    script.src = url;

    // 插入script标签到 DOM (附加到<head>)
    var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
    ref.parentNode.insertBefore( script, ref );

    // 在脚本被加载 (以及被执行后),移除它
    script.onload = function () {
        this.remove();
    };

};

处理提交时的响应

现在, 我们的回调方法只是将任何MailChimp响应的日志记录到控制台中

// 显示表单状态
var displayMailChimpStatus = function (data) {
    console.log(data);
};

如果你查看返回的数据, 它是一个有两个键: resultmsg的JSON对象。 result 的值要么是 error 要么是 success,并且 msg 的值是result的一个简短的解释。

{
    msg: 'freddie@mailchimp.com is already subscribed to list Bananas Are Awesome. [Click here to update your profile.](http://mailchimp.us1.list-manage.com/subscribe/send-email?u=12345abcdef&id=123&e=abc==)'
    result: 'error'
}

//或者...

{
    msg: 'Almost finished... We need to confirm your email address. To complete the subscription process, please click the link in the email we just sent you.'
    result: 'success'
}

查看 Chris Ferdinandi的演示 Form Validation: MailChimp with Ajax Submit - Result(@cferdinandi)在 CodePen上。

我们应该检查以确保我们返回的数据具有这两个键。否则, 当我们去使用它们时,会抛出一个JavaScript错误。

// 显示表单状态
var displayMailChimpStatus = function (data) {

    // 确保数据格式正确
    if (!data.result || !data.msg ) return;

};

显示状态消息

我们会给表单添加一个 <div> , 就添加在提交按钮的前面,用于添加错误或者成功信息.我们给它一个mc-status类.

<form action="//us1.list-manage.com/subscribe/post?u=12345abcdef&id=abc123" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank">
    /* ... */
    <div class="mc-status"></div>
    <div><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</form>

displayMailChimpStatus()函数里, 我们要找到.mc-status容器,并添加msg

// 显示表单状态
var displayMailChimpStatus = function (data) {

    // 获取状态消息内容的区域
    var mcStatus = document.querySelector('.mc-status');
    if (!mcStatus) return;

    // 更新状态信息
    mcStatus.innerHTML = data.msg;

};

我们可以根据提交是否成功,对消息进行样式化.

我们已经使用.error-message为我们的错误消息设置了一些样式, 所以让我们重新使用它. 我们将会创建一个新类, .success-message, 用于成功提交时的样式.

.success-message {
    color: green;
    font-style: italic;
    margin-bottom: 1em;
}

现在,我们可以根据result有条件地添加一个类(并删除另一个)。

// 显示表单状态
var displayMailChimpStatus = function (data) {

    // 获取状态消息内容区域
    var mcStatus = document.querySelector('.mc-status');
    if (!mcStatus) return;

    // 更新我们的状态信息
    mcStatus.innerHTML = data.msg;

    // 如果出现错误, 添加错误类
    if (data.result === 'error') {
        mcStatus.classList.remove('success-message');
        mcStatus.classList.add('error-message');
        return;
    }

    // 否则,添加成功类
    mcStatus.classList.remove('error-message');
    mcStatus.classList.add('success-message');

};

查看Chris Ferdinandi的演示Form Validation: MailChimp with Ajax Submit(@cferdinandi)在 CodePen上.

一个重要的跨越性改进

虽然我们的信息很容易被用户发现, 但使用辅助技术(如屏幕阅读器)的人可能不会知道已将消息添加到DOM中.

我们将使用JavaScript来使我们的信息获得焦点。 为了做到这一点,我们还需要添加值为-1tabindex,因为div元素自身是不可以获取焦点的.

// 显示表单状态
var displayMailChimpStatus = function (data) {

    // 获取状态消息内容区域
    var mcStatus = document.querySelector('.mc-status');
    if (!mcStatus) return;

    //更新我们的状态信息
    mcStatus.innerHTML = data.msg;

    // 使我们的状态信息获得焦点
    mcStatus.addAttribute('tabindex', '-1');
    mcStatus.focus();

    // 如果有错误,添加错误类
    if (data.result === 'error') {
        mcStatus.classList.remove('success-message');
        mcStatus.classList.add('error-message');
        return;
    }

    // 否则, 添加成功类
    mcStatus.classList.remove('error-message');
    mcStatus.classList.add('success-message');

};

这将很有可能为我们的状态消息添加一个蓝色的轮廓。这有一个对于链接,按钮,以及其他的自身可获取焦点的区域来讲很重要的功能,但它对于我们的消息来说不是必须的。 我们可以用一点CSS删除它。

.mc-status:focus {
    outline: none;
}

最终结果

我们现在有一个轻量级的,无依赖关系的脚本来验证我们的MailChimp表单并异步提交。

我们完成的脚本在不压缩时大小为 19kb 。 当压缩后, 脚本只有 9kb大小。比MailChimp提供的轻15.5倍。

不错!

文章系列:

  • 在HTML中的约束验证
  • 用JavaScript编写约束验证API
  • 一个Validity State API Polyfill
  • 验证MailChimp订阅表单Nike Ambassador 11