对于这个由三部分组成的系列有关HTML5 Web表单的最后一篇文章,我们将讨论JavaScript集成和约束验证API。 如果您尚未这样做,请阅读The Markup和CSS文章,以确保您熟悉这些概念。

HTML5允许我们无需任何JavaScript编码即可实现客户端表单验证。 但是,在实现更复杂的表单时,我们需要增强本机验证,因为:

  • 并非所有浏览器都支持所有HTML5输入类型和CSS选择器
  • 错误消息气泡使用通用文本(“请填写此字段”)并且难以设置样式
  • :invalid:required样式将在用户与表单进行交互之前应用于页面加载。

大量的JavaScript魔术和Constraint Validation API可以改善用户体验。 请注意,如果您想支持我们将竭尽所能的各种浏览器和输入类型,可能会有些混乱。

截取表格

在HTML5之前,客户端验证涉及在表单上附加一个submit处理程序,该处理程序将验证字段,显示错误并防止Submit事件。

在HTML5中,浏览器将首先执行自己的验证-在表单有效之前,将不会触发submit事件。 因此,如果您想做一些复杂的事情,例如显示自己的错误,比较或自动填充字段,则必须通过将表单的noValidate属性设置为true来关闭本机验证:

var form = document.getElementById("myform");
form.noValidate = true;

// set handler to validate the form
// onsubmit used for easier cross-browser compatibility
form.onsubmit = validateForm;

当然,这意味着您必须检查代码中的字段错误,但是仍然可以利用本机浏览器验证,我们将在不久后看到。

字段.willValidate属性

每个输入字段都有一个.willValidate属性。 返回:

  • 当浏览器将本地验证字段时为true
  • 当浏览器将不验证该字段时为false ,或者
  • 当浏览器不支持本地HTML5验证(例如IE8)时, 未定义

由于我们在上面禁用了本机验证,因此每个字段都将返回false。 让我们创建一个validateForm处理程序,该处理程序遍历所有字段并检查本地验证是否可用:

