本篇内容
  • 表单的基础知识
  • 文本框脚本
  • 选择框脚本
  • 表单序列化
  • 富文本编辑

曲子Web表单没有为许多常见任务提供现成的解决手段,很多开发人员不仅会在验证表单时使用JavaScirpt,而且还增强了一些标准表单控件的默认行为。

1 表单的基础知识

HTMLFormElement类型

在HTML中,表单是由<form>元素来表示的,而在JavaScript中,表单对应的则是HTMLFormElement类型。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特性。

取得<form>元素引用的方式有好几种。其中最常见的方式就是将它看成与其他元素一样,并为其添加id特性,然后再使用getElementById()方法找到它。

其次,通过document.forms可以取得页面中所有的表单。在这个集合中,可以通过数值索引或name值采取得特定的表单。

另外,在较早的浏览器或者那些支持向后兼容的浏览器中,也会把每个设置了name特性的表单作为属性保存在document对象中。例如,通过document.form2可以访问到名为”form2”的表单。不过不推荐

注意,可以同时为表单指定id和name属性.但它们的值不-定相同

1.1 提交表单

用户单击提交按钮或图像按钮时,就会提交表单。使用<input><button>都可以定义提交按钮,只要将其type特性的值设置为”submit”即可。而图像按钮则是通过将<input>的type特性值设置为”image”来定义的。因此,只要我们单击以下代码生成的按钮,就可以提交表单。

1
2
3
4
5
<input type="submit" value="Submit Form">

<button type="submit">Submit Form</button>

<input type="image" src="graphic.git">

只要表单中存在上面列出的任何一种按钮,那么在相应表单控件拥有焦点的情况下.接回车键就可以提交该表单。(textarea是一个例外,在文本区中回车会换行。)如果表单里没有提交按钮,按回车键不会提交表单。
以这种方式提交表单时,浏览器会在将请求发送给服务器之前触发submit事件。这样,我们就有机会验证表单数据,并据以决定是否允许表单提交。阻止这个事件的默认行为就可以取消表单提交。例如,下列代码会阻止表单提交。

1
2
3
4
5
var form = document.getElementById('myForm');
EventUtil.addHandler(form, 'submit', function (event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
});

调用prevetnDefault()方法阻止了表单提交。一般来说,在表单数据无效而不能发送给服务器时,可以使用这一技术。

在JavaScript中,以编程方式调用submit()方法也可以提交表单。而且,这种方式无需表单包含提交接钮,任何时候都可以正常提交表单。来看一个例子。

1
2
var form = document.getElementById('myForm');
form.submit();

在以调用submit()方法的形式提交表单时,不会触发submit事件,因此要记得在调用此方法之前先验证表单数据。

提交表单时可能出现的最大问题,就是重复提交表单。在第一次提交表单后,如果伏时间没有反应,用户可能会变得不耐烦。这时候,他们也许会反复单击提交按钮。结果往往很麻烦(因为服务器要处理重复的消求),或者会造成错误(如果用户是下订单,那么可能会多订好几份)。解决这一问题的办法有两个:在第一次提交表单后就禁用提交按钮,或者利用onsubmit事件处理程序取消后续的 表单提交操作。

1.2 重置表单

在用户单击重置按钮时,表单会被重置。使用type特性值为”reset”的<input><button>都可以创建重置按钮,如下面的例子所示。

1
2
3
<input type="reset" value="reset Form">

<button type="reset">reset Form</button>

在重置表单时,所有表单字段都会恢复到页面刚加载完毕时的初始值。如果某个字段的初始值为空,就会恢复为默认值;而带有默认值的字段,也会恢复为默认值。
用户单击重置按钮重置表单时,会触发reset事件。利用这个机会,我们可以在必要时取消重置操作。例如,下面展示了阻止重置表单的代码。

1
2
3
4
5
var form = document.getElementById('myForm');
EventUtil.addHandler(form, 'reset', function (event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
});

与提交表单一样,也可以通过JavaScript来重置表单,如下面的例子所示。

1
2
var form = document.getElementById('myForm');
form.reset();

与词用submit()方法不同,调用reset()方法会像单击重置按钮一样触发reset事件。

在Web设计中,重置表单通常意味着对已经填写的数据不满意。重置表单经常会导效用户摸不着头脑,如果意外地触发了表单重置事件,那么用户甚至会很恼火。

1.3 表单字段

可以像访问页面中的其他元素一样,使用原生DOM方法访问表单元素。此外,每个表单都有elements属性,该属性是表单中所有元素的集合。这个elements集合是一个有序列表,其中包含着表单中的所有字段,例如<input>,<textarea><button><fieldset>。每个表单字段在elements集合中的顺序,与它们出现在标记中的顺序相同,可以按照位置和name特性来访问它们。下面来看一个例子。

1
2
3
4
5
6
var form = document.getElementById('form1');

var field1 = form.elements[0];
var field2 = form.elements['textbox1'];

var fieldCount = form.elements.length;

如果有多个表单控件都在使用一个name(如单选按钮),那么就会返回以该name命名的一个NodeList。例如,以下面的HTML代码片段为例。

1
2
3
4
5
6
7
<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>

在这个HTML表单中,有3个单选按钮,它们的name都是”color”,意味着这3个字段是一起的。在访问elements[‘color’]时,就会返回一下NodeList,其中包含这3个元素;不过,如果访问elements[0],则只会返回第一个元素。

也可以通过访问表单的属性访问元素,例如form[0]可以取得第一个表单字段,而form[‘color’]则可以取得第一个命名字段。这些属性与通过elements集合访问到的元素是相同的。但是,我们应该尽可能使用elements,通过表单属性访问元素只是为了与旧浏览器向后兼容而保留的一种过渡方式。

1.共有的表单字段属性

