表单脚步
JavaScript 较早的一个用途是承担一部分服务器端表单处理的责任。虽然 Web 和 JavaScript 都已经发展了很多年,但 Web 表单的变化不是很大。由于不能直接使用表单解决问题,因此开发者不得不使用 JavaScript 既做表单验证,又用于增强标准表单控件的默认行为。
表单基础
Web 表单在 HTML 中以 <form>
元素表示,在 JavaScript 中则以 HTMLFormElement 类型表示。 21 HTMLFormElement 类型继承自 HTMLElement 类型,因此拥有与其他 HTML 元素一样的默认属性。不过,HTMLFormElement 也有自己的属性和方法。
属性 | 描述 |
---|---|
acceptCharset | 服务器可以接收的字符集,等价于 HTML 的 accept-charset 属性。 |
action | 请求的 URL,等价于 HTML 的 action 属性。 |
elements | 表单中所有控件的 HTMLCollection 。 |
enctype | 请求的编码类型,等价于 HTML 的 enctype 属性。 |
length | 表单中控件的数量。 |
method | HTTP 请求的方法类型,通常是 "get" 或 "post",等价于 HTML 的 method 属性。 |
name | 表单的名字,等价于 HTML 的 name 属性。 |
reset() | 把表单字段重置为各自的默认值。 |
submit() | 提交表单。 |
target | 用于发送请求和接收响应的窗口的名字,等价于 HTML 的 target 属性。 |
提交表单
表单是通过用户点击提交按钮或图片按钮的方式提交的。提交按钮可以使用 type 属性为"submit" 的 <input>
或 <button>
元素来定义,图片按钮可以使用 type 属性为"image"的 <input>
元素来定义。
<!-- 通用提交按钮 -->
<input type="submit" value="Submit Form" />
<!-- 自定义提交按钮 -->
<button type="submit">Submit Form</button>
<!-- 图片按钮 -->
<input type="image" src="graphic.gif" />
以这种方式提交表单会在向服务器发送请求之前触发 submit 事件。这样就提供了一个验证表单数据的机会,可以根据验证结果决定是否真的要提交。阻止这个事件的默认行为可以取消提交表单。
let form = document.getElementById('myForm');
form.addEventListener('submit', event => {
// 阻止表单提交
event.preventDefault();
});
表单提交的一个最大的问题是可能会提交两次表单。如果提交表单之后没有什么反应,那么没有耐心的用户可能会多次点击提交按钮。结果是很烦人的(因为服务器要处理重复的请求),甚至可能造成损失(如果用户正在购物,则可能会多次下单)。解决这个问题主要有两种方式:在表单提交后禁用提交 按钮,或者通过 onsubmit 事件处理程序取消之后的表单提交。
重置表单
用户单击重置按钮可以重置表单。重置按钮可以使用 type 属性为"reset"的 <input>
或 <button>
元素来创建。
<!-- 通用重置按钮 -->
<input type="reset" value="Reset Form" />
<!-- 自定义重置按钮 -->
<button type="reset">Reset Form</button>
表单字段
表单元素可以像页面中的其他元素一样使用原生 DOM 方法来访问。此外,所有表单元素都是表单 elements 属性(元素集合)中包含的一个值。 这个 elements 集合是一个有序列表,包含对表单中所有字段的引用,包括所有 <input>
、<textarea>
、<button>
、<select>
和 <fieldset>
元素。elements 集合中的每个字段都以它们在 HTML 标记中出现的次序保存,可以通过索引位置和 name 属性来访问。 以下是几个例子:
let form = document.getElementById('form1');
// 取得表单中的第一个字段
let field1 = form.elements[0];
// 取得表单中名为"textbox1"的字段
let field2 = form.elements['textbox1'];
// 取得字段的数量
let fieldCount = form.elements.length;
如果多个表单控件使用了同一个 name,比如像单选按钮那样,则会返回包含所有同名元素的 HTMLCollection。比如,来看下面的 HTML 代码片段:
<form method="post" id="myForm">
<ul>
<li><input type="radio" name="color" value="red" />Red</li>
<li><input type="radio" name="color" value="green" />Green</li>
<li><input type="radio" name="color" value="blue" />Blue</li>
</ul>
</form>
let form = document.getElementById('myForm');
let colorFields = form.elements['color'];
console.log(colorFields.length); // 3
let firstColorField = colorFields[0];
let firstFormField = form.elements[0];
console.log(firstColorField === firstFormField);
// true
表单字段的公共属性
除 <fieldset>
元素以外,所有表单字段都有一组同样的属性。由于 <input>
类型可以表示多种表单字段,因此某些属性只适用于特定类型的字段。除此之外的属性可以在任何表单字段上使用。
属性 | 描述 |
---|---|
disabled | 布尔值 ,表示表单字段是否禁用。 |
form | 指针,指向表单字段所属的表单。这个属性是只读的。 |
name | 字符串,这个字段的名字。 |
readOnly | 布尔值,表示这个字段是否只读。 |
tabIndex | 数值,表示这个字段在按 Tab 键时的切换顺序。 |
type | 字符串,表示字段类型,如 "checkbox"、"radio" 等。 |
value | 要提交给服务器的字段值。对于文件输入字段来说,这个属性是只读的,仅包含计算机上某个文件的路径。 |
这里面除了 form 属性以外,JavaScript 可以动态修改任何属性。
let form = document.getElementById('myForm');
let field = form.elements[0];
field.value = 'Another value';
// 检查字段所属的表单
console.log(field.form === form); // true
// 给字段设置焦点
field.focus();
// 禁用字段
field.disabled = true;
// 改变字段的类型(不推荐,但对<input>来说是可能的)
field.type = 'checkbox';
表单字段的公共方法
每个表单字段都有两个公共方法: focus()
和 blur()
。focus()
方法把浏览器焦点设置到表单字段,这意味着该字段会变成活动字段并可以响应键盘事件。 blur()
用于从元素上移除焦点。
// 将输入框激活焦点
element.focus();
// 取消焦点
element.blur();
表单字段的公共事件
除了鼠标、键盘、变化和 HTML 事件外,所有字段还支持以下 3 个事件。
事件 | 描述 |
---|---|
blur | 在字段失去焦点时触发。 |
change | 在 <input> 和 <textarea> 元素的 value 发生变化且失去焦点时触发,或者在 <select> 元素中选中项发生变化时触发。 |
focus | 在字段获得焦点时触发。 |
blur
和 focus
事件会因为用户手动改变字段焦点或者调用 blur()
或 focus()
方法而触发。这两个事件对所有表单都会一视同仁。change 事件则不然,它会因控件不同而在不同时机触发。对于 <input>
和 <textarea>
元素,change 事件会在字段失去焦点,同时 value 自控件获得焦点后发生变化时触发。对于 <select>
元素,change
事件会在用户改变了选中项时触发,不需要控 件失去焦点。
文本框编程
在 HTML 中有两种表示文本框的方式:单行使用 <input>
元素,多行使用 <textarea>
元素。这两个控件非常相似,大多数时候行为也一样。不过,它们也有非常重要的区别。
默认情况下,<input>
元素显示为文本框,省略 type 属性会以"text"作为默认值。然后可以通过size 属性指定文本框的宽度,这个宽度是以字符数来计量的。而 value 属性用于指定文本框的初始值, maxLength 属性用于指定文本框允许的最多字符数。
<input type="text" size="25" maxlength="50" value="initial value" />
<textarea>
元素总是会创建多行文本框。可以使用 rows 属性指定这个文本框的高度,以字符数计量;以 cols 属性指定以字符数计量的文本框宽度,类似于 <input>
元素的 size 属性。与 <input>
不同的是,<textarea>
的初始值必须包含在 <textarea>
和 </textarea>
之间,如下所示:
<textarea rows="25" cols="5">
initial value
</textarea>
同样与 <input>
元素不同的是,<textarea>
不能在 HTML 中指定最大允许的字符数。
除了标记中的不同,这两种类型的文本框都会在 value 属性中保存自己的内容。通过这个属性, 可以读取也可以设置文本模式的值。
选择文本
输入过滤
不同文本框经常需要保证输入特定类型或格式的数据。或许数据需要包含特定字符或必须匹配某个特定模式。由于文本框默认并未提供什么验证功能,因此必须通过 JavaScript 来实现这种输入过滤。组合使用相关事件及 DOM 能力,可以把常规的文本框转换为能够理解自己所收集数据的智能输入框。
屏蔽字符
有些输入框需要出现或不出现特定字符。例如,让用户输入手机号的文本框就不应该出现非数字字符。我们知道 keypress 事件负责向文本框插入字符,因此可以通过阻止这个事件的默认行为来屏蔽非数字字符。
textbox.addEventListener('keypress', event => {
event.preventDefault();
});
运行以上代码会让文本框变成只读,因为所有按键都被屏蔽了。如果想只屏蔽特定字符,则需要检查事件的 charCode 属性,以确定正确的回应方式。
textbox.addEventListener('keypress', event => {
if (!/\d/.test(String.fromCharCode(event.charCode))) {
event.preventDefault();
}
});
处理剪贴板
IE 是第一个支持剪贴板相关事件及通过 JavaScript 访问剪贴板数据的浏览器。IE 的实现成为了事实标准,这是因为 Safari、Chrome、Opera 和 Firefox 都实现了相同的事件和剪贴板访问机制,后来 HTML5 也增加了剪贴板事件 。以下是与剪贴板相关的 6 个事件。
事件 | 触发时机 |
---|---|
beforecopy | 复制操作发生前触发 |
copy | 复制操作发生时触发 |
beforecut | 剪切操作发生前触发 |
cut | 剪切操作发生时触发 |
beforepaste | 粘贴操作发生前触发 |
paste | 粘贴操作发生时触发 |
通过 beforecopy、beforecut 和 beforepaste 事件可以在向剪贴板发送或从中检索数据前修改数据。不过,取消这些事件并不会取消剪贴板操作。要阻止实际的剪贴板操作,必须取消 copy、cut 和 paste 事件。
剪贴板上的数据可以通过 window 对象(IE)或 event 对象(Firefox、Safari 和 Chrome)上的 clipboardData 对象来获取。在 Firefox、Safari 和 Chrome 中,为防止未经授权访问剪贴板,只能在剪贴板事件期间访问 clipboardData 对象;IE 则在任何时候都会暴露 clipboardData 对象。为了跨浏览器兼容,最好只在剪贴板事件期间使用这个对象。
clipboardData 对象上有 3 个方法:getData()
、setData()
和 clearData()
,其中 getData()
方法从剪贴板检索字符串数据,并接收一个参数,该参数是要检索的数据的格式。IE 为此规定了两个选项:"text"和"URL"。Firefox、Safari 和 Chrome 则期待 MIME 类型,不过会将"text"视为等价于 "text/plain"。
function getClipboardText(event) {
var clipboardData = event.clipboardData || window.clipboardData;
return clipboardData.getData('text');
}
function setClipboardText(event, value) {
if (event.clipboardData) {
return event.clipboardData.setData('text/plain', value);
} else if (window.clipboardData) {
return window.clipboardData.setData('text', value);
}
}
自动切换
JavaScript 可以通过很多方式来增强表单字段的易用性。最常用的是在当前字段完成时自动切换到下一个字段。对于要收集数据的长度已知(比如电话号码)的字段是可以这样处理的。在美国,电话号码通常分为 3 个部分:区号、交换局号,外加 4 位数字。在网页中,可以通过 3 个文本框来表示这几个部分,比如:
<form>
<input type="text" name="tel1" id="txtTel1" maxlength="3" />
<input type="text" name="tel2" id="txtTel2" maxlength="3" />
<input type="text" name="tel3" id="txtTel3" maxlength="4" />
</form>
为增加这个表单的易用性并加速数据输入,可以在每个文本框输入到最大允许字符数时自动把焦点切换到下一个文本框。因此,当用户在第一个文本框中输入 3 个字符后,就把焦点移到第二个文本框,当用户在第二个文本框中输入 3 个字符后,把焦点再移到第三个文本框。这种自动切换文本框的行为可以通过如下代码实现:
function tabForward(event) {
let target = event.target;
if (target.value.length == target.maxLength) {
let form = target.form;
for (let i = 0, len = form.elements.length; i < len; i++) {
if (form.elements[i] == target) {
if (form.elements[i + 1]) {
form.elements[i + 1].focus();
}
return;
}
}
}
}
let inputIds = ['txtTel1', 'txtTel2', 'txtTel3'];
for (let id of inputIds) {
let textbox = document.getElementById(id);
textbox.addEventListener('keyup', tabForward);
}
let textbox1 = document.getElementById('txtTel1');
let textbox2 = document.getElementById('txtTel2');
let textbox3 = document.getElementById('txtTel3');
这个 tabForward()
函数是实现自动切换的关键。它通过比较用户输入文本的长度与 maxlength 属性的值来检测输入是否达到了最大长度。如果两者相等(因为浏览器会强制最大字符数,所以不可能出现多的情况),那么就要通过循环表单中的元素集合找到当前文本框,并把焦点设置到下一个元素。 这个函数接着给每一个文本框都指定了 onkeyup 事件处理程序。因为 keyup 事件会在每个新字符被插入到文本框中时触发,所以此时应该是检测文本框内容长度的最佳时机。在填写这个简单的表单时,用户不用按 Tab 键切换字段和提交表单。
HTML5 约束验证 API
HTML5 为浏览器新增了在提交表单前验证数据的能力。这些能力实现了基本的验证,即使 JavaScript 不可用或加载失败也没关系。这是因为浏览器自身会基于指定的规则进行验证,并在出错时显示适当的错误消息(无须 JavaScript)。
验证会根据某些条件应用到表单字段。可以使用 HTML 标记指定对特定字段的约束,然后浏览器会根据这些约束自动执行表单验证。