function validateForm(event) {

	// fetch cross-browser event object and form node
	event = (event ? event : window.event);
	var
		form = (event.target ? event.target : event.srcElement),
		f, field, formvalid = true;

	// loop all fields
	for (f = 0; f < form.elements; f++) {

		// get field
		field = form.elements[f];

		// ignore buttons, fieldsets, etc.
		if (field.nodeName !== "INPUT" && field.nodeName !== "TEXTAREA" && field.nodeName !== "SELECT") continue;

循环遍历表单的elements集合中的所有字段,并检查它们是否是输入,而不是其他类型(例如按钮和字段集)。 下一行很重要…

// is native browser validation available?
		if (typeof field.willValidate !== "undefined") {

			// native validation available

		}
		else {

			// native validation not available

		}

falseundefined都是falsey值,因此您不能仅检查field.willValidate

现在我们知道第一个块中的代码将评估何时可以使用本机验证。 然而…

浏览器是否支持输入类型?

如果阅读了第一部分 ,您会记得不支持的输入类型会回到text 。 例如:

<input type="date" name="dob" />

Firefox 29或IE11本身不支持。 这些浏览器将(有效)呈现:

<input type="text" name="dob" />

但是两种浏览器都支持text类型的验证,因此field.willValidate不会返回undefined ! 因此,我们必须检查我们的type属性是否与对象的.type属性匹配-如果不匹配,则需要实施旧版后备验证,例如




// native validation available
			if (field.nodeName === "INPUT" && field.type !== field.getAttribute("type")) {

				// input type not supported! Use legacy JavaScript validation

			}

Field .checkValidity()方法

如果可以使用本机验证,则可以执行.checkValidity()方法来验证该字段。 如果没有问题,则该方法返回true,否则返回false

有一个类似的.reportValidity()方法,它无需重新检查即可返回当前状态,尽管这种方法用处不大,并非所有浏览器都支持。

两种方法还将:

  1. 设置字段的.validity对象,以便可以更详细地检查错误,并且
  2. 验证失败时,在字段上触发invalid事件。 这可用于显示错误,更改颜色等。请注意,没有相应的valid事件,因此请记住在必要时重置错误样式和消息。

字段.validity对象

.validity对象具有以下属性:

.valid –如果该字段没有错误,则返回true,否则返回false
.valueMissing –如果需要该字段但已输入值,则返回true
.typeMismatch –如果值的语法不正确(例如,格式错误的电子邮件地址),则返回true
.patternMismatch –如果该值与pattern属性的正则表达式不匹配,则返回true
.tooLong如果该值大于允许的maxlength则返回true
.tooShort -如果该值大于允许短返回true minlength
.rangeUnderFlow –如果该值小于min则返回true
.rangeOverflow –如果该值大于max则返回true
.stepMismatch –如果该值与step不匹配,则返回true
.badInput –如果条目无法转换为值,则返回true
.customError –如果该字段设置了自定义错误,则返回true

并非所有浏览器都支持所有属性,因此请注意不要做太多假设。 在大多数情况下, .valid.checkValidity()的结果应足以显示或隐藏错误消息。

在旧版浏览器中支持.validity

您可以在旧版浏览器中手动模拟.validity对象,例如

// native validation not available
			field.validity = field.validity || {};

			// set to result of validation function
			field.validity.valid = LegacyValidation(field);

这样可以确保可以在所有浏览器中测试.validity.valid

字段.setCustomValidity()方法

可以传递.setCustomValidity()方法:

  • 一个空字符串。 这.checkValidity()字段设置为有效,因此.checkValidity().validity.valid将返回true ,或者
  • 一个包含错误消息的字符串,它将显示在消息提示框中(如果使用)。 该消息还会将字段标记为失败,因此.checkValidity().validity.valid将返回false,并且将触发invalid事件。

请注意,您还可以使用字段的.validationMessage属性检查当前消息。

全部放在一起

现在,我们有了一个简单的通用跨浏览器表单验证系统的基础:

var form = document.getElementById("myform");
form.noValidate = true;

// set handler to validate the form
// onsubmit used for easier cross-browser compatibility
form.onsubmit = validateForm;

function validateForm(event) {

	// fetch cross-browser event object and form node
	event = (event ? event : window.event);
	var
		form = (event.target ? event.target : event.srcElement),
		f, field, formvalid = true;

	// loop all fields
	for (f = 0; f < form.elements; f++) {

		// get field
		field = form.elements[f];

		// ignore buttons, fieldsets, etc.
		if (field.nodeName !== "INPUT" && field.nodeName !== "TEXTAREA" && field.nodeName !== "SELECT") continue;

		// is native browser validation available?
		if (typeof field.willValidate !== "undefined") {

			// native validation available
			if (field.nodeName === "INPUT" && field.type !== field.getAttribute("type")) {

				// input type not supported! Use legacy JavaScript validation
				field.setCustomValidity(LegacyValidation(field) ? "" : "error");

			}

			// native browser check
			field.checkValidity();

		}
		else {

			// native validation not available
			field.validity = field.validity || {};

			// set to result of validation function
			field.validity.valid = LegacyValidation(field);

			// if "invalid" events are required, trigger it here

		}

		if (field.validity.valid) {

			// remove error styles and messages

		}
		else {

			// style field, show error, etc.

			// form is invalid
			formvalid = false;
		}

	}

	// cancel form submit if validation fails
	if (!formvalid) {
		if (event.preventDefault) event.preventDefault();
	}
	return formvalid;
}


// basic legacy validation checking
function LegacyValidation(field) {

	var
		valid = true,
		val = field.value,
		type = field.getAttribute("type"),
		chkbox = (type === "checkbox" || type === "radio"),
		required = field.getAttribute("required"),
		minlength = field.getAttribute("minlength"),
		maxlength = field.getAttribute("maxlength"),
		pattern = field.getAttribute("pattern");

	// disabled fields should not be validated
	if (field.disabled) return valid;

    // value required?
	valid = valid && (!required ||
		(chkbox && field.checked) ||
		(!chkbox && val !== "")
	);

	// minlength or maxlength set?
	valid = valid && (chkbox || (
		(!minlength || val.length >= minlength) &&
		(!maxlength || val.length <= maxlength)
	));

	// test pattern
	if (valid && pattern) {
		pattern = new RegExp(pattern);
		valid = pattern.test(val);
	}

	return valid;
}

LegacyValidation特意保留为简单; 它会检查requiredminlengthmaxlengthpattern正则表达式,但是您将需要其他代码来检查电子邮件,URL,日期,数字,范围等。

这就引出了一个问题: 如果您要为旧版浏览器编写现场验证代码,为什么还要麻烦使用本机浏览器API? 很好! 仅当您想支持IE6以上的所有浏览器并提供类似的用户体验时,才需要上面的代码。 并非总是必要的……

  • 对于简单的表单,您可能不需要任何JavaScript代码。 那些使用旧版浏览器的用户可能会退回到服务器端验证-应该始终予以实施。
  • 如果您需要更复杂的表单,但只需要支持最新的浏览器(IE10 +),则可以删除所有旧的验证代码。 如果您的表单使用的日期是Firefox和IE当前不支持的日期,则只需使用其他JavaScript。
  • 即使您确实需要代码来检查IE9及以下版本中的电子邮件,数字等字段,也请使其保持简单,并在停止支持这些浏览器后将其删除。 现在有点混乱,但是情况会有所改善。

但是请记住,始终使用正确的HTML5字段类型。 这些浏览器将提供本机输入控件,并在禁用JavaScript的情况下强制执行更快的客户端验证。