除了<fieldset>元素之外,所有表单字段都拥有相同的一组属性。由于<input>类型可以表示多种表单字段,因此有些属性只适用于某些字段,但还有一些属性是所有字段所共有的。表单字段共有的 属性和方法如下。

  • disabled:布尔值,表示当前字段是否被禁用。
  • form:指向当前字段所属表单的指针;只读。
  • name:当前字段的名称。
  • readOnly:布尔值,表示当前字段是否只读。
  • tablndex:表示当前字段的切换(tab)序号。
  • type:当前字段的类型,如”checkbox”、”radio”,等等。
  • value:当前字段将被提交给服务器的值。对文件字段来说,这个属性是只读的,包含着文件
    在计算机中的路径。

除了form属性之外,可以通过JavaScript动态修改其他任何属性。

1
2
3
4
5
var form = document.getElementById('form1');
var field = form.elements[0];

field.value = "test";
field.focus();

能够动态修改表单字段属性,意味着我们可以在任何时候,以任何方式来动态操作表单。例如,很多用户可能会重复单击表单的提交按钮。在涉及信用卡消费时,这就是个问题:因为会导致费用翻番。为此,最常见的解决方案,就是在第一次单击后就禁用提交按钮。只要侦听submit事件,并在该事件发生时禁用提交按钮即可。以下就是这样一个例子。

1
2
3
4
5
6
7
EventUtil.addHandler(form, 'submit', function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);

var btn = target.elements['submit-btn'];
btn.disabled = true;
})

除了<fieldset>之外,所有表单字段都有type属性。对于<input>元素,这个值等于HTML特性type的值。对于其他元素,这个type属性的值如下表所列。

说明 HTML示例 type属性的值
单选列表 <select>...</select> ‘select-one’
多选列表 <select multiple>...</select> ‘select-multiple’
自定义按钮 <button>...</button> ‘submit’
自定义非提交按钮 <button type="button">...</button> ‘button’
自定义重置按钮 <button type="reset">...</button> ‘reset’
自定义提交按钮 <button type="submit">...</button> ‘submit’

此外,<input><button>元素的type属性是可以动态修改的,而<select>元素的type属性则是只读的。

2.共有的表单字段方法

每个表单字段都有两个方法:focus()和blur()。其中,focus()方法用于将浏览器的焦点设置到表单字段,即激活表单字段,使其可以响应键盘事件。例如,接收到焦点的文本框会显示插入符号,随时可以接收输入。使用focus()方法,可以将用户的注意力吸引到页面中的某个部位。例如,在页面加载完毕后,将焦点转移到表单中的第一个字段。为此,可以侦听页面的load事件,并在该事件发生时在表单的第一个字段上调用focus()方法,如下面的例子所示。

1
2
3
EventUtil.addHandler(window, "load", function(event) {
docwnent.forms[0].elements[0].focus();
});

要注意的是,如果第一个表单字段是一个<input>元素,且其type特性的值为”hidden”,那么以上代码会导致错误。另外,如果使用css的display和visibility属性隐藏了该字段,同样也会导致错误。

HTML5为表单字段新增了一个autofocus属性。在支持这个属性的浏览器中,只要设置这个属性,不用JavaScript就能自动把焦点移动到相应字段。例如:

1
<input type="text" autofocus>

为了保证前面的代码在设置autofocus的浏览器中正常运行,必须先检测是否设置了该属性。如果设置了,就不用再调用focus()了。

1
2
3
4
5
6
EventUtil.addHandler(window, "load", function(event) {
var element = docwnent.forms[0].elements[0];
if (element.autofocus !== true) {
element.focus();
}
});

因为autofocus是一个布尔值属性,所以在支持的浏览器中它的值应该是true。(在不支持的浏览器中,它的值将是空字符串。)为此,上面的代码只有在autofocus不等于true的情况下才会调用focus(),从而保证向前兼容。支持autofocus属性的浏览器有Firefox4+、Safari5+、Chrome和Opera9.6。

在默认情况下,只有表单字段可以获得焦点。 对于其他元素而言,如果先将其tabIndex属性设置为-1,然后再调用focus()方法,也可以让这些元素获得焦点。只有Opera不支持这种技术。

与focus()方法相对的是blur()方法,它的作用是从元素中移走焦点。在调用blur()方法时,并不会把焦点转移到某个特定的元素上;仅仅是将焦点从调用这个方法的元索上面移走而已。在早期Web开发中,那时候的表单字段还没有readonly特性,因此就可以使用blur()方法来创建只读字段。现在,虽然需要使用blur()的场合不多了,但必要时还可以使用的。用法如下:

1
docwnent.forms[0].elements[0].blur();

3.共有的表单字段事件

除了支持鼠标、键盘、更改和HTML事件之外,所有表单字段都支持下列3个事件。

  • blur:当前字段失去焦点时触发。
  • change:对于<input><textarea>元素,在它们失去焦点且value值改变时触发;对于<select>元素,在其选项改变时触发。
  • focus:当前字段获得焦点时触发。

当用户改变了当前字段的焦点,或者我们询用了blur()或focus()方法时,都可以触发blur和focus事件。这两个事件在所有表单字段中都是相同的。但是,change事件在不同表单控件中触发的次数会有所不同。对于<input><textarea>元素,当它们从获得焦点到失去焦点且value值改变时,才会触发change事件。对于<select>元素,只要用户选择了不同的选项,就会触发change事件;换句话说,不失去焦点也会触发change事件。
通常,可以使用focus和blur事件来以某种方式改变用户界面。要么是向用户给出视觉提示,要么是向界面中添加额外的功能。而change事件则经常用于验证用户在字段中输入的数据。例如,假设有一个文本框,我们只允许用户输入数值。此时,可以利用focus事件修改文本框的背景颜色,以便更清楚地表明这个字段获得了焦点。可以利用blur事件恢复文本框的背景颜色,利用change事件在用户输入了非数值字符时再次修改背景颜色。

