在本系列文章的过去几篇文章中,我们学习了如何使用少量输入类型和验证属性来本地验证表单。
我们学习了如何使用约束验证 API 来增强原生浏览器验证过程,从而获得更好的整体用户体验。我们还编写了一个 polyfill,将支持扩展到 IE9(并填补一些更新版本中的功能漏洞)。
现在,让我们利用我们所学到的知识,将其应用到一个真实的例子中:MailChimp 注册表单。
文章系列
- HTML 中的约束验证
- JavaScript 中的约束验证 API
- 有效性状态 API polyfill
- 验证 MailChimp 订阅表单(您现在的位置!)
一个占用空间很大的简单表单
当您在您的网站上嵌入 MailChimp 注册表单时,它会附带一个名为 `mc-validate.js` 的 JavaScript 验证脚本。
此文件的大小为 140kb(已压缩),并包含整个 jQuery 库、两个第三方插件以及一些自定义的 MailChimp 代码。我们可以做得更好!
去除臃肿
首先,让我们获取一个没有臃肿的 MailChimp 表单。
在 MailChimp 中,您获取可嵌入表单代码的地方,点击标签为“裸露”的选项卡。此版本不包含任何 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` 包装器中删除它。
- 我们还可以删除通知访问者“* 表示必填项”的文本。
- 让我们从表单字段周围的 `.mc-field-group` 类中删除它,并从字段本身的空 `class` 属性中删除它。
- 我们还应该从电子邮件字段中删除 `.required` 和 `.email` 类,因为它们仅用作 MailChimp 验证脚本的挂钩。
- 我继续从电子邮件标签中删除了 `*`。不过,如何标记必填字段完全取决于您。
- 我们可以删除 `div#mce-responses` 容器,它仅由 MailChimp JavaScript 文件使用。
- 我们还可以从提交按钮周围的 `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>
添加约束验证
现在,让我们添加一些输入类型和验证属性,以便浏览器可以为我们本地验证表单。
电子邮件字段的 `type` 已经设置为 `email`,这很好。我们也应该添加 `required` 属性和一个 `pattern` 来强制电子邮件包含 TLD(地址的 `.com` 部分)。我们还应该包含一个 `title`,让用户知道他们必须有一个 TLD。
使用约束验证 API 增强
这是一个很好的起点,但我们可以通过添加我们在此系列文章的前面编写过的表单验证脚本来增强用户体验。
我们的验证脚本在压缩之前只有 6.7kb,比 MailChimp 提供的脚本小 20 倍。但是,如果我们希望确保对 IE9 的支持,那么我们应该包含我们的有效性状态 polyfill 和 Eli Grey 的 classList.js polyfill。
这使我们的总文件大小在未压缩的情况下达到 15.5kb,仍然比 MailChimp 验证脚本小 9 倍。
使用 Ajax 提交表单
MailChimp 提供的 `mc-validate.js` 脚本不仅仅验证表单。它还使用 Ajax 提交表单并显示状态消息。
当您点击修改后的表单上的提交按钮时,它会将访问者重定向到 MailChimp 网站。这是一种完全有效的做事方式。
但是,我们也可以在没有 jQuery 的情况下重新创建 MailChimp 的 Ajax 表单提交,以获得更好的用户体验。
我们想要做的第一件事是阻止表单通过页面重新加载来提交,就像它通常那样。在我们的 `submit` 事件监听器中,如果存在错误,我们将调用 `event.preventDefault`。相反,让我们无论如何都调用它。
// Check all fields on submit
document.addEventListener('submit', function (event) {
// Only run on forms flagged for validation
if (!event.target.classList.contains('validate')) return;
// Prevent form from submitting
event.preventDefault();
...
}, false);
使用 JSONP
MailChimp 提供的 `mc-validate.js` 脚本使用 JSONP 来绕过跨域安全错误。
JSONP 通过将返回的数据加载为 `document` 中的脚本元素来工作,然后将该数据传递给一个回调函数,该函数执行所有繁重的工作。
设置我们的提交 URL
首先,让我们设置一个函数,当我们的表单准备好提交时可以运行它,并在我们的 `submit` 事件监听器中调用它。
// Submit the form
var submitMailChimpForm = function (form) {
// Code goes here...
};
// Check all fields on submit
document.addEventListener('submit', function (event) {
...
// Otherwise, let the form submit normally
// You could also bolt in an Ajax form submit process here
submitMailChimpForm(event.target);
}, false);
我们需要做的第一件事是从表单的 `action` 属性中获取 URL。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
};
在 `mc-validate.js` 脚本中,URL 中的 `/post?u='` 被替换为 `/post-json?u=`。我们可以使用 `replace()` 方法轻松地做到这一点。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
};
序列化我们的表单数据
接下来,我们要获取所有表单字段数据,并从中创建一个键值对查询字符串。例如,`FNAME=Freddie%20Chimp&[email protected]`。
让我们创建一个另一个函数来处理这个问题。
// Serialize the form data into a query string
var serialize = function (form) {
// Code goes here...
};
现在,我们要遍历所有表单字段,并创建键值对。我将在此基础上继续进行 Simon Steinberger 为此完成的工作。
首先,我们将创建一个 `serialized` 变量,将其设置为一个空字符串。
// Serialize the form data into a query string
// Forked and modified from https://stackoverflow.com/a/30153391/1293256
var serialize = function (form) {
// Setup our serialized data
var serialized = '';
};
现在让我们使用 `form.elements` 获取表单中的所有字段,并循环遍历它们。
如果该字段没有名称、是提交或按钮、被禁用或是一个文件或重置输入,我们将跳过它。
如果它不是一个 `checkbox` 或 `radio`(一个针对 `select`、`textarea` 和各种 `input` 类型的很好的兜底)或者它确实是一个 `checkbox` 或 `radio` 并且它被选中,我们将将其转换为一个键值对,在开头添加一个 `&`,并将它附加到我们的 `serialized` 字符串中。我们还将确保对键和值进行编码,以便在 URL 中使用。
最后,我们将返回序列化字符串。
// Serialize the form data into a query string
// Forked and modified from https://stackoverflow.com/a/30153391/1293256
var serialize = function (form) {
// Setup our serialized data
var serialized = '';
// Loop through each field in the form
for (i = 0; i < form.elements.length; i++) {
var field = form.elements[i];
// Don't serialize fields without a name, submits, buttons, file and reset inputs, and disabled fields
if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue;
// Convert field data to a query string
if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
serialized += '&' + encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value);
}
}
return serialized;
};
现在我们有了序列化表单数据,我们可以将其添加到我们的 URL 中。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
url += serialize(form);
};
添加回调函数
JSONP 工作原理的关键部分是回调函数。
传统的 Ajax 请求会将数据返回给你。而 JSONP 则是将数据传递给一个回调函数。这个函数 *必须* 是全局的(即,附加到 window
而不是在另一个函数内部)。
让我们创建一个回调函数,并在控制台中记录返回的数据,以便我们能够看到 MailChimp 返回了什么。
// Display the form status
var displayMailChimpStatus = function (data) {
console.log(data);
};
现在我们可以将此回调函数添加到我们的 URL 中。大多数 JSONP 使用 callback
作为此查询字符串的键,但 MailChimp 使用 c
。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
url += serialize(form) + '&c=displayMailChimpStatus';
};
将我们的脚本注入 DOM
现在我们准备将我们的脚本注入 DOM。首先,我们将创建一个新的脚本元素,并将我们的 URL 作为它的 src
属性。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
url += serialize(form) + '&c=displayMailChimpStatus';
// Create script with url and callback (if specified)
var script = window.document.createElement( 'script' );
script.src = url;
};
接下来,我们将获取 DOM 中找到的第一个 <script>
元素,并将我们的新脚本元素注入到它之前,使用 insertBefore()
方法。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
url += serialize(form) + '&c=displayMailChimpStatus';
// Create script with url and callback (if specified)
var script = window.document.createElement( 'script' );
script.src = url;
// Insert script tag into the DOM (append to <head>)
var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
ref.parentNode.insertBefore( script, ref );
};
最后,当我们的脚本成功加载后,我们将从 DOM 中删除它。
// Submit the form
var submitMailChimpForm = function (form) {
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
url += serialize(form) + '&c=displayMailChimpStatus';
// Create script with url and callback (if specified)
var script = window.document.createElement( 'script' );
script.src = url;
// Insert script tag into the DOM (append to <head>)
var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
ref.parentNode.insertBefore( script, ref );
// After the script is loaded (and executed), remove it
script.onload = function () {
this.remove();
};
};
处理提交响应
现在,我们的回调方法只是将 MailChimp 返回的任何内容记录到控制台中。
// Display the form status
var displayMailChimpStatus = function (data) {
console.log(data);
};
如果你查看返回的数据,它是一个包含两个键的 JSON 对象:result
和 msg
。result
的值要么是 error
要么是 success
,而 msg
的值是一个简短的字符串,解释了 result
。
{
msg: '[email protected] is already subscribed to list Bananas Are Awesome. Click here to update your profile.'
result: 'error'
}
// Or...
{
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'
}
我们应该检查以确保返回的数据包含这两个键。否则,当我们尝试使用它们时,就会抛出 JavaScript 错误。
// Display the form status
var displayMailChimpStatus = function (data) {
// Make sure the data is in the right format
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
添加到它。
// Display the form status
var displayMailChimpStatus = function (data) {
// Get the status message content area
var mcStatus = document.querySelector('.mc-status');
if (!mcStatus) return;
// Update our status message
mcStatus.innerHTML = data.msg;
};
我们可以根据提交是否成功,以不同的方式对消息进行样式设置。
我们已经为错误消息设置了一些样式,使用 .error-message
类,所以让我们重用它们。我们将创建一个新的类 .success-message
,用于成功提交。
.success-message {
color: green;
font-style: italic;
margin-bottom: 1em;
}
现在,我们可以根据 result
条件地添加其中一个类(并删除另一个)。
// Display the form status
var displayMailChimpStatus = function (data) {
// Get the status message content area
var mcStatus = document.querySelector('.mc-status');
if (!mcStatus) return;
// Update our status message
mcStatus.innerHTML = data.msg;
// If error, add error class
if (data.result === 'error') {
mcStatus.classList.remove('success-message');
mcStatus.classList.add('error-message');
return;
}
// Otherwise, add success class
mcStatus.classList.remove('error-message');
mcStatus.classList.add('success-message');
};
一项重要的可访问性改进
虽然我们的消息会很容易被视力正常的用户发现,但使用辅助技术(如屏幕阅读器)的人可能不会天生知道 DOM 中添加了消息。
我们将使用 JavaScript 将我们的消息带入焦点。为此,我们还需要添加 tabindex
属性,值为 -1
,因为 <div>
元素本身不可聚焦。
// Display the form status
var displayMailChimpStatus = function (data) {
// Get the status message content area
var mcStatus = document.querySelector('.mc-status');
if (!mcStatus) return;
// Update our status message
mcStatus.innerHTML = data.msg;
// Bring our status message into focus
mcStatus.setAttribute('tabindex', '-1');
mcStatus.focus();
// If error, add error class
if (data.result === 'error') {
mcStatus.classList.remove('success-message');
mcStatus.classList.add('error-message');
return;
}
// Otherwise, add success class
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
- 有效性状态 API polyfill
- 验证 MailChimp 订阅表单(您现在的位置!)
你的正则表达式有问题,它通过了超过电子邮件地址格式规范允许长度的电子邮件地址(本地部分 64 个字符,总共 255 个字符,包括域名)。
当然,这个正则表达式很好维护……或者说不好维护。这就是为什么不推荐使用像这样的正则表达式的原因。正如 Jamie Zawinski 所说
“有些人,当面对问题时,会想
“我知道了,我会使用正则表达式。” 现在他们有两个问题了。”
这对很多使用 Mailchimp 表单并希望减少额外内容的人来说很有用。感谢你的撰写!
感谢这篇优秀的博客文章。非常有用,因为我正准备开始研究 Mailchimp 表单的优化。
关于 HTML 代码有一个问题。
隐藏输入字段的名称是否应该与从帖子 URL 中获取的 ‘u’ 和 ‘id’ 参数的组合相同?