构建表单验证
何时验证?
首先,我们要验证三个地方:
- 在用户提交时验证表单。
- 在用户编辑字段时验证字段。
- 在服务器收到数据时验证数据。
我们需要在服务器端验证数据的原因(不管其他两个验证有多好)是因为恶意用户可以访问 HTML 和 JavaScript 代码、伪造请求或通过以下方式绕过客户端验证其他方式。
如果验证将发生在服务器端,为什么我们需要客户端验证?简短的回答是客户端验证减少了错误提交的数量,从而减少了流量。第二个动机是客户端验证允许更快、更轻松地向用户提供反馈。
话虽如此,当前的帖子只会处理客户端验证。
现在,为了运行验证代码,我们需要处理适当的事件。在 JavaScript 中添加事件处理程序的推荐方法是在目标元素上调用 addEventListener。遗憾的是 browser support 在旧版本的 Internet Explorer 上并不好。
所以,我们将从You Might Not Need jQuery 提取代码以添加事件处理程序:
function addEventListener(el, eventName, handler) {
if (el.addEventListener) {
el.addEventListener(eventName, handler);
} else {
el.attachEvent('on' + eventName, function(){
handler.call(el);
});
}
}
注意:attachEvent 是 Microsoft 的专有扩展。
现在我们需要决定要处理的事件...
最初的诱惑是处理字段的每一个细微变化。这样做的缺点是,只需键入一个字符,系统可能会告诉用户它是错误的(因为值太短,或其他原因)。这可以解释为好像输入的单个字符是错误的,然后用户在继续之前停下来分析错误。
Luke Wroblewski, "Inline Validation in Web Forms", 2009 表明,使用“模糊”事件(失去焦点)进行验证会导致用户更快地填写表单。
以下是文章的摘录:
(...) 当简单的问题被标记为“正确”时,参与者会感到困惑,这一事实支持了这种解释:
“你是说我输入了一个有效的名字还是我的名字正确?”
“这是确认邮政编码格式正确还是我的邮政编码正确?”
这些类型的参与者问题在测试期间引起了一些小问题。我们的参与者知道我们无法知道他们的正确姓名或邮政编码,因此他们知道绿色复选标记并不意味着“正确”。但他们认为这意味着什么?他们不确定——这就是问题所在。由于不知道信息的含义,我们的参与者停下来向主持人提问,而不是自信地回答非常简单的问题。
(...)
当多个参与者在尝试回答问题时注意到错误消息时,他们在输入字段中输入了一个额外的字符,而不是等待消息更新。如果更新的消息继续显示错误,他们输入另一个字符,然后等待验证消息再次更新,依此类推,导致平均完成时间更长。
(...)
“令人沮丧的是,在 [the field] 向你闪烁之前,你没有机会在 [the field] 中投入任何东西。”
“当我单击名字字段时,它立即出现说 [我的名字] 太短。嗯,当然是!我还没开始呢!”
“当你没有完成打字时,我发现红十字会出现很烦人。这真的很让人分心。”
因此,最初的建议是使用blur 事件进行验证。
然而,这带来了另一个问题。如果仅在 blur 上进行验证,则当字段的状态设置为无效时,对其进行编辑将继续将其显示为无效 - 直到用户离开该字段。这可能会导致用户怀疑他们输入的内容是否仍然错误,而在他们离开该字段之前不会进行验证。
为防止出现该问题,我们将为每个字段设置以下状态:
- 空。这是该领域的原始状态。不要开始将所有内容都显示为无效。
- 正在验证。用户正在键入或编辑该字段。它既无效也不无效。
- 有效。用户在该字段中输入了有效输入。
- 无效。用户在字段中输入了无效输入。
这导致以下状态图:
使用yUML 创建的图表。
注意:出于实际目的,Empty 和 Validating 可以被认为是等效的。在这两种状态下,都不会向用户显示验证状态。此外,当用户重置表单时,应该可以返回到Empty 状态(如果给出了这样的选项)。
那么我们有以下内容:
注意:您可能会考虑的另一件事是使用timer 开始验证,以在input、change 和keyup 事件的某个间隔后运行一次。要正确执行此操作,每次运行其中一个事件时都必须重置此计时器。它是容易出错的代码,价值不大。
在哪里验证?
HTML5 已经为data form validation 添加了多种方式。然而,browser support 并不是最好的。这意味着即使我们选择扩展 HTML5 验证,它也可能无法工作,具体取决于浏览器。
因此,我们将跳过 HTML5 验证,然后继续添加我们的事件:
function setupValidation(form)
{
addEventHandler(form, 'submit', submitHandler);
var elementIndex = form.elements.length;
while (elementIndex--)
{
addEventHandler (form.elements[elementIndex], 'reset', resetHandler);
addEventHandler (form.elements[elementIndex], 'input', validatingHandler);
addEventHandler (form.elements[elementIndex], 'change', validatingHandler);
addEventHandler (form.elements[elementIndex], 'keyup', validatingHandler);
addEventHandler (form.elements[elementIndex], 'blur', validateHandler);
}
}
现在,鉴于我们只构建表单验证,而不是表单验证框架或库......我们可以只获取表单元素和字段来放置我们想要的任何验证。如果我们这样做,那么表单不需要是参数。
为了让我们的代码在页面加载时运行,我们可以从 You Might Not Need jQuery 获取另一个 sn-p:
function ready(fn) {
if (document.readyState != 'loading'){
fn();
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn);
} else {
document.attachEvent('onreadystatechange', function() {
if (document.readyState != 'loading')
fn();
});
}
}
现在,我们需要能够存储字段的状态。这可以通过元素对象的自定义属性、属性或类来完成。使用类来标记验证也有助于表单的呈现。
在验证表单 (submitHandler) 时,您必须决定是要依赖存储的字段验证还是再次验证。如果您对依赖于其他字段的字段进行验证,您可能希望将该验证标记为旧的,这样表单验证代码将知道再次运行验证。无论哪种方式,都将通过遍历每个字段来完成验证。
根据resetHandler 和validatingHandler 的想法是删除valid 和invalid 状态,出于前面解释的用户体验原因。
如果操作正确,就不应该出现向用户发出错误信号的情况。也就是说,代码不应该在字段有效时将其显示为无效;两者都不应在字段无效时将其显示为有效。
您可能想要禁用 HTML5 验证。这可以通过将novalidate 属性添加到表单来完成。也可以像这样通过 JavaScript 完成:
form.setAttribute('novalidate', 'novalidate');
您可能还想查看字段的属性willValidate。
如果你想使用 HTML5 验证,可以使用函数checkValidity。
延伸阅读:Client-Side Form Validation with HTML5 和 HTML5 Forms: JavaScript and the Constraint Validation API。
另外,文章Constraint Validation: Native Client Side Validation for Web Forms
作者 TJ VanToll 有很好的 HTML5 验证示例。
如果我们要构建一个完整的表单验证库,我们会遇到读取 HTML5 验证属性并模仿它们的行为以便为旧浏览器提供它的麻烦。我们还必须担心如何指定 HTML5 不提供的其他验证规则(例如检查两个字段是否匹配),而不必为每种情况调整 JavaScript 代码(因为,正如我所说,如果我们正在制作图书馆)。
在哪里放置反馈
可用性建议是内联反馈。即在字段旁边添加内联元素,并将反馈作为文本。然后你可以使用 CSS 或 JavaScript 让它看起来很花哨。
这个建议的原因是依赖屏幕阅读器的人会在正确的位置获得反馈。此外,即使 CSS 被禁用或无法加载,它也将继续有意义。
这几乎就是您使用span 元素所做的事情。每个字段都需要一个。也许整个表单的一个是您想要放置一些不与任何字段直接关联的消息。
注意:在读取某个字段的值时,我们通常使用field.value。该值的长度为field.value.length。然而应该注意的是,根据输入的类型,读取值的方式会发生变化。对于radio 和checkbox,请使用field.checked。对于select,您需要field.options(应特别注意可以有多个值的select 字段)。最后image、reset、'button' 和submit 没有要检查的值。
太多、太复杂?
您不必这样做!其他人以前做过,您可以从他们那里获取代码! 哇哈哈!
这将通过使用诸如validate.js 或我自己的thoth.js 之类的开源库来完成。其他答案建议其他库。
您应该可以find more atlernatives。我不会假装列出详尽的清单。
重用代码被认为是一种很好的做法。您也可以选择研究此类库的代码以了解它们的工作原理。
旧答案
我将首先在您的代码中添加 cmets:
function checkPass()
{
var pass1 = document.getElementById('pass1');
var pass2 = document.getElementById('pass2');
var message = document.getElementById('error-nwl');
var goodColor = "#66cc66";
var badColor = "#ff6666";
// You start by checking if they match
if(pass1.value == pass2.value){
pass2.style.backgroundColor = goodColor;
message.style.color = goodColor;
message.innerHTML = "ok!"
}else{
pass2.style.backgroundColor = badColor;
message.style.color = badColor;
message.innerHTML = " These passwords don't match"
}
// And then that messages gets removed by the result of the length check
// Also, pass1.length is undefined
if(pass1.length > 5){
pass1.style.backgroundColor = goodColor;
message.style.color = goodColor;
message.innerHTML = "character number ok!"
}else{
pass1.style.backgroundColor = badColor;
message.style.color = badColor;
message.innerHTML = " you have to enter at least 6 digit!"
}
}
<input name="password" type="password" placeholder="password" id="pass1"/>
<input name="repeatpassword" type="password" placeholder="confirm password" id="pass2" onkeyup="checkPass(); return false;" />
<div id="error-nwl"></div>
相反,您应该强调状态是有效的,除非另有验证:
function checkPass()
{
var pass1 = document.getElementById('pass1');
var pass2 = document.getElementById('pass2');
var message = document.getElementById('error-nwl');
var goodColor = "#66cc66";
var badColor = "#ff6666";
message.style.color = goodColor;
message.innerHTML = "ok!"
if(pass1.value == pass2.value){
pass2.style.backgroundColor = goodColor;
}else{
pass2.style.backgroundColor = badColor;
message.style.color = badColor;
message.innerHTML = " These passwords don't match"
}
if(pass1.value.length > 5){
pass1.style.backgroundColor = goodColor;
}else{
pass1.style.backgroundColor = badColor;
message.style.color = badColor;
message.innerHTML = " you have to enter at least 6 digit!"
}
}
<input name="password" type="password" placeholder="password" id="pass1"/>
<input name="repeatpassword" type="password" placeholder="confirm password" id="pass2" onkeyup="checkPass(); return false;" />
<div id="error-nwl"></div>
除此之外,请注意,如果您使两个字段匹配,然后编辑第一个字段,则消息不会消失。事实上,编辑第一个永远不会让消息消失,因为我们仍然只检查第二个。相反,您可以同时检查两者。
此外,使用 keyup 可能会令人讨厌和困惑,您可以考虑使用 onblur 来验证用户何时离开该字段(即该字段失去焦点时,也就是模糊)。
如果您想要更高级的东西,您可以使用 keyup 方法在用户键入时删除消息,甚至可以再次检查,但在每次击键时重置的计时器上...
或者您可以使用 HTML5 验证,为什么不呢?
我刚刚更新了我的 JavaScript 库 thoth 以支持最小长度验证。还发布了一个帮助库来简化表单验证 - 根据具体情况,它可能需要一些调整,特别是它不包含任何本地化机制。
使用 thoth,您可以按如下方式实现您的代码。注意:如果您想将它们添加到您的代码中,请下载这些库。
Thoth 将确保此验证在 IE8 或更高版本中有效,如果 javascript 不可用,它将降级为 HTML5 表单验证。请记住,客户端始终可以操作 Javascript 和 HTML 代码,因此请在服务器上重复您的验证。
.valid
{
color: #66cc66;
}
.invalid
{
color: #ff6666;
}
<!DOCTYPE html>
<head>
<title>Demo</title>
<script src="https://rawgit.com/theraot/thoth/master/thoth.js"></script>
<script src="https://rawgit.com/theraot/thoth/master/form_helper.js"></script>
</head>
<form data-on-valid="document.getElementById('ok').style.display='';" data-on-invalid="document.getElementById('ok').style.display='none';" data-lacking="you have to enter at least 6 digit!" data-lacking-class="invalid">
<input name="password" type="password" placeholder="password" id="pass1" minlength="6" required="required"/>
<input name="repeatpassword" type="password" placeholder="confirm password" id="pass2" minlength="6" data-validate="match(@#pass1)" required="required"/>
</form>
<div id="ok" class="valid" style="display:none">ok!</div>
data-属性这里有不少,我给你分解一下:
-
data-on-valid:表单验证正确时将运行的代码。
-
data-on-invalid:表单未通过验证时将运行的代码。
-
data-lacking:字符不足时使用的字符串格式。同样,data-remaining 和 data-excess 将分别在到达 maxlength 和文本超出 maxlength 之前有空间时工作。
-
data-lacking-class:用于缺少消息的 css 类,类似 data-remaining-class 和 data-excess-class 存在。
以上是由帮助库form_helper.cs添加的。从库 thoth 中仅使用以下内容:
-
data-validate:额外的验证。在这种情况下,它用于添加验证以验证两个字段是否匹配。
很抱歉缺少文档。
注意:data-on-valid 和 data-on-invalid 不是正确的事件。