在此,onfocus事件处理程序将文本框的背景颜色修改为黄色,以清楚地表单当前字段已经激活。随后,onblur和onchange事件处理程序则会在发现非敬值字符时,将文本框背景颜色修改为红色。为了测试用户输入的是不是非数值。这里针对文本框的value属性使用了简单的正则表达式。而且,为确保无论文本框的值如何变化,验证规则始终如一。onblur和onchange事件处理程序中使用了相同的正则表达式。

关于blur和change事件的关系,并没有严格的规定。在某些浏览器中,blur事件会先于change事件发生;而在其他浏览器中,则恰好相反。

2 文本框脚本

在HTML中,有两种方式来表现文本框:一种是使用<input>元素的单行文本框,另一种是使用<textarea>的多行文本框。这两个控件非常相似,而且多数时候的行为也差不多。不过,它们之间仍然存在一些重要的区别。
要表现文本框,必须将<input>元素的type特性设置为”text”。而通过设置size特性,可以指定文本框中能够显示的字符数。通过value特性,可以设置文本框的初始值,而maxlength特性则用于指定文本框可以接受的最大字符数。如果要创建一个文本框,让它能够显示25个字符,但输入不能超过50个字符,可以使用以下代码。

1
<input type="text" size="25" maxlength="50" value="test">

相对而言,<textarea>元素则始终会呈现为一个多行文本框。要指定文本框的大小,可以使用rows和cols特性。其中,rows特性指定的是文本框的字符行数,而cols特性指定的是文本框的字符列数。

1
<textarea rows="25" cols="5">test</textarea>

<textares><input>的区别在于:<textarea>的初始值必须要放在标签之间;不能在HTML中给<textarea>指定最大字符数。

建议读者使用value属性读取或设置文本框的值,不建议使用标准的DOM方法。换句话说,不要使用setAttribute()设置<input>元素的value特性,也不要去修改<textarea>元素的第一个子节点。原因很简单:对value属性所作的修改,不一定会反映在DOM中。因此,在处理文本框的值时,最好不要使用DOM方法。

2.1 选择文本

上述两种文本框都支持select()方法,这个方法用于选择文本框中的所有文本。在调用select()方法时,大多数浏览器(Opera除外)都会将焦点设置到文本框中。这个方法不接受参数,可以在任何时候被调用。看一个例子。

1
2
var textbox = document.forms[0].elements['textbox1'];
textbox.select();

在文本握获得焦点时选择其所有文本,这是一种非常常见的做法,特别是在文本框包含默认值的时候。因为这样做可以让用户不必一个一个地删除文本。下面展示了实现这一操作的代码

1
2
3
4
5
6
EventUtil.addHandler(textbox, 'focus', function (event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);

target.select();
});

将上面的代码应用到文本框之后,只要文本框获得焦点,就会选择其中所有的文本。这种技术能够较大幅度地提升表单的易用性。

1.选择(select)事件

与select()方法对应的,是-个select事件。在选择了文本框中的文本时,就会触发select事件。不过,到底什么时候触发select事件,还会因浏览器而异。
在IE9+、Opera、Firefox、Chrome和Safari中。只有用户选择了文本(而且要释放鼠标),才会触发select事件。而IE8及更早版本中,只要用户选择了一个字母(不必释放鼠标),就会触发select事件。另外,在调用select()方法时也会触发select事件。

1
2
3
4
var textbox = document.forms[0].elements['textbox1'];
EventUtil.addHandler(textbox, 'select', function (event) {
alert('text selected' + textbox.value);
});

2.取得选择的文本

虽然通过select事件我们可以知道用户什么时候选择了文本,但仍然不知道用户选择了什么文本。HTML5通过一些扩展方案解决了这个问题,以便更顺利地取得选择的文本。该规范采取的办法是添加两个属性:selectionStart和selectionEnd。这两个属性中保存的是基于0的数值,表示所选择文本的范围(即文本选区开头和结尾的偏移量)。因此,要取得用户在文本框中选择的文本,可以使用如下代码。

1
2
3
function getSelectedText(textbox) {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
}

因为substring()方法基于字符串的偏移量执行操作,所以将selectionStart和selectionEnd直接传给它就可以取得选中的文本。

IE8及更早的版本中有一个document.selection对象,其中保存着用户在整个文档范围内选择的文本信息

3.选择部分文本

HTML5也为选择文本框中的部分文本提供了解决方案,即最早由Firefox引入的setSelectionRange()方法。现在除select()方法之外,所有文本框都有一个setSelectionRange()方法。这个方法接收两个参数:要选择的第一个字符的索引和要选择的最后一个字符之后的字符的索引(类似于substring()方法的两个参数)。来看一个例子。

1
2
3
4
5
6
7
8
9
10
textbox.value = "Hello world!"; 

// 选择所有文本
textbox.setSelectionRange(0, textbox.value.length); // 'Hello world!”

// 前3个字符
textbox.setSelectionRange(0, 3); // "Hel"

// 第4至第6个字符
textbox.setSelectionRange(4, 7); // "o w"

兼容IE的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
function selectText (textbox, startIndex, stopindex) {
if (textbox.setSelectionRange) {
textbox.setSelectionRange(startindex, stopindex);
} else if (textbox.createTextRange) {
var range = textbox.createTextRange();
range.collapse(true);
range.moveStart("character", startindex);
range .moveEnd("character", stopindex - startindex);
range.select();
}

textbox.focus();
}

这个selectText()函数接收三个参数:要操作的文本框、要选择文本中第一个字符的索引和要选择文本中最后一个字符之后的索引。

2.2 过滤输入

