我们知道原生的html元素支持做一些简单的验证,但是不是很美观,我们可以自己来定义这些错误消息,仍然使用元素的那套验证。
validity
属性提供了关于表单字段的一组信息,值是布尔值(true
/false
)。
var myField = document.querySelector('input[type="text"]');
var validityState = myField.validity;
返回的对象(validityState
)包含以下属性:
-
valid
: 当字段通过验证的时候,是true
。 -
valueMissing
: 当必填的字段是空的时候,这个是true
。 -
typeMismatch
: 当字段type
限制是email
或url
的时候,但输入的value
(值)不是正确的类型,这个时候返回的值是true
。 -
tooShort
: 这个就是当元素有minLength
属性的时候,但是输入的value
没有达到这个限制(比这个小),这时候是true
。 -
tooLong
: 这个和tooShort
差不多,不过这个是和maxLength
进行对比。 -
patternMismatch
: 这个与typeMismatch
差不多,不过是与pattern
类型相比较。 -
badInput
: 类型是number
,但是输入的value
不是number
,就会返回true
。 -
stepMismatch
: 当字段具有step
属性且输入的value
不符合步骤值时,返回true
。 -
rangeOverflow
: 当字段具有max
属性且输入的数字value
大于max时,就返回true
。 -
rangeUnderflow
: 当字段具有min
属性且输入的数字value
低于min时,就会返回true
。
下面就用这些验证和js配合,来构建一个健壮的表单验证。
禁用原生验证
首先使用novalidate
属性来禁止原生的form验证提示。
作为最佳实践,我们应该使用js脚本来添加,这样当js无法加载时,保证原生的验证仍然可用。
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].setAttribute('novalidate', true);
}
可能有些表单,你不想验证,比如搜索表单。你只想验证一些需要验证的表单,那么我们来加一个类名,叫做. validate
。
// js加载时添加novalidate属性
var forms = document.querySelectorAll('.validate');
for (var i = 0; i < forms.length; i++) {
forms[i].setAttribute('novalidate', true);
}
可以在线查看效果。
用户离开时检查有效性(失去焦点)
我们将使用事件冒泡(或事件传播)来监听所有blur
事件,而不是为每个表单字段添加一个监听器。
// Listen to all blur events
document.addEventListener('blur', function (event) {
// Do something on blur...
}, true);
对于上面代码第三个参数(
true
)不了解的同学可以看看这里。
第三个参数叫做useCapture
,他通常默认是设置成false
。blur
事件不会像click
事件那样冒泡。将此参数设置为true
允许我们捕获所有blur
事件,而不仅仅是那些直接发生在我们正在侦听的元素上的事件。
接下来,我们要确保blur元素是具有.validate
类的表单中的字段。我们可以使用event.target
获取blur的元素,并通过调用event.target.form
获取它的父级form。然后使用classList
来检查是否含有验证这个类。
// Listen to all blur events
document.addEventListener('blur', function (event) {
// Only run if the field is in a form to be validated
if (!event.target.form.classList.contains('validate')) return;
// Validate the field
var error = event.target.validity;
console.log(error);
}, true);
如果error
为true
,则该字段有效。否则,出现错误。
可以在线查看。
获取错误
由于我们需要检查每个属性,因此代码可能会有点长。让我们为此设置一个单独的函数,并将我们的字段传递给它。
// Validate the field
var hasError = function (field) {
// Get the error
};
// Listen to all blur events
document.addEventListner('blur', function (event) {
// Only run if the field is in a form to be validated
if (!event.target.form.classList.contains('validate')) return;
// Validate the field
var error = hasError(event.target);
}, true);
我们要忽略一些字段类型:禁用的字段,file
和reset
输入以及submit
input和按钮。如果一个字段不是其中之一,那就让它有效。
// Validate the field
var hasError = function (field) {
// Don't validate submits, buttons, file and reset inputs, and disabled fields
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
// Get validity
var validity = field.validity;
};
如果没有错误,我们将返回null
。否则,我们将检查每个有效状态属性,直到找到错误。
// Validate the field
var hasError = function (field) {
// Don't validate submits, buttons, file and reset inputs, and disabled fields
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
// Get validity
var validity = field.validity;
// If valid, return null
if (validity.valid) return;
// If field is required and empty
if (validity.valueMissing) return 'Please fill out this field.';
// If not the right type
if (validity.typeMismatch) return 'Please use the correct input type.';
// If too short
if (validity.tooShort) return 'Please lengthen this text.';
// If too long
if (validity.tooLong) return 'Please shorten this text.';
// If number input isn't a number
if (validity.badInput) return 'Please enter a number.';
// If a number value doesn't match the step interval
if (validity.stepMismatch) return 'Please select a valid value.';
// If a number field is over the max
if (validity.rangeOverflow) return 'Please select a smaller value.';
// If a number field is below the min
if (validity.rangeUnderflow) return 'Please select a larger value.';
// If pattern doesn't match
if (validity.patternMismatch) return 'Please match the requested format.';
// If all else fails, return a generic catchall error
return 'The value you entered for this field is invalid.';
};
这是一个好的开始,但我们可以做一些额外的解析,以使我们的一些错误更有用。对于typeMismatch
,我们可以检查它是否应该是email
或url
,并相应地自定义错误。
// If not the right type
if (validity.typeMismatch) {
// Email
if (field.type === 'email') return 'Please enter an email address.';
// URL
if (field.type === 'url') return 'Please enter a URL.';
}
如果字段值太长或太短,我们可以找出它应该有多长或多短以及实际有多长或多短。然后我们可以在错误中包含该信息。
// If too short
if (validity.tooShort) return 'Please lengthen this text to ' + field.getAttribute('minLength') + ' characters or more. You are currently using ' + field.value.length + ' characters.';
// If too long
if (validity.tooLong) return 'Please short this text to no more than ' + field.getAttribute('maxLength') + ' characters. You are currently using ' + field.value.length + ' characters.';
如果数字字段超出或低于允许范围,我们可以在错误中包含最小或最大允许值。
// If a number field is over the max
if (validity.rangeOverflow) return 'Please select a value that is no more than ' + field.getAttribute('max') + '.';
// If a number field is below the min
if (validity.rangeUnderflow) return 'Please select a value that is no less than ' + field.getAttribute('min') + '.';
如果pattern
模式不匹配且字段具有title
,我们可以将其用作我们的错误显示,就像浏览器原生行为一样。
// If pattern doesn't match
if (validity.patternMismatch) {
// If pattern info is included, return custom error
if (field.hasAttribute('title')) return field.getAttribute('title');
// Otherwise, generic error
return 'Please match the requested format.';
}
这是我们的hasError()
函数的完整代码:
// Validate the field
var hasError = function (field) {
// Don't validate submits, buttons, file and reset inputs, and disabled fields
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
// Get validity
var validity = field.validity;
// If valid, return null
if (validity.valid) return;
// If field is required and empty
if (validity.valueMissing) return 'Please fill out this field.';
// If not the right type
if (validity.typeMismatch) {
// Email
if (field.type === 'email') return 'Please enter an email address.';
// URL
if (field.type === 'url') return 'Please enter a URL.';
}
// If too short
if (validity.tooShort) return 'Please lengthen this text to ' + field.getAttribute('minLength') + ' characters or more. You are currently using ' + field.value.length + ' characters.';
// If too long
if (validity.tooLong) return 'Please shorten this text to no more than ' + field.getAttribute('maxLength') + ' characters. You are currently using ' + field.value.length + ' characters.';
// If number input isn't a number
if (validity.badInput) return 'Please enter a number.';
// If a number value doesn't match the step interval
if (validity.stepMismatch) return 'Please select a valid value.';
// If a number field is over the max
if (validity.rangeOverflow) return 'Please select a value that is no more than ' + field.getAttribute('max') + '.';
// If a number field is below the min
if (validity.rangeUnderflow) return 'Please select a value that is no less than ' + field.getAttribute('min') + '.';
// If pattern doesn't match
if (validity.patternMismatch) {
// If pattern info is included, return custom error
if (field.hasAttribute('title')) return field.getAttribute('title');
// Otherwise, generic error
return 'Please match the requested format.';
}
// If all else fails, return a generic catchall error
return 'The value you entered for this field is invalid.';
};
在线的codepen中自己尝试一下。
显示错误消息
我们将创建一个showError()
函数来处理它,并传入我们的字段和错误。然后,我们将在事件监听器中调用它。
// Show the error message
var showError = function (field, error) {
// Show the error message...
};
// Listen to all blur events
document.addEventListener('blur', function (event) {
// Only run if the field is in a form to be validated
if (!event.target.form.classList.contains('validate')) return;
// Validate the field
var error = hasError(event.target);
// If there's an error, show it
if (error) {
showError(event.target, error);
}
}, true);
showError
中将会做几件事:
- 我们将在字段中添加一个带有错误的类,以便我们可以设置它的样式。
- 如果已存在错误消息,我们将使用新文本对其进行更新。
- 否则,我们将创建一条消息并在字段后立即将其注入DOM。
我们还将使用字段ID为消息创建唯一ID,以便稍后再次找到它(如果没有ID,则使用name
)。
var showError = function (field, error) {
// Add error class to field
field.classList.add('error');
// Get field id or name
var id = field.id || field.name;
if (!id) return;
// Check if error message field already exists
// If not, create one
var message = field.form.querySelector('.error-message#error-for-' + id );
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
field.parentNode.insertBefore( message, field.nextSibling );
}
// Update error message
message.innerHTML = error;
// Show error message
message.style.display = 'block';
message.style.visibility = 'visible';
};
为了确保屏幕阅读器和其他辅助技术知道我们的错误消息与我们的字段相关联,我们还需要添加aria-describedby
角色。
var showError = function (field, error) {
// Add error class to field
field.classList.add('error');
// Get field id or name
var id = field.id || field.name;
if (!id) return;
// Check if error message field already exists
// If not, create one
var message = field.form.querySelector('.error-message#error-for-' + id );
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
field.parentNode.insertBefore( message, field.nextSibling );
}
// Add ARIA role to the field
field.setAttribute('aria-describedby', 'error-for-' + id);
// Update error message
message.innerHTML = error;
// Show error message
message.style.display = 'block';
message.style.visibility = 'visible';
};
设置错误消息的样式
我们可以使用.error
和.error-message
类来设置表单字段和错误消息的样式。
举个简单的例子,你可能希望在带有错误的字段周围显示红色边框,并将错误消息设置为红色和斜体。
.error {
border-color: red;
}
.error-message {
color: red;
font-style: italic;
}
在线效果。
隐藏错误消息
一旦我们显示错误,字段验证后,我们需要删除错误消息。让我们创建另一个函数removeError()
,并传入该字段。我们也将从事件监听器调用此函数。
// Remove the error message
var removeError = function (field) {
// Remove the error message...
};
// Listen to all blur events
document.addEventListener('blur', function (event) {
// Only run if the field is in a form to be validated
if (!event.target.form.classList.contains('validate')) return;
// Validate the field
var error = event.target.validity;
// If there's an error, show it
if (error) {
showError(event.target, error);
return;
}
// Otherwise, remove any existing error message
removeError(event.target);
}, true);
在removeError()
中,我们想要:
- 从我们的字段中删除错误类。
- 从字段中删除aria-describedby角色。
- 隐藏DOM中的任何可见错误消息。
因为我们可以在页面上有多个表单,并且这些表单可能具有相同名称或ID的字段(即使这些字段无效,也会发生),我们将使用querySelector
限制我们对错误消息的搜索,而不是整个文档。
// Remove the error message
var removeError = function (field) {
// Remove error class to field
field.classList.remove('error');
// Remove ARIA role from the field
field.removeAttribute('aria-describedby');
// Get field id or name
var id = field.id || field.name;
if (!id) return;
// Check if an error message is in the DOM
var message = field.form.querySelector('.error-message#error-for-' + id + '');
if (!message) return;
// If so, hide it
message.innerHTML = '';
message.style.display = 'none';
message.style.visibility = 'hidden';
};
来看看效果。
如果该字段是单选按钮或复选框,我们需要更改我们将错误消息添加到DOM的方式。
对于这些类型的输入,字段标签通常位于字段之后,或者完全包装它。此外,如果单选按钮是组的一部分,我们希望错误出现在组之后,而不仅仅是单选按钮。
看看这里。
首先,我们需要修改showError()
方法。如果字段type
是radio
并且它具有name
,我们希望获得具有相同name
的所有单选按钮(即组中的所有其他单选按钮)并将我们的field
变量重置为组中的最后一个。
// Show the error message
var showError = function (field, error) {
// Add error class to field
field.classList.add('error');
// If the field is a radio button and part of a group, error all and get the last item in the group
if (field.type === 'radio' && field.name) {
var group = document.getElementsByName(field.name);
if (group.length > 0) {
for (var i = 0; i < group.length; i++) {
// Only check fields in current form
if (group[i].form !== field.form) continue;
group[i].classList.add('error');
}
field = group[group.length - 1];
}
}
...
};
当我们将消息注入DOM时,我们首先要检查字段类型是否为radio
或checkbox
。如果是这样,我们希望得到字段标签并在它之后而不是在字段本身之后注入我们的消息。
// Show the error message
var showError = function (field, error) {
...
// Check if error message field already exists
// If not, create one
var message = field.form.querySelector('.error-message#error-for-' + id );
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
// If the field is a radio button or checkbox, insert error after the label
var label;
if (field.type === 'radio' || field.type ==='checkbox') {
label = field.form.querySelector('label[for="' + id + '"]') || field.parentNode;
if (label) {
label.parentNode.insertBefore( message, label.nextSibling );
}
}
// Otherwise, insert it after the field
if (!label) {
field.parentNode.insertBefore( message, field.nextSibling );
}
}
...
};
当我们去除错误时,我们同样需要检查该字段是否是组中一部分的单选按钮,如果是,请使用该组中的最后一个单选按钮来获取错误消息的ID。
// Remove the error message
var removeError = function (field) {
// Remove error class to field
field.classList.remove('error');
// If the field is a radio button and part of a group, remove error from all and get the last item in the group
if (field.type === 'radio' && field.name) {
var group = document.getElementsByName(field.name);
if (group.length > 0) {
for (var i = 0; i < group.length; i++) {
// Only check fields in current form
if (group[i].form !== field.form) continue;
group[i].classList.remove('error');
}
field = group[group.length - 1];
}
}
...
};
那再来看看在线的例子啊
检查提交中的所有字段
为submit
事件添加一个监听器。
// Check all fields on submit
document.addEventListener('submit', function (event) {
// Validate all fields...
}, false);
如果表单具有.validate类,我们将获取每个字段,遍历每个字段,并检查错误。
// Check all fields on submit
document.addEventListener('submit', function (event) {
// Only run on forms flagged for validation
if (!event.target.classList.contains('validate')) return;
// Get all of the form elements
var fields = event.target.elements;
// Validate each field
// Store the first field with an error to a variable so we can bring it into focus later
var error, hasErrors;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
showError(fields[i], error);
if (!hasErrors) {
hasErrors = fields[i];
}
}
}
// If there are errrors, don't submit form and focus on first element with error
if (hasErrors) {
event.preventDefault();
hasErrors.focus();
}
// Otherwise, let the form submit normally
// You could also bolt in an Ajax form submit process here
}, false);
来啊,再来看看在线的现成版本