《JavaScript高级程序设计》第十四章,表单脚本。
基本行为
可能要先说一下<form>这个元素了,它对应的JavaScript对象类型是HTMLFormElement,这种类型有下面这么些属性方法。
- acceptCharset:服务器能够处理的字符集,同HTML里的- accept-charset
- enctype:请求的编码类型,同HTML里的- enctype
- action:接收请求的URL,同HTML里的- action
- method:发送的请求类型(- POST、- GET等),同HTML里的- method
- name:表单名称,同HTML里的- name
- elements:表单元素的集合
- length:表单里元素的数量
- reset():重置表单
- submit():提交表单
获取表单
可以常规地根据id获取表单,也可以用document.forms[index/"name"]来获取表单。
提交表单
将type设置为"submit"的<input>/<button>在被点击/回车时会提交表单,此时会先触发submit事件、再将其发往服务器。借用submit的事件处理函数,我们可以对表单数据进行校验,以决定是否允许表单提交,不允许则event.preventDefault()。不能用onclick,因为click和submit的顺序在不同浏览器之间有区别。
也可以在代码中使用form.submit()来提交表单,但这不会触发submit事件。
提交表单要注意的就是重复提交了,解决办法有:按钮点击一次即置灰、在onsubmit里取消后续的提交操作。
重置表单
将type设置为"rest"的<input>/<button>在被点击时会重置表单,此时会先触发reset事件、再将各字段重置(或为空或为默认值)。也可以借用reset的事件处理函数去取消默认行为。
也可以在代码中使用form.reset()来重置表单,这里就会触发reset事件了。
表单字段
使用form.elements[index/"name"]来获取表单里的元素,如果用name来获取且同name的元素有多个,那就会返回一个NodeList。
表单里的字段无非是些输入框、单选框、复选框等等,那么就很容易想到它们会有哪些属性了,列举如下:form(指向所属表单的指针)、name、value、type、tabIndex、disabled、readOnly、autofocus(自动聚焦),这些属性除了第一个外都是可以改写的。
思考一下,也很容易想到它们大概拥有的方法:focus()、blur()。
这些元素也会和一些事件相关,结合上面可以想到有focus、blur事件,而除了这俩还有一个change事件,表示元素的value变化了。
下面就来关注一下表单中常见的元素。
文本框
表示文本框
| 1 | <!-- ⬇必需 ⬇可显示的字符数 ⬇可接受的字符数 --> | 
选择文本
可以使用select()方法选择文本框中的所有文本,一般的使用场景为文本框获得焦点后全选,这样方便用户去删除或复制。
| 1 | textbox.addEventListener("focus", (e) => { | 
与select()方法对应的是select事件,这个事件会在选择文本框的文本时触发,不过具体时序因浏览器而异。
选择好文本之后,可以通过元素的selectionStart和selectionEnd来明确选中的文本起始和结束处的偏移量,可以用textbox.value.substring(textbox.selectionStart, textbox.selectionEnd)来获取内容。
还可以使用setSelectionRange()方法来选择部分文本,接收两个参数:起始字符下标、结束字符下标后一个。
屏蔽字符
字符输入是keypress的结果,因此可以在其事件处理函数中屏蔽特定字符的输入,下面是个只允许输入数值的例子。
| 1 | textbox.addEventListener("keypress", (e) => { | 
操作剪贴板
有六个剪贴板事件:beforecopy、copy、beforecut、cut、beforepaste、paste。不带before的事件在剪贴板进行操作时发生,带before的事件则通常用于向剪贴板发送数据、或者从剪贴板取得数据。
可以通过clipboardData属性拿到剪贴板数据。不过不同浏览器有区别,比如挂到不同的对象上、可访问性不一样等,为了兼容最好只在发生剪贴板事件时使用此对象。这个clipboardData对象有三个方法:getData(type)、setData(type, value)、clearData()。这里的type都表示数据格式,在IE中有"text"和"URL",在Firefox/Safari/Chrome中有"text/plain"。
自动切换焦点
一般的情景是,用户填完既定长度的数据后,表明这个字段填完了,随即自动切换焦点。下面举个这样的例子:
| 1 | (function() { | 
HTML5约束验证API
一种自动验证的约束,包括:
- 对含有required的必填约束
- 类型为"email"、"URL"的内容匹配约束
- 类型为"number"、"range"、"datetime"、"datetime-local"、"date"、"month"、"week"、"time"时,指定的min、max、step等约束
- 含有pattern的正则匹配约束(如在HTML中加上pattern="\d+")
- 对含有noValidate的表单/含有formnovalidate的字段不需验证约束(当我不需要约束时,这种不约束也算一种约束hh)
根据上面的这些约束,可以定义字段是否「有效」。下面是判断有效性的两种方法:
- checkValidity()方法
| 1 | // 用于单个字段 | 
- validity属性
 它会告诉我们为什么字段有效或无效。这个对象包含一系列属性,每个属性都是布尔值:- customError:如果设置了- setCustomValidity()则为- true,否则为- false
- patternMismatch:值与- pattern不匹配
- rangeOverflow:值比- max大
- rangeUnderflow:值比- min小
- stepMismatch:- min和- max之间步长设置不合理
- tooLong:值长度超过- maxLength
- typeMismatch:值不是- "mail"或- "url"要求的格式
- valid:当其他属性都是- false时为- true,否则为- false
- valueMissing:- required的值无内容
 
| 1 | if(input.validity && !input.validity.valid) { | 
选择框
选择框由<select>和<option>组成。
对于<select>,有几个属性:value、multiple、options、size(选择框中可见的行数)、selectedIndex。
对于<option>,有几个属性:index、label、text、value、selected。
获取选项
可以用options获取所有选项,用selectedIndex获取当前选中项的下标(即使是多选,这个属性也只有一个下标,值为所有选中项中第一个的下标),用value获取这个选择框的值。
value的内容有这么些规则:
- 如果没有选中的项,则value为""
- 如果有一个选中项,且该项value已在HTML中指定,则value为该项的value
- 如果有一个选中项,且该项value未在HTML中指定,则value为该项的文本
- 如果由多个选中项,则value为根据前面两条规则获取的第一个选中项的值
举个例子如下,当选第一项,值为"a";当选第四项,值为"";当选第五项,值为"E"。
| 1 | <select name="location"> | 
选择选项
可以指定selectedIndex来选择选项(对于多选,每设置一次都会取消之前的选项并选择这次的选项),也可以将选项的selected设置为true来选择选项(对于多选,这回是真的可以多选了)。
添加选项
- DOM操作1 
 2
 3
 4
 5
 6
 7
 8
 9// 使用createElement创建 
 let newOption = document.createElement("option");
 newOption.appendChild(document.createTextNode("Option text"));
 newOption.setAttribute("value", "Option value");
 selectbox.appendChild(newOption);
 // 使用`Option`构造函数来创建
 let newOption = new Option("Option text", "Option value");
 selectbox.appendChild(newOption);
- add()方法
 此方法接收两个参数:要添加的选项、将位于新选项之后的选项。IE中第二个参数可选,且该内容为索引;DOM中第二个参数则为必填项。- 1 
 2- let newOption = new Option("Option text", "Option value"); 
 selectbox.add(newOption, undefined);
移除选项
- DOM操作 - 1 - selectbox.removeChild(selectbox.options[0]); 
- remove()方法
 此方法接收一个参数:要移除选项的索引。- 1 - selectbox.remove(0); 
- 置空 
 还可以将相应选项设置为- null来移除选项。- 1 - selectbox.options[0] = null; 
要注意的是,移除选项后会自动重置每一个选项的index属性。
移动选项
就要借助DOM操作啦,举几个例子。
| 1 | // 把第一个选择框的某选项直接移到第二个选择框处 | 
移动选项也会自动重置每一个选项的index属性。
表单序列化
有一些原则,比如:
- 各个字段名称和值进行URL编码,用&分隔
- 不发送禁用的字段、类型为"reset"和"button"的按钮
- 只发送勾选的复选框和单选框
- 复选框中,每个选中的值单独一个条目
- 单选框中,选项有value则为该值,无则为文本值
| 1 | function serialize(form) { | 
富文本编辑
据说又叫「所见即所得」,其实编辑邮件正文那块儿就是一种。
创建区域
- 使用 - iframe
 一种方法是,在页面中嵌入一个包含空HTML的- iframe,通过将它的- designMode设置为- "on"(默认为- "off"),这个空白HTML页面可以被编辑(显示插入符号),而编辑对象则是该页面- <body>元素的HTML代码。要注意的是,只有在页面完全加载之后才能设置- designMode,因此可以把它写在- onload里。
- 使用 - contenteditable
 还有一种方式,在元素的HTML中设置- contenteditable。这样,元素包含的任何文本内容都可以编辑了,就好像这个元素变成了- <textarea>(试了一下,确实会出现插入符号,也可以输入内容)。- contenteditable有三个值:- "true"、- "false"、- "inherit"。
操作富文本
与富文本编辑器交互的主要方式,就是使用frame["x"].document.execCommand()或document.execCommand()。它可以对文档执行预定义的命令,接收三个参数:命令名称、表示浏览器是否应该为当前命令提供用户界面的一个布尔值、执行命令必须的一个值(如不需要值则传null)。下面列举一些命令及相应的第三个参数(没写的则是不需要参数的null):
- backcolor:参数为颜色字符串,设置文档背景色
- bold:将选择的文本加粗
- italic:将选择的文本转为斜体
- underline:将选择的文本添加下划线
- copy:将选择的文本复制到剪贴板
- paste:将剪贴板文本粘贴到选择的文本
- cut:将选择的文本剪切到剪贴板
- createLink:参数为URL字符串,将选择的文本转换成链接,指向特定URL
- unlink:移除文本链接,撤销createlink
- indent:缩进文本
- outdent:凸排文本
- formatblock:参数为要包围当前文本块的HTML标签(如<h1>),用给定的标签格式化选择的文本
- removeformat:删除格式,撤销formatblock
- fontname:参数为字体名称,给选择的文本指定字体
- fontsize:参数为1~7,给选择的文本指定字号
- fontcolor:参数为颜色字符串,给选择的文本指定颜色
- inserthorizontalrule:在插入字符处插入一个<hr>元素
- insertimage:参数为图片URL,在插入字符处插入一个图像
- insertorderlist:在插入字符处插入一个<ol>元素
- insertunorderlist:在插入字符处插入一个<ul>元素
- insertparagraph:在插入字符处插入一个<p>元素
- justifycenter:文本块居中对齐
- justifyleft:文本块居左对齐
- selectall:选择所有文本
- delete:删除选择的文本
queryCommandEnabled()方法,可以用来检测针对当前选择的文本或当前插入字符所在位置,是否适合执行某个命令,会返回表示是否合适的布尔值。如下代码,可以看看能否对当前选择的文本执行bold:
| 1 | const result = frames["richedit"].document.queryCommandEnabled("bold"); | 
queryCommandState()方法,可以用来检测是否已将指定指令应用到了选择的文本,也是返回布尔值。如下代码,可以看看当前选择的文本是否已经转换为粗体:
| 1 | const isBold = frames["richedit"].document.queryCommandState("bold"); | 
queryCommandValue()方法,可以取得执行命令时传入的参数(即execCommand()的第三个参数)。如下代码,如果文本在应用fontsize命令时传入了7,那就会返回"7":
| 1 | const fontSize = frames["richedit"].document.queryCommandValue("fontsize"); | 
富文本选区
也就是为了更精确地控制选中的内容。用window或document对象的getSelection()方法可以确定实际选择的文本,它会返回一个Selection对象。每个Selection对象都有下面的属性和方法。
属性:
- anchorNode:选区起点所在节点
- anchorOffset:- anchorNode中排除在选区之外的字符数量(也就是从最开始到选区起点经过的字符数量)
- focusNode:选区终点所在节点
- focusOffset:- focusNode中包含在选区之内的字符数量
- isCollapsed:选区起点和终点是否重合
- rangeCount:选区包含的DOM范围的数量
方法:
- addRange(range):将指定的DOM范围添加到选区中
- removeRange(range):从选区中移除指定的DOM范围
- removeAllRanges():从选区中移除所有DOM范围,这样会移除选区(因为选区至少要有一个范围)
- getRangeAt(index):返回索引对应的选区的DOM范围
- deleteFromDocument():从文档中删除选区中的文本,同- document.execCommand("delete", false, null)
- collapse(node, offset):将选区折叠到指定节点中的相应的文本偏移位置
- collapseToEnd():将选区折叠到终点位置
- collapseToStart():将选区折叠到起点位置
- containsNode(node):指定节点是否包含在选区中
- extend(node, offset):通过移动- focusNode和- focusOffset扩展选区
- selectAllChildren(node):清除选区并选择指定节点的所有子节点
- toString():返回选区包含的文本内容
举一个操作的例子,这里给富文本编辑器中被选择的文本添加黄色的背景:
| 1 | const selection = frames["richedit"].getSelection(); | 
* 表单与富文本
在严格意义上来说,富文本编辑器不属于表单,因此其中的内容不会被自动提交给服务器,因此需要手动提取并提交。通常可以添加一个隐藏的表单字段,让它的值等于从iframe中/contenteditable元素中的HTML,再提交给服务器。
| 1 | form.addEventListener("submit", (e) => { |