我们经常会要求用户在文本框中输入特定的数据,或者输入特定格式的数据。例如,必须包含某些字符,或者必须匹配某种模式。由于文本框在默认情况下没有提供多少验证数据的手段,因此必须使用JavaScript来完成此类过滤输入的操作。而综合运用事件和DOM手段,就可以将普通的文本框转换成能够理解用户输入数据的功能型控件。

1.屏蔽字符

有时候,我们需要用户输入的文本中包含或不包含某些字符。例如,电话号码中不能包含非数值字符。如前所述,响应向文本框中插入字符操作的是keypress事件。因此,可以通过阻止这个事件的默认行为来屏蔽此类字符。在极端的情况下,可以通过下列代码屏蔽所有按键操作。

1
2
3
EventUtil.addHandler(textbox, 'keypress', function (event) {
event = EventUtil.getEvent(event); EventUtil.preventDefault(event);
});

如果只想屏蔽特定的字符,则需要检测keypress事件对应的字符编码,然后再决定如何响应。例如,下列代码只允许用户输入数值。

1
2
3
4
5
6
EventUtil.addHandler(textbox, 'keypress', function (event) {
event = EventUtil.getEvent(event);
var charCode = EventUtil.getCharCode(event);

if (!/\d/.teet(String.fromCharcode(charCode))) EventUtil.preventDefault(event);
});

虽然理论上只应该在用户按下字符键时才触发keypress事件,但有些浏览器也会对其他键触发此事件。Firefox和Safari(3.1版本以前)会对向上键、向下键、退格键和删除键触发keypress事件:Safari3.1及更新版本则不会对这些键触发keypress事件。这意味着,仅考虑到屏蔽不是数值的字符还不够,还要避免屏蔽这些极为常用和必要的键。所幸的是,要检测这些键并不困难。在Firefox中,所有自非字符键触发的keypress事件对应的字符编码为0,而在Safari3以前的版本中,对应的字符编码全部为8。为了让代码更通用,只要不屏蔽那些字符编码小于10的键即可。故而,可以将上面的函数量写成如下所示。

1
2
3
4
5
6
EventUtil.addHandler(textbox, 'keypress', function (event) {
event = EventUtil.getEvent(event);
var charCode = EventUtil.getCharCode(event);

if (!/\d/.teet(String.fromCharcode(charCode)) && charCode > 9) EventUtil.preventDefault(event);
});

除此之外,还有一个问题需要处理:复制、粘贴及其他操作还要用到Ctrl键。在除IE之外的所有浏览器中,前面的代码也会屏蔽ctrl+C、Ctrl+V,以及其他使用Ctrl的组合键。因此,最后还要添加一个检测条件,以确保用户没有按下Ctrl键,如下面的例子所示。

1
2
3
4
5
6
EventUtil.addHandler(textbox, 'keypress', function (event) {
event = EventUtil.getEvent(event);
var charCode = EventUtil.getCharCode(event);

if (!/\d/.teet(String.fromCharcode(charCode)) && charCode > 9 && !event.ctrlKey) EventUtil.preventDefault(event);
});

2.操作剪贴板

下列就是6个剪贴板事件。

  • beforecopy:在发生复制操作前触发。
  • copy:在发生复制操作时触发。
  • beforecut:在发生剪切操作前触发。
  • cut:在发生剪切操作时触发。
  • beforepaste:在发生帖贴操作前触发。
  • paste:在发生粘贴操作时触发。

由于没有针对剪贴板操作的标准,这些事件及相关对象会因浏览器而异。在Safari、Chrome和Firefox中,beforecopy、beforecut和beforepaste事件只会在显示针对对文本框的上下文菜单(预期将发生剪贴板事件)的情况下触发。但是,也则会在触发copy、cut和paste事件之前先行触发这些事件。至于copy、cut和paste事件,只要是在上下文菜单中选择了相应选项,或者使用了相应的键盘组合键,所有浏览器都会触发它们。

在实际的事件发生之前,通过beforecopy、beforecut和beforepaste事件可以在向剪贴板发送数据,或者从剪贴板取得数据之前修改数据。不过,取消这些事件并不会取消对剪贴板的操作——只有取消copy、cut和paste事件,才能阻止相应操作发生。

要访问剪贴板中的数据,可以使用clipboardData对象:在IE中,这个对象是window对象的属性;而在Firefox4+、Safari和Chrome中,这个对象是相应event对象的属性。但是,在Firefox、Safari和Chorme中,只有在处理剪贴板事件期间clipboardData对象才有效,这是为了防止对剪贴板的未授权访问;在IE中,则可以随时访问clipboardData对象。 为了确保跨浏览器兼容性,最好只在发生剪贴板事件期间使用这个对象。

这个clipboardData对象有三个方法:getData()、setData()和clearData()。其中,getData()用于从剪贴板中取得数据,它接受一个参数,即要取得的数据的格式。在IE中,有两种数据格式:”text”和”URL”。在Firefox、Safari和Chrome中,这个参数是一种MIME类型;不过,可以用”text”代表”text/plain”。

类似地,setData()方法的第一个参数也是数据类型,第二个参数是要放在剪贴板中的文本。对于第一个参数,IE照样支持”text”和”URL”,而Safari和Chrome仍然只支持MIME类型。但是,与getData()方法不同的是,Safari和Chrome的setData()方法不能识别”text”类型。这两个浏览器在成功将文本放到剪贴板中后,都会返回true;否则,返回false。为了弥合这些差异,我们可以向EventUtil中再添加下列方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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事件中,可以确定剪贴板中的值是否有效,如果无效,就可以像下面示例中那样,取消默认的行为。

1
2
3
4
5
6
7
8
EventUtil.addHandler(textbox, 'paste', function (event) {
event = EventUtil.getEvent(event);
var text = EventUtil.getClipboardText(event);

if (!/\d*$/.text(text)) {
EventUtil.preventDefault(event);
}
})

