本章内容
- 理解表单
- 文本框验证与交互
- 使用其他表单控制
14.1 表单的基础知识
通过document.forms
可以取得页面中所有的表单。在这个集合里,可以通过数值索引或 name 值来取得特定的表单。
var firstForm = document.forms[0];
var myForm = document.forms["form2"];
14.1.1 提交表单
var form = document.getElementById("myForm");
EventUtil.addHandler(form, "submit", function(event) {
//取得事件对象
event = EventUtil.getEvent(event);
//阻止默认事件
EventUtil.preventDefault(event);
});
14.1.2 重置表单
var form = document.getElementById("myForm");
EventUtil.addHandler(form, "reset", function(event) {
//取得事件对象
event = EventUtil.getEvent(event);
//阻止表单重置
EventUtil.preventDefault(event);
});
14.1.3 表单字段
可以像访问页面中的其他元素一样,使用原生 DOM 方法访问表单元素。
- 共有的表单字段属性
- 共有的表单字段方法
- 共有的表单字段事件
14.2 文本框脚本
在 HTML 中,有两种方式来表现文本框:一种是使用<input>
元素的单行文本框,另一种是使用<textarea>
的多行文本框。
14.2.1 选择文本
上述两种文本都支持select()
方法,这个方法用于选择文本框中的所有文本。
- 选择(select)事件
- 取得选择的文本
- 选择部分文本
14.2.2 过滤输入
- 屏蔽字符
响应向文本框中插入字符操作的是keypress
事件。因此,可以通过阻止这个事件的默认行为来屏蔽此类字符。
EventUtil.addHandler(textbox, "keypress", function(event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
});
运行以上代码后,由于所有按键操作都将被屏蔽。如果只想屏蔽特定的字符,则需要检测keypress
事件对应的字符编码,然后再决定如何响应。例如,下列代码只允许用户输入数值。
EventUtil.addHandler(textbox, "keypress", function (event) {
event = EventUtil.getTarget(event);
var target = EventUtil.getTarget(event);
var charCode = EventUtil.getCharCode(event);
if(!/\d/.test(String.fromCharCode(charCode))) {
EventUtil.preventDefault(event);
}
});
虽然理论上只应该在用户按下字符键时才触发keypress
事件,但有些浏览器也会对其他键触发此事件。为了让代码更通用,只要不屏蔽那些字符编码小于 10 的键即可。将上面的函数重写如下。
EventUtil.addHandler(textbox, "keypress", function (event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var charCode = EventUtil.getCharCode(event);
if (!/\d/.test(String.fromCharCode(charCode)) && charCode > 9) {
EventUtil.preventDefault(event);
}
});
复制、粘贴及其他操作还要用到 Ctrl 键。在除 IE 之外的所有浏览器中,前面的代码也会屏蔽 Ctrl+C、Ctrl+V,以及其他使用 Ctrl 的组合键。因此,最后还要添加一个检测条件。
EventUtil.addHandler(textbox, "keypress", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var charCode = EventUtil.getCharCode(event);
if(!/\d/.test(String.fromCharCode(charCode) && charCode > 9 && !event.ctrlKey) {
EventUtil.preventDefault(event);
});
});
- 操作剪贴板
var EventUtil = {
getClipboardText: function(event) {
var clipboardData = (event.clipboardData || window.clipboardData);
return clipboardData.getData("text");
},
setClipboardText: function(event, value) {
if (event.clipboardData) {
return event.clipboardData.setData("text/plain", value);
} else if (window.clipboardData) {
return window.clipboardData.setData("text", value);
}
},
};
在paste
事件中,可以确定剪贴板中的值是否有效。
EventUtil.addHandler(textbox, "paste", function (event) {
event = EventUtil.getEvent(event);
var text = EventUtil.getClipboardText(event);
if (!/^\d*$/.test(text)) {
EventUtil.preventDefault(event);
}
});
14.2.3 自动切换焦点
(function () {
function tabForward(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.value.length == target.maxLength) {
var form = target.form;
for(var 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;
}
}
}
}
var textbox1 = document.getElementById("txtTel1");
var textbox2 = document.getElementById("txtTel2");
var textbox3 = document.getElementById("txtTel3");
EventUtil.addHandler(textbox, "keyup", tabForward);
EventUtil.addHandler(textbox2, "keyup", tabForward);
EventUtil.addHandler(textbox3, "keyup", tabForward);
})();
14.2.4 HTML5 约束验证 API
- 必填字段
第一种情况是在表单字段中指定了required
属性 - 其他输入类型
- 数值范围
- 输入模式
HTML5 为文本字段新增了pattern
属性。这个属性的值是一个正则表达式,用于匹配文本框中的值。
与其他输入类型相似,不能阻止用户输入无效的文本。 - 检测有效性
使用checkValidity()
方法可以检测表单中的某个字段是否有效。
if (document.forms[0].elements[0].checkValidity()) {
//字段有效,继续
} else {
//字段无效
}
要检测整个表单是否有效,可以在表单自身调用checkValidity()
方法。
if (document.forms[0].checkValidity()) {
//表单有效,继续
} else {
//表单无效
}
validity
属性则会告诉你为什么字段有效或无效。这个对象包含一系列属性,每个属性返回一个布尔值。
- 禁用验证
通过设置novalidate
属性,可以告诉表单不进行验证。
如果一个表单中有多个提交按钮,为了指定点击某个提交按钮不必验证表单,可以在相应的按钮上添加formnovalidate
属性。
14.3 选择框脚本
选择框是通过<select>
和<option>
元素创建的。为了方便与这个控件交互,除了所有表单字段公有的属性和方法外,HTMLSelectElement
类型还提供了下列属性和方法。
-
add(newOption, relOption)
:向控件中插入新<option>
元素,其位置在相关项之前。 -
multiple
:布尔值,表示是否允许多项选择,等价于 HTML 中的multiple
特性。 -
options
:控件中所有<option>
元素的HTMLCollection
。 -
remove(index)
: 移除给定位置的选项。 -
selectedIndex
:基于 0 的选中项的索引,如果没有选中项,则值为 -1。对于支持多选的控件,只保存选中项中第一项的索引。 -
size
:选择框中可见的行数;等价于 HTML 的size
特性。 - 选择框的
type
属性不是"select-one"
,就是"select-multiple"
,这取决于 HTML 代码中有没有multiple
特性。选择框的value
属性由当前选中项决定。
在 DOM 中,每个<option>
元素都有一个HTMLOptionElement
对象表示。为便于访问数据,HTMLOptionElement
对象添加了下列属性:
-
index
:当前选项在options
集合中的索引。 -
label
:当前选项的标签;等价于 HTML 中的label
特性。 -
selected
:布尔值,表示当前选项是否被选中。将这个属性设置为true
可以选中当前选项。 -
text
:选项的文本。 -
value
:选项的值(等价于 HTML 中的value
特性)。
14.3.1 选择选项
对于只允许选择一项的选择框,访问选中项的最简单方式,就是使用选择框的selectedIndex
属性。
var selectedOption = selectbox.options[selectbox.selectedIndex];
多选选择框,设置selectedIndex
属性会导致取消以前的所有选项并选择指定的那一项,而读取selectedIndex
则会返回选中项中第一项的索引值。
另一种选择选项的方式,就是取得对某一项的引用,然后将其selected
属性设置为true
。
selectbox.options[0].selected = true;
多选框可以通过设置selected
属性选中任意多个项。在单选框中,修改某个选项的selected
属性则会取消对其他选项的选择。需要注意的是,将selected
属性设置为false
对单选框没有影响。
要取得所有选中的项,可以循环遍历选项集合,然后测试每个选项的selected属性。如下。
function getSelectedOption(selectbox) {
var result = new Array();
var option = null;
for (var i=0,len=selectbox.options.length; i < len; i++) {
option = selectbox.options[i];
if (option.selected) {
result.push(option);
}
}
return result;
}
14.3.2 添加选项
第一种方式使用 DOM 方法。第二种方式是使用Option
构造函数。
第三种添加新选项的方式是使用选择框的add()
方法。兼容 DOM 的浏览器要求必须指定第二个参数。
var newOption = new Option("Option text", "Option value");
selectbox.add(newOption, undefined);
如果你想将新选项添加到其他位置(不是最后一个),就应该使用标准的 DOM 技术和insertBefore()
方法。
14.3.3 移除选项
首先,可以使用 DOM 的removeChild()
方法,为其传入要移除的选项。如下所示:
selectbox.removeChild(selectbox.options[0]);
其次,可以使用选择框的remove()
方法。这个方法可以接受一个参数,即要移除选项的索引,如下所示:
selectbox.remove(0);
最后一种方式,就是将相应选项设置为null
。这种方式也是 DOM 出现之前浏览器的遗留机制。如:
selectbox.options[0] = null;
要清除选择框中所有的项,需要迭代所有选项并逐个移除它们。如下所示:
function clearSelectbox(selectbox) {
for (var i=0, len=selectbox.options.length; i < len; i++) {
selectbox.remove(i?0);
}
}
这个函数每次只移除选择框中的第一个选项。由于移除第一个选项后,所有后续选项都会自动向上移动一个位置,因此重复移除第一个选项就可以移除所有选项了。
14.3.4 移动和重排选项
移动使用 DOM 的appendChild()
方法。
重排最合适的 DOM 的方法就是insertBefore()
。
14.4 表单序列化
随着 Ajax 的出现,表单序列化已经成为一种常见需求。可以利用表单字段的type
属性,连同name
和value
属性一起实现对表单的序列化。在编写代码之前,必须先搞清楚在表单提交期间,浏览器是怎样将数据发送给服务器的。
- 对表单字段的名称和值进行 URL 编码,使用和号(&)分隔。
- 不发送禁用的表单字段。
- 只发送勾选的复选框和单选按钮。
- 不发送 type 为“
reset
”和“button
”的按钮。 - 多选选择框中的每个选中的值单独一个条目。
- 在单击提交按钮提交表单的情况下,也会发送提交按钮;否则,不发送提交按钮。也包括
type
为“image
”的<input>
元素。 -
<select>
元素的值,就是选中的<option>
元素的value
特性的值。如果<option>
元素没有value
特性,则是<option>
元素的文本值。
以下就是实现表单序列化的代码。
function serialize(form) {
var parts = [],
field = null,
i,
len,
j,
optLen,
option,
optValue;
for (i=0, len=form.elements.length; i < len; i++) {
field = form.elements[i];
switch(field.type) {
case "select-one":
case "select-multiple":
if (field.name.length) {
for (j=0,optLen = field.options.length; j < optLen; j++) {
option = field.options[j];
if (option.selected) {
optValue = "";
if (option.hasAttribute) {
optValue = option.hasAttribute("value") ? option.value : option.text;
} else {
optValue = option.attributes("value").specified ? option.value : option.text;
}
parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(optValue));
}
}
}
break;
case undefined: //字段集
case "file": //文件输入
case "submit": //提交按钮
case "reset": //重置按钮
case "button": //自定义按钮
break;
case "radio": //单选按钮
case "checkbox": //复选框
if(!field.checked) {
break;
}
/* 执行默认操作 */
default: //不包含没有名字的表单字段
if (field.name.length) {
parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));
}
}
}
return parts.join("&");
}
14.5 富文本编辑
富文本编辑,又称为 WYSIWYG(What You See Is What You Get,所见即所得)。这一技术的本质,就是在页面中嵌入一个包含空 HTML 页面的 iframe
。通过设置designMode
属性,这个空白的HTML 页面可以被编辑,而编辑对象则是该页面<body>
元素的 HTML 代码。designMode
属性由两个可能的值:“off
”(默认值)和“on
”。在设置为“on
”时,整个文档都会变得可以编辑(显示插入符号),然后就可以像使用字处理软件一样,通过键盘将文本内容加粗、变成斜体,等等。
可以给iframe
指定一个非常简单的 HTML 页面作为其内容来源。例如:
<!DOCTYPE html>
<html>
<head>
<title>Blank Page for Rich Text Editing</title>
</head>
<body>
</body>
</html>
只有在页面完全加载之后才能设置designMode
属性。因此,在包含页面中,需要使用onload
事件处理程序来在恰当的时刻设置designMode
,如下所示:
<iframe name="richedit" style="height:100px;width:100px;" src="blank.htm"></iframe>
<script type="text/javascript">
EventUtil.addHandler(window, "load", function() {
frames["richedit"].document.designMode = "on";
});
</script>
等到以上代码执行之后,你就会在页面中看到一个类似文本框的可编辑区字段。这个区字段具有与其他网页相同的默认样式;不过,通过为空白页面应用 CSS 样式,可以修改可编辑区字段的外观。
14.5.1 使用 contenteditable 属性
另一种编辑富文本内容的方式是使用名为contenteditable
的特殊属性,这个属性也是由 IE 最早实现的。可以把contenteditable
属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。不需要iframe
、空白页和 JavaScript。
<div class="editable" id="richedit" contenteditable></div>
通过在这个元素上设置contenteditable
属性,也能打开或关闭编辑模式。
var div = document.getElementById("richedit");
richedit.contentEditable = "true";
contenteditable
属性有三个可能的值:“true
”表示打开、“false
”、表示关闭、“inherit
”表示从父元素那里继承(因为可以在contenteditable
元素中创建或删除元素)。兼容性很强。
14.5.2 操作富文本
与富文本编辑器交互的主要方式,就是使用document.execCommand()
。这个方法可以对文档执行预定义的命令,而且可以应用大多数格式。可以为document.execCommand()
方法传递 3 个参数:要执行的命令名称、表示浏览器是否应该为当前命令提供用户界面的一个布尔值和执行命令必须的一个值(如果不需要值,则传递null
)。为了确保跨浏览器的兼容性,第二个参数应该始终设置为false
。
不同浏览器支持的预定义命令也不一样。
其中,与剪贴板有关的命令在不同浏览器中的差异极大。Opera 根本没有实现任何剪贴板命令,而 Firefox 在默认情况下会禁用它们。Safari 和 Chrome 实现了cut
和copy
,但没有实现paste
。不过,即使不能通过document.execCommand()
来执行这些命令,但却可以通过相应的快捷键来实现同样的操作。
可以在任何时候使用这些命令来修改富文本区域的外观,如下例。
//转换粗体文本
frames["richedit"].document.execCommand("bold", false, null);
//转换斜体文本
frames["richedit"].document.execCommand("italic", false, null);
//创建指向 www.wrox.com 的链接
frames["richedit"].document.execCommand("createlink", false, "http://www.wrox.com");
//格式化为 1 级标题
frames["richedit"].document.execCommand("formatblock", false, "<h1>");
同样的方法也适用于页面中contenteditable
属性为true
的区块,只要把对框架的引用替换成当前窗口的document
对象即可。
需要注意的是,虽然所有浏览器都支持这些命令,但这些命令所产生的 HTML 不一定相同。
可以使用queryCommandEnabled()
来检测是否可以针对当前选择的文本,或者当前插入字符所在位置执行某个命令。
另外,queryCommandState()
方法用于确定是否已将指定命令应用到了选择的文本。
最后,queryCommandValue()
,用于取得执行命令时传入的值(即前面例子中传给document.execCommand()
的第三个参数)。
14.5.3 富文本选区
在富文本编辑器中,使用框架(iframe
)的getSelection()
方法,可以确定实际选择的文本。这个方法是window
对象和document
对象的属性,调用它会返回一个表示当前选择文本的Selection
对象。每个Selection
对象都有下列属性。
-
anchorNode
:选区起点所在的节点。 -
anchorOffset
:在到达选区起点位置之前跳过的anchorNode
中的字符数量。 -
focusNode
:选区终点所在的节点。 -
focusOffset
:focusNode
中包含在选区之内的字符数量。 -
isCollapsed
:布尔值,表示选区的起点和终点是否重合。 -
rangeCount
:选区中包含的 DOM 范围的数量。
Selection
对象的这些属性并没有包含多少有用的信息。好在,该对象的方法提供了更多信息。
IE 8 及更早的版本不支持 DOM 范围,但我们可以通过它支持的selection
对象操作选择的文本。
var range = frame["richedit"].document.selection.createRange();
var selectedText = range.text;
14.5.4 表单与富文本
从技术上说,富文本编辑器并不属于表单。换句话说,富文本编辑器中 HTML 不会被自动提交给服务器,而需要我们手工来提取并提交 HTML。为此,通常可以添加一个隐藏的表单字段,让它的值等于从iframe
中提取出的 HTML。
14.6 小结
使用 JavaScript 可以增强已有的表单字段,从而创造出新的功能,或者提升表单的易用性。
- 可以使用一些标准或非标准的方法选择文本框的全部或部分文本。
- 大多数浏览器都采用了 Firefox 操作选择文本的方式,但 IE 仍然坚持自己的实现。
- 在文本框的内容变化时,可以通过侦听键盘事件以及检测插入的字符,来允许或禁止用户输入某些字符。
在文本框内容必须限制为某些特定字符的情况下,就可以利用剪贴板事件来屏蔽通过粘贴向文本框中插入内容的操作。
选择框也是经常要通过 JavaScript 来控制的一个表单字段。
富文本编辑功能是通过一个包含空 HTML 文档的iframe
元素来实现的。