《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
2let 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) => { |