在这里,onpaste事件处理程序可以确保只有数值才会被粘贴到文本框中。如果剪贴板的值与正则表达式不匹配,则会取消粘贴操作。Firefox、Safari和Chrome只允许在onpaste事件处理程序中访问getData()方法。
并非所有浏览器都支持访问剪贴板,所以更简单的做法是屏蔽一或多个剪贴板操作。在支持copy、cut和paste事件的浏览器中(IE、Safiri、Chrome和Firefox3及更高版本),很容易阻止这些事件的默认行为。在Opera中,则需要阻止那些会触发这些事件的按键操作,同时还要阻止在文本框中显示上下文菜单。

2.3 自动切换焦点

使用JavaScript可以从多个方面增强表单字段的易用性。其中,最常见的一种方式就是在用户填写完当前字段时,自动将焦点切换到下一个字段。通常,在自动切换焦点之前,必须知道用户已经输入了既定长度的数据(例如电话号码)。例如,美国的电话号码通常会分为三部分:区号、局号和另外4位数字。为取得完整的电话号码,很多网页中都会提供下列 3 个文本框:

1
2
3
<input type="text" name="tel" id="txtTel1" maxlength="3"> 
<input type="text" name="tel2" id="txtTel2" maxlength="3">
<input type="text" name="tel3" id="txtTel3" maxlength="4">

为增强易用性,同时加快数据输入,可以在前一个文本框中的字符达到最大数量后,自动将焦点切换到下一个文本框。换句话说,用户在第一个文本框中输入了3个数字之后,焦点就会切换到第二个文本框,再输入3个数字,焦点又会切换到第三个文本框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(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(textbox1, 'keyup', tabForward);
EventUtil.addHandler(textbox2, 'keyup', tabForward);
EventUtil.addHandler(textbox3, 'keyup', tabForward);
}) ();

开始的tabForward()函数是实现”自动切换焦点”的关键所在。这个函数通过比较用户输入的值与文本框的maxlength特性,可以确定是否已经达到最大长度。如果这两个值相等(因为浏览苦苦最终会强制它们相等,因此用户绝不会多输入字符),则需要查找袭单字段集合,直至找到下一个文本框。找到下一个文本框之后,则将焦点切换到该文本框。然后,我们把这个函数指定为每个文本框的onkeyup事件处理程序。由于keyup事件会在用户输入了新字符之后触发,所以此时是检测文本框中内容长度的最佳时机。这样一来,用户在填写这个简单的表单时,就不必再通过按制表键切换表单字段和提交表单了。
不过请记住,这些代码只适用于前面给出的标记,而且没有考虑隐藏字段。

2.4 HTML5约束验证API

为了在将表单提交到服务器之前验证数据,HTML5新增了一些功能。有了这些功能,即使JavaScript被禁用或者由于种种原因未能加载,也可以确保基本的验证。
换句话说,浏览器自己会根据标记中的规则执行验证,然后自己显示适当的错误消息(完全不用JavaScript插手)。当然,这个功能只有在支持HTML5这部分内容的浏览器中才有效,这些浏览器有Firefox4+、Safari5+、Chrome和Operal0+。

只有在某些情况下表单字段才能进行自动验证。具体来说,就是要在HTML标记中为特定的字段指定一些约束,然后浏览器才会自动执行表单验证。

1.必填字段

第一种情况是在表单字段中指定了required属性,如下面的例子所示:

1
<input type="text" name="username" required>

任何标注有required的字段,在提交表单时都不能空着。这个属性适用于<input><textarea><select>字段(Opera11及之前版本还不支持<select>的required属性)。在JavaScript中,通过对应的required属性,可以检查某个表单字段是否为必填字段。

1
var isUsernameRequired = document.forms[0].elements['username'].required;

另外,使用下面这行代码可以测试浏览器是否支待required属性。

1
var isRequiredSupported = "required" in document.createElement('input');

2.其他输入类型

HTML5为<input>元素的type属性又增加了几个值。这些新的类型不仅能反映数据类型的信息,而且还能提供一些默认的验证功能。其中,”email”和”url”是两个得到支持最多的类型,各浏览器也都为它们增加了定制的验证机制。例如:

1
2
<input type="email" name="email">
<input type="url" name="pageurl">

要检测浏览器是否支持这些新类型,可以在JavaScript创建一个<input>元素,然后将type属性设置为”email”或”url”,最后再检测这个属性的值。不支持它们的旧版本浏览器会自动将来知的值设置为”text”,而支持的浏览器则会返回正确的值。

1
2
3
4
var input = document.createElement('input');
input.type = 'email';

var isEmailSupported = (input.type === 'email');

3.数值范围

“number”、”range”、”datetime”、”datetime-local”、”date”、”month”、”week”、”time”。浏览器对这几个类型的支持情况并不好,因此如果真想选用的话,要特别小心。目前,浏览器开发商主要关注更好的跨平台兼容性以及更多的逻辑功能。因此,本节介绍的内容某种程度上有些超前,不一定马上就能在实际开发中使用。
对所有这些数值类型的输入元素,可以指定min属性(最小的可能值)、max属性(最大的可能值)和step属性(从min到max的两个刻度间的差值)。例如.想让用户只能输入。到100的值,而且这个值必须是5的倍数,可以这样写代码:

1
<input type="number" min="0" max="100" step="5" name="count">

以上这些属性在JavaScript中都能通过对应的元素访问(或修改)。此外.还有两个方法:stepUp()和stepOown(),都接收一个可选的参数:要在当前值基础上加上或减去的数值。(默认是加或减1)这两个方法还没有得到任何浏览器支持,但下面的例子演示了它们的用法。

1
2
3
4
input.setUp();  // +1
input.setUp(5); // +5
input.setDown(); // -1
input.setDown(10); // -10

4.输入模式

HTML5为文本字段新增了pattern属性。这个属性的值是一个正则表达式,用于匹配文本框中的值。例如,如果只想、允许在文本字段中输入数值,可以像下面的代码一样应用约束:

1
<input type="text" pattern="\d+" name="count">

注意,模式的开头和末尾不用加^和$符号(假定已经有了)。这两个符号表示输入的值必须从头到尾都与模式匹配。
与其他输入类型相似,指定pattern也不能阻止用户输入无效的文本。这个模式应用给浏览器来判断值是有效,还是无效。在JavaScript中可以通过pattern属性访问模式。

1
var pattern = document.forms[0].elements['count'].pattern;

使用以下代码可以检测浏览器是否支持pattern属性。

1
var isPatternSupported = "pattern" in document.createElement('input');

5.检测有效性

使用checkValidity()方法可以检测表单中的某个字段是否有效。所有表单字段都有个方法,如果字段的值有效,这个方法返回true,否则返回false。 字段的值是否有效的判断依据是本节前面介绍过的那些约束。换句话说,必填字段中如果没有值就是无效的,而字段中的值与pattern属性不匹配也是无效的。例如:

1
2
3
4
5
if (document.forms[0].elements[0].checkValidity()) {
// 字段有效
} else {
// 字段无效
}

要检测整个表单是否有效,可以在表单自身调用checkValidity()方法。如果所有表单字段都有效,这个方法返回true;即使有一个字段无效,这个方法也会返回false。

1
2
3
4
5
if (document.forms[0].checkValidity()) {
// 表单有效
} else {
// 表单无效
}

与checkValidity()方法简单地告诉你字段是否有效相比,validity属性则会告诉你为什么字段有效或无效。这个对象中包含一系列属性,每个属性会返回一个布尔值 。

  • customError:如果设置了 setCustomValidity(),则为true,否则返回false。
  • patternMismatch:如果值与指定的pattern属性不匹配,返回true。
  • rangeOverflow:如果值比max值大,返回true。
  • rangeUnderflow:如果值比win值小,返回true。
  • stepMisMatch:如果min和max之间的步长值不合理,返回true。
  • tooLong:如果值的长度超过了maxlength属性指定的长度,返回true。有的浏览器(如Firefox4)会自动约束字符数量,因此这个值可能永远都远回false。
  • typeMismatch:如果值是”mail”或”url”要求的格式,返回true。
  • valid:如果这里的其他属性都是false,返回true。checkValidity()也要求相同的值。
  • valueMissing:如果标注为required的字段中没有值,返回true。

6.禁用验证

通过设置novalidate属性,可以告诉表单不进行验证。

1
2
3
<form method="post" action="signup.php" novalidate>
...
</form>

在JavaScript中使用noValidate属性可以取得或设置这个值,如果这个属性存在,值为true;如果不存在,值为false。

1
document.forms[0].noValidate = true;    //  禁用

如果一个表单中有多个提交按钮,为了指定点击某个提交按钮不必验证袭单,可以在相应的按钮上添加formnovalidate属性。

1
2
3
4
<form method="post" action="signup.php" novalidate>
<input type="text" name="tel" id="txtTel1" maxlength="3">
<input type="text" name="tel2" id="txtTel2" maxlength="3" formnovalidate>
</form>

3 选择框脚本

选择框是通过<select><Option>元素创建的。为了方便与这个控件交互,除了所有表单字段共有的属性和方法外,HTMLSelectElement类型还提供了下列属性和方法。

  • add(newOption, relOption):向控件中插入新<option>元素, 其位置在相关项(relOption)之前。
  • multiple:布尔值,表示是否允许多项选择;等价于HTML中的multiple特性。
  • options:控件中所有<option>元素的HTMLCollection。
  • remove(index):移除给定位置的选项。
  • selectedIndex:基于0的选中项的索引,如果没有选中项,则值为-1。对于支持多选的控件,只保存选中项中第一项的索引。
  • size:选择框中可见的行数;等价于HTML中的size特性。

选择框的type属性不是”select-one”,就是”select-multiple”,这取决于HTML代码中有没有multiple特性。选择框的value属性由当前选中项决定,相应规则如下。

  • 如果没有选中的项,则选择框的value属性保存空字符串。
  • 如果有一个选中项,而且该项的value特性已经在HTML中指定,则选择框的value属性等于选中项的value特性。即使value特性的值是空字符串,也同样遵循此条规则。
  • 如果有一个选中项,但该项的value特性在HTML中未指定,则选择框的value属性等于该项的文本。
  • 如果有多个选中项,则选择框的value属性将依据前两条规则取得第一个选中项的值。

在DOM中,每个<option>元素都有一个HTMLOptionElement对象表示。为便于访问数据,HTMLOptionElement对象添加了下列属性:

  • index:当前选项在options集合中的索引。
  • label:当前选项的标签;等价于HTML中的label特性。
  • selected:布尔值,表示当前选项是否被选中。将这个属性设置为true可以选中当前选项。
  • text:选项的文本。
  • value:选项的值(等价于HTML中的value特性)。

其中大部分属性的目的,都是为了方便对选项数据的访问。虽然也可以使用常规的DOM功能采访问这些信息,但效率是比较低的,如下面的例子所示:

1
2
3
4
5
6
7
8
9
var selectbox = document.forms[0].elements['location'];

// 不推荐
var text = selectbox.options[0].firstChild.nodeValue; // 选项的文本
var value = selectbox.options[0].getAttribute('value'); // 选项的值

// 推荐
var text = selectbox.options[0].text; // 选项的文本
var value = selectbox.options[0].value; // 选项的值

在操作选项时,我们建议最好是使用特定于选项的属性,因为所有浏览器都支持这些属性。在将表单控件作为DOM节点的情况下,实际的交互方式则会因浏览器而异。我们不推荐使用标准DOM技术修改<option>元素的文本或者值。

最后,我们还想提醒读者注意一点:选择框的change事件与其他表单字段的change事件触发的条件不一样。其他表单字段的change事件是在值被修改且焦点离开当前字段时触发,而选择框的change事件只要选中了选项就会触发。

不同浏览器下,选项的value属性返回什么值也存在差别。但是,在所有浏览器中,value属性始终等于va1ue特性。在未指定value特性的情况下,IE8会返回空字符串,而IE9+、Firefox、Chrome和Opera则会返回与text特性相同的值。

3.1 选择选项

对于只允许选择一项的选择框,访问选中项的最简单方式,就是使用选择框的selectedIndex属性,如下面的例子所示:

1
var selectedOption = selectbox.options[selectbox.selectedIndex);

取得选中项之后,可以像下面这样显示该选项的信息:

1
2
3
4
var selectedIndex = selectbox.selectedIndex; 
var selectedOption = selectbox.options[selectedIndex];

alert('Selected index: ' + selectedIndex + ';Selected text:' + selectedOption.text + ';Selected value:' + selectedOption.value);

对于可以选择多琐的选择框,selectedfIndex属性就好像只允许选择一项一样。设置selectedIndex会导致取消以前的所有选项并选择指定的那一项,而读取selectedIndex则只会返回选中项中第一项的索引值。
另一种选择选项的方式,就是取得对某一项的引用,然后将其selected属性设置为true。例如,下面的代码会选中选择框中的第一项:

1
selectbox.options[0].selected = true;

与selectedIndex不同,在允许多选的选择框中设置选项的selected属性,不会取消对其他选中项的选择,因而可以动态选中任意多个项。但是,如果是在单选选择框中,修改某个选项的selected属性则会取消对其他选项的选择。需要注意的是,将selected属性设置为false对单选选择框没有影响。
实际上,selected属性的作用主要是确定用户选择了选择框中的哪一项。要取得所有选中的项,可以循环遍历选项集合,然后测试每个选项的selected属性。来看下面的例子。

1
2
3
4
5
6
7
8
9
10
11
function getSelectedOptions(selectbox) {
var restult = [];
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;
}

3.2 添加选项

可以使用JavaScript动态创建选项,并将它们添加到选择框中。添加l选项的方式有很多,第一种方式就是使用如下所示的DOM方法。

1
2
3
4
var newOption = document.createElement('option');
newOption.appendChild(document.createTextNode('Option text'));
newOption.setAttribute('value', 'Option value');
selectbox.appendChild(newOption);

第二种方式是使用Option构造函数来创建新选项,这个构造函数是DOM出现之前就有的,一直遗留到现在。Option构造函数接受两个参数:文本(text)和值(value);第2个参数可选。虽然这个构造函数会创建一个Object的实例,但兼容DOM的浏览器会返回一个<option>元素。换句话说,在这种情况下,我们仍然可以使用appendChild()将新选项添加到选择框中。来看下面的例子。

1
2
var newOption = new Option('Option text', 'Option value');
selectbox.appendChild(newOption); // IE8及以前有问题

这种方式在除IE之外的浏览器中都可以使用。由于存在bug,IE在这种方式下不能正确设置新选项的文本。

第三种添加新选项的方式是使用选择框的add()方法。DOM规定这个方法接受两个参数:要添加的新选项和将位于新选项之后的选项。如果想在列表的最后添加一个选项,应该将第二个参数设置为null。在IE对add()方法的实现中,第二个参数是可选的,而且如果指定,该参数必须是新选项之后选项的索引。兼容DOM的浏览器要求必须指定第二个参数,因此要想编写跨浏览器的代码,就不能只传入一个参数。这时候,为第二个参数传入undefined,就可以在所有浏览器中都将新选项铺入到列表最后了。来看一个例子。

1
2
var newOption = new Option('Option text', 'Option value');
selectbox.add(newOption, undefined); // 最佳方案

在IE和兼容DOM的浏览器中,上面的代码都可以正常使用。如果你想将新选项添加到其他位置(不是最后一个),就应该使用标准的DOM技术和insertBefore()方法。

就和在HTML中一样,此时也不一定要为选项指定位。换句话说,只为option构造函数传入一个参数(选项的文本)也没有问题。

3.3 移除选项

与添加选项类似,移除选项的方式也有很多种。首先,可以使用DOM 的removeChild()方法,为其传人要移除的选项,如下面的例子所示:

1
selectbox.removeChild(selectbox.options[0]);    // 移除第一个选项

其次,可以使用选择框的remove()方法。这个方法接受一个参数,即要移除选项的索引,如下面 的例子所示:

1
selectbox.remove(0);    // 移除第一个选项

最后一种方式,就是将相应选项设置为null。这种方式也是DOM出现之前浏览器的遗留机制。例如:

1
selectbox.options[0] = null;    // 移除第一个选项

要消除选择框中所有的项,需要选代所有选项并逐个移除它们,如下面的例子所示:

1
2
3
4
5
function clearSelectbox(selectbox) {
for (var i = 0, len = selectbox.options.length; i < len; i++) {
selectbox.remove(i);
}
}

这个函数每次只移除选择框中的第一个选项。由于移除第一个选项后,所有后续选项都会自动向上移动一个位置,因此重复移除第一个选项就可以移除所有选项了。

3.4 移动和重排选项

在DOM标准出现之前,将一个选择程中的选项移动到另一个选择框中是非常麻烦的。整个过程要涉及从第一个选择框中移除选项,然后以相同的文本和值创建新选项,最后再将新选项添加到第二个选择框中。而使用DOM的appendChild()方法,就可以将第一个选择框中的选项直接移动到第二个选择框中。我们知道,如果为appendChild()方法传入一个文档中已有的元素,那么就会先从该元素的父节点中移除它,再把它添加到指定的位置。下面的代码展示了将第一个选择框中的第一个选项移动到第二个选择框中的过程。

1
2
3
4
var selectbox1 = document.getElementById('selLocations1');
var selectbox2 = document.getElementById('selLocations2');

selectbox2.appendChild(selectbox1.options[0]);

移动选项与移除选项有一个共同之处,即会重宣每一个选项的index属性。

重排选项次序的过程也十分类似,最好的方式仍然是使用DOM方法。要将选择框中的某一项移动到特定位置,最合适的DOM方法就是insertBefore();appendChild()方法只适用于将选项添加到选择框的最后。要在选择框中向前移动一 个选项的位置,可以使用以下代码:

1
2
var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index - 1]); // 类似的,向后移动一个位置index+2

IE7存在一个重绘问题,有时候会导致使用DOM均持的选项马上正确显示。

4 表单序列化

在JavaScript中,可以利用表单字段的type属性,连同name和value属性一起实现对表单的序列化。在编写代码之前,有必须先搞清楚在表单提交期间,浏览器是怎样将数据发送给服务器的。

  • 对表单字段的名称和值进行URL编码,使用和号(&)分隔。
  • 不发送禁用的表单字段。
  • 只发送钩选的复选框和单选按钮。
  • 不发送type为”reset”和”button”的按钮。
  • 多选选择框中的每个选中的值单独一个条目。
  • 在单击提交按钮提交表单的情况下,也会发送提交按钮;否则,不发送提交按钮。也包括type为”image”的<input>元素。
  • <select>元素的值,就是选中的<option>元素的value特性的值。如果<Option>元素没有value特性, 则是<option>元素的文本值。

在表单序列化过程中,一般不包含任何按钮字段,因为结果字符串很可能是通过其他方式提交的。
除此之外的其他上述规则都应该遵循。以下就是实现表单序列化的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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: //fieldset
case "file": //file input
case "submit": //submit button
case "reset": //reset button
case "button": //custom button
break;

case "radio": //radio button
case "checkbox": //checkbox
if (!field.checked){
break;
}
/* falls through */

default:
//don't include form fields without names
if (field.name.length){
parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));
}
}
}
return parts.join("&");
}

5 富文本编辑

(本篇不对富文本编辑做详细介绍)

富文本编辑,又称为WYSIWYG(What You See Is What You Get,所见即所得)。
这一技术的本质,就是在页面中嵌入一个包含空HTML页面的iframe。通过设置designMode属性,这个空白的HTML页面可以被编辑,而编辑对象则是该页面<body>元素的HTML代码。designMode属性有两个可能是值:”off”(默认值)和”on”。在设置为”on”时,整个文档都会变得可以编辑(显示插入符号),然后就可以像使用字处理软件一样,通过键盘将文本内容加粗、变成斜体,等等。
可以给iframe指定一个非常简单的HTML页面作为其内容来源。例如:

1
2
3
4
5
<!doctype html>
<html>
<head></head>
<body></body>
</html>

这个页面在iframe中可以像其他页面一样被加载。要让它可以编辑,必须要将designMode设置为”on”,但只有在页面完全加载之后才能设置这个属性。因此,在包含页面中,需要使用onload事件处理程序来在恰当的时刻设置designMode。如

1
2
3
4
5
6
7
<iframe name="richedit" style="height: 100px;width: 100px;" src="blank.html"></iframe> 

<script type="text/javascript">
EventUtil.addHandler(window, 'load', function () {
frames['richedit'].document.designMode = 'on';
});
</script>

等到以上代码执行之后,你就会在页面中看到一个类似文本框的可编辑区字段。这个区字段具有与其他网页相同的默认样式;不过,通过为空白页面应用css样式,可以修改可编辑区字段的外观。

5.1 使用contenteditable属性

另一种编辑富文本内容的方式是使用名为contenteditable的特殊属性,这个属性也是由IE最早实现的。可以把contenteditable属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。这种方法之所以受到欢迎,是因为它不需要iframe、空白页和JavaScript,只要为元素设置contenteditable属性即可。

1
<div class="editable" id="richedit" contenteditable></div>

这样,元素中包含的任何文本内容就都可以编辑了,就好像这个元素变成了<textarea>元素一样。
通过在这个元素上设置contenteditable属性,也能打开或关闭编辑模式。

1
2
var div = document.getElementById('richedit');
richedit.contentEditable = 'true';

contenteditable属性有三个可能的值:”true”表示打开、”false”表示关闭,”inherit”表示从父元素那里继承(因为可以在contenteditable元素中创建或删除元索)。支持contenteditable属性的元素有IE、Firefox、Chrome、Safari和Opera。在移动设备上.支持contenteditable属性的浏览器有iOS5+中的Safari和Android3+中的WebKit。

5.2 操作富文本

与富文本编辑器交互的主要方式,就是使用document.execCommand()。这个方法可以对文档执行预定义的命令,而且可以应用大多数格式。可以为document.execCommand()方法传递3个参数:要执行的命令名称、表示浏览器是否应该为当前命令提供用户界面的-个布尔值和执行命令必须的一个值(如果不需要值,则传递null)。为了确保跨浏览器的兼容性,第二个参数应该始终设置为false,因为Firefox会在该参数为true时抛出错误。
不同浏览器支持的预定义命令也不一样。下表列出了那些被支持最多的命令。

具体命令参数可见mdn-execCommand

5.3 富文本选区

在富文本编辑器中,使用框架(iframe)的getSelection()方法,可以确定实际选择的文本。这个方法是window对象和document对象的属性,调用它会返回一个表示当前选择文本的Selection对象。每个Selection对象都有下列属性。

mdn-Selection
mdn-getSelection

温习:

  • 提交表单、重置表单及表单相关方法、属性、事件;
  • 选择、粘贴文本的相关事件;
  • 表单验证、序列化
  • 简单了解富文本编辑

(完)