第 19 章 表单脚本
19.1 表单基础
Web 表单在 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>
元素的引用。
let form = document.getElementById('form1')
// 取得页面中的第一个表单
let firstForm = document.forms[0]
// 取得名字为"form2"的表单
let myForm = document.forms['form2']
表单可以同时拥有 id
和 name
,而且两者可以不相同。
19.1.1 提交表单
<!-- 通用提交按钮 -->
<input type="submit" value="Submit Form" />
<!-- 自定义提交按钮 -->
<button type="submit">Submit Form</button>
<!-- 图片按钮 -->
<input type="image" src="graphic.gif" />
<script>
let form = document.getElementById('myForm')
//提交表单
form.submit()
form.addEventListener('submit', (event) => {
// 阻止表单提交
event.preventDefault()
})
</script>
如果表单中有上述任何一个按钮,且焦点在表单中某个控件上,则按回车键也可以提交表单。(textarea 控件是个例外,当焦点在它上面时,按回车键会换行。)
通过 submit()
提交表单时,submit
事件不会触发。因此在调用这个方法前要先做数据验证。
19.1.2 重置表单
<!-- 通用重置按钮 -->
<input type="reset" value="Reset Form" />
<!-- 自定义重置按钮 -->
<button type="reset">Reset Form</button>
<script>
let form = document.getElementById('myForm')
//重置表单
form.reset()
form.addEventListener('reset', (event) => {
event.preventDefault()
})
</script>
与 submit()
方法的功能不同,调用 reset()
方法会像单击了重置按钮一样触发 reset
事件。
19.1.3 表单字段
let form = document.getElementById('form1')
// 取得表单中的第一个字段
let field1 = form.elements[0]
// 取得表单中名为"textbox1"的字段
let field2 = form.elements['textbox1']
// 取得字段的数量
let fieldCount = form.elements.length
如果多个表单控件使用了同一个 name
,比如像单选按钮那样,则会返回包含所有同名元素的 HTMLCollection
。
<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>
<script>
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
</script>
1.表单字段的公共属性
❑ disabled:布尔值,表示表单字段是否禁用。
❑ form:指针,指向表单字段所属的表单。这个属性是只读的。
❑ name:字符串,这个字段的名字。
❑ readOnly:布尔值,表示这个字段是否只读。
❑ tabIndex:数值,表示这个字段在按 Tab 键时的切换顺序。
❑ type:字符串,表示字段类型,如"checkbox"、"radio"等。
❑ value:要提交给服务器的字段值。对文件输入字段来说,这个属性是只读的,仅包含计算机上某个文件的路径。
2.表单字段的公共方法
每个表单字段都有两个公共方法:focus()
和 blur()
。focus()
方法把浏览器焦点设置到表单字段,这意味着该字段会变成活动字段并可以响应键盘事件,主要用来引起用户对页面中某个部分的注意。
HTML5 为表单字段增加了 autofocus
属性,支持的浏览器会自动为带有该属性的元素设置焦点,而无须使用 JavaScript。
<input type="text" autofocus />
<script>
window.addEventListener("load", (event) => {
let element = document.forms[0].elements[0];
if (element.autofocus ! == true) {
element.focus();
console.log("JS focus");
}
});
</script>
注意
默认情况下只能给表单元素设置焦点。不过,通过将 tabIndex
属性设置为 -1 再调用 focus()
,也可以给任意元素设置焦点。只有 Opera 不支持这个技术。
blur()
用于从元素上移除焦点。调用 blur()
时,焦点不会转移到任何特定元素,仅仅只是从调用这个方法的元素上移除了。
3.表单字段的公共事件
❑ blur:在字段失去焦点时触发。
❑ change:在<input>
和<textarea>
元素的 value 发生变化且失去焦点时触发,或者在<select>
元素中选中项发生变化时触发。
❑ focus:在字段获得焦点时触发。
blur
和 focus
事件会因为用户手动改变字段焦点或者调用 blur()
或 focus()
方法而触发。这两个事件对所有表单都会一视同仁。change
事件则不然,它会因控件不同而在不同时机触发。对于<input>
和<textarea>
元素,change
事件会在字段失去焦点,同时 value
自控件获得焦点后发生变化时触发。对于<select>
元素,change
事件会在用户改变了选中项时触发,不需要控件失去焦点。
19.2 文本框编程
在 HTML 中有两种表示文本框的方式:单行使用<input>
元素,多行使用<textarea>
元素。
<input>
元素显示为文本框,省略 type
属性会以"text"作为默认值。然后可以通过 size
属性指定文本框的宽度,这个宽度是以字符数来计量的。而 value
属性用于指定文本框的初始值,maxLength
属性用于指定文本框允许的最多字符数。因此要创建一个一次可显示 25 个字符,但最多允许显示 50 个字符的文本框。
<textarea>
元素总是会创建多行文本框。可以使用 rows
属性指定这个文本框的高度,以字符数计量;以 cols
属性指定以字符数计量的文本框宽度,类似于<input>
元素的 size
属性。与<input>
不同的是,<textarea>
的初始值必须包含在<textarea>
和</textarea>
之间。
<input type="text" size="25" maxlength="50" value="initial value" />
<textarea rows="25" cols="5">initial value</textarea>
同样与<input>
元素不同的是,<textarea>
不能在 HTML 中指定最大允许的字符数。
除了标记中的不同,这两种类型的文本框都会在 value
属性中保存自己的内容。通过这个属性,可以读取也可以设置文本模式的值。
let textbox = document.forms[0].elements['textbox1']
console.log(textbox.value)
textbox.value = 'Some new value'
19.2.1 选择文本
两种文本框都支持一个名为 select()
的方法,此方法用于全部选中文本框中的文本。大多数浏览器会在调用 select()
方法后自动将焦点设置到文本框(Opera 例外)。这个方法不接收参数,可以在任何时候调用。
textbox.addEventListener('focus', (event) => {
event.target.select()
})
1.select 事件
与 select()
方法相对,还有一个 select
事件。当选中文本框中的文本时,会触发 select
事件。这个事件确切的触发时机因浏览器而异。在 IE9+、Opera、Firefox、Chrome 和 Safari 中,select
事件会在用户选择完文本后立即触发;在 IE8 及更早版本中,则会在第一个字符被选中时触发。另外,调用 select()
方法也会触发 select
事件。
textbox.addEventListener('select', (event) => {
console.log(`Text selected: ${textbox.value}`)
})
2.取得选中文本
虽然 select
事件能够表明有文本被选中,但不能提供选中了哪些文本的信息。HTML5 对此进行了扩展,以方便更好地获取选中的文本。扩展为文本框添加了两个属性:selectionStart
和 selectionEnd
。这两个属性包含基于 0 的数值,分别表示文本选区的起点和终点(文本选区起点的偏移量和文本选区终点的偏移量)。
function getSelectedText(textbox) {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd)
}
这个扩展在 IE9+、Firefox、Safari、Chrome 和 Opera 中都可以使用。IE8 及更早版本不支持这两个属性,因此需要使用其他方式。
function getSelectedText(textbox) {
if (typeoftextbox.selectionStart == 'number') {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd)
} else if (document.selection) {
return document.selection.createRange().text
}
}
3.部分选中文本
setSelectionRange()
方法也可以在所有文本框中使用。这个方法接收两个参数:要选择的第一个字符的索引和停止选择的字符的索引(与字符串的 substring()
方法一样)。
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"
如果想看到选择,则必须在调用 setSelectionRange()
之前或之后给文本框设置焦点。这个方法在 IE9、Firefox、Safari、Chrome 和 Opera 中都可以使用。
textbox.value = 'Hello world! '
var range = textbox.createTextRange()
// 选择所有文本
range.collapse(true)
range.moveStart('character', 0)
range.moveEnd('character', textbox.value.length) // "Hello world! "
range.select()
// 选择前3 个字符
range.collapse(true)
range.moveStart('character', 0)
range.moveEnd('character', 3)
range.select() // "Hel"
// 选择第4~6 个字符
range.collapse(true)
range.moveStart('character', 4)
range.moveEnd('character', 6)
range.select() // "o w"
19.2.2 输入过滤
1.屏蔽字符
textbox.addEventListener('keypress', (event) => {
if (!/\d/.test(String.fromCharCode(event.charCode)) && event.charCode > 9 && !event.ctrlKey) {
event.preventDefault()
}
})
2.处理剪贴板
❑ beforecopy:复制操作发生前触发。
❑ copy:复制操作发生时触发。
❑ beforecut:剪切操作发生前触发。
❑ cut:剪切操作发生时触发。
❑ beforepaste:粘贴操作发生前触发。
❑ paste:粘贴操作发生时触发。
剪贴板上的数据可以通过 window
对象(IE)或 event
对象(Firefox、Safari 和 Chrome)上的 clipboardData
对象来获取。
clipboardData
对象上有 3 个方法:getData()
、setData()
和 clearData()
,其中 getData()
方法从剪贴板检索字符串数据,并接收一个参数,该参数是要检索的数据的格式。IE 为此规定了两个选项:"text
"和"URL
"。Firefox、Safari 和 Chrome 则期待 MIME 类型,不过会将"text
"视为等价于"text/plain
"。
setData()
方法也类似,其第一个参数用于指定数据类型,第二个参数是要放到剪贴板上的文本。同样,IE 支持"text
"和"URL
", Safari 和 Chrome 则期待 MIME 类型。不过,与 getData()
不同的是,Safari 和 Chrome 不认可"text
"类型。只有在 IE8 及更早版本中调用 setData()
才有效,其他浏览器会忽略对这个方法的调用。
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)
}
}
19.2.3 自动切换
<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" />
<script>
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')
</script>
19.2.4 HTML5 约束验证 API
1.必填字段
<input type="text" name="username" required />
任何带有 required
属性的字段都必须有值,否则无法提交表单。这个属性适用于<input>
、<textarea>
和<select>
字段。
可以通过 JavaScript 检测对应元素的 required
属性来判断表单字段是否为必填:
let isUsernameRequired = document.forms[0].elements['username'].required
还可以使用下面的代码检测浏览器是否支持 required
属性:
let isRequiredSupported = 'required' in document.createElement('input')
注意
不同浏览器处理必填字段的机制不同。Firefox、Chrome、IE 和 Opera 会阻止表单提交并在相应字段下面显示有帮助信息的弹框,而 Safari 什么也不做,也不会阻止提交表单。
2.更多输入类型
<input type="email" name="email" /> <input type="url" name="homepage" />
3.数值范围
除了"email
"和"url
", HTML5 还定义了其他几种新的输入元素类型,它们都是期待某种数值输入的,包括:"number
"、"range
"、"datetime
"、"datetime-local
"、"date
"、"month
"、"week
"和"time
"。
<input type="number" min="0" max="100" step="5" name="count" />
<script>
input.stepUp() // 加1
input.stepUp(5) // 加5
input.stepDown() // 减1
input.stepDown(10) // 减10
</script>
4.输入模式
HTML5 为文本字段新增了 pattern
属性。这个属性用于指定一个正则表达式,用户输入的文本必须与之匹配。
5.检测有效性
使用 checkValidity()
方法可以检测表单中任意给定字段是否有效。这个方法在所有表单元素上都可以使用,如果字段值有效就会返回 true
,否则返回 false
。判断字段是否有效的依据是本节前面提到的约束条件,因此必填字段如果没有值就会被视为无效,而字段值不匹配 pattern
属性也会被视为无效。
if (document.forms[0].elements[0].checkValidity()) {
// 字段有效,继续
} else {
// 字段无效
}
if (document.forms[0].checkValidity()) {
// 表单有效,继续
} else {
// 表单无效
}
checkValidity()
方法只会告诉我们字段是否有效,而 validity
属性会告诉我们字段为什么有效或无效。这个属性是一个对象,包含一系列返回布尔值的属性。
❑ customError:如果设置了 setCustomValidity()就返回 true,否则返回 false。
❑ patternMismatch:如果字段值不匹配指定的 pattern 属性则返回 true。
❑ rangeOverflow:如果字段值大于 max 的值则返回 true。
❑ rangeUnderflow:如果字段值小于 min 的值则返回 true。
❑ stepMisMatch:如果字段值与 min、max 和 step 的值不相符则返回 true。
❑ tooLong:如果字段值的长度超过了 maxlength 属性指定的值则返回 true。某些浏览器,如 Firefox 4 会自动限制字符数量,因此这个属性值始终为 false。
❑ typeMismatch:如果字段值不是"email"或"url"要求的格式则返回 true。
❑ valid:如果其他所有属性的值都为 false 则返回 true。与 checkValidity()的条件一致。
❑ valueMissing:如果字段是必填的但没有值则返回 true。
if (input.validity && !input.validity.valid) {
if (input.validity.valueMissing) {
console.log('Please specify a value.')
} else if (input.validity.typeMismatch) {
console.log('Please enter an email address.')
} else {
console.log('Value is invalid.')
}
}
6.禁用验证
通过指定 novalidate
属性可以禁止对表单进行任何验证:
<form method="post" action="/signup" novalidate>
<!-- 表单元素 -->
</form>
<script>
document.forms[0].noValidate = true // 关闭验证
</script>
如果一个表单中有多个提交按钮,那么可以给特定的提交按钮添加 formnovalidate
属性,指定通过该按钮无须验证即可提交表单:
<form method="post" action="/foo">
<!-- 表单元素 -->
<input type="submit" value="Regular Submit" />
<input type="submit" formnovalidate name="btnNoValidate" value="Non-validating Submit" />
</form>
<script>
// 关闭验证
document.forms[0].elements['btnNoValidate'].formNoValidate = true
</script>
19.3 选择框编程
选择框是使用<select>
和<option>
元素创建的。为方便交互,HTMLSelectElement
类型在所有表单字段的公共能力之外又提供了以下属性和方法。
❑ add(newOption, relOption):在 relOption 之前向控件中添加新的<option>
。
❑ multiple:布尔值,表示是否允许多选,等价于 HTML 的 multiple 属性。
❑ options:控件中所有<option>
元素的 HTMLCollection。
❑ remove(index):移除给定位置的选项。
❑ selectedIndex:选中项基于 0 的索引值,如果没有选中项则为-1。对于允许多选的列表,始终是第一个选项的索引。
❑ size:选择框中可见的行数,等价于 HTML 的 size 属性。
选择框的 type 属性可能是"select-one"或"select-multiple",具体取决于 multiple
属性是否存在。当前选中项根据以下规则决定选择框的 value
属性。
❑ 如果没有选中项,则选择框的值是空字符串。
❑ 如果有一个选中项,且其 value 属性有值,则选择框的值就是选中项 value 属性的值。即使 value 属性的值是空字符串也是如此。
❑ 如果有一个选中项,且其 value 属性没有指定值,则选择框的值是该项的文本内容。
❑ 如果有多个选中项,则选择框的值根据前两条规则取得第一个选中项的值。
每个<option>
元素在 DOM 中都由一个 HTMLOptionElement
对象表示。HTMLOptionElement
类型为方便数据存取添加了以下属性。
❑ index:选项在 options 集合中的索引。
❑ label:选项的标签,等价于 HTML 的 label 属性。
❑ selected:布尔值,表示是否选中了当前选项。把这个属性设置为 true 会选中当前选项。
❑ text:选项的文本。
❑ value:选项的值(等价于 HTML 的 value 属性)。
选择框的 change
事件与其他表单字段是不一样的。其他表单字段会在自己的值改变后触发 change
事件,然后字段失去焦点。而选择框会在选中一项时立即触发 change
事件。
19.3.1 选项处理
对于只允许选择一项的选择框,获取选项最简单的方式是使用选择框的 selectedIndex
属性
let selectedIndex = selectbox.selectedIndex
let selectedOption = selectbox.options[selectedIndex]
对于允许多选的选择框,selectedIndex
属性就像只允许选择一项一样。设置 selectedIndex
会移除所有选项,只选择指定的项,而获取 selectedIndex
只会返回选中的第一项的索引。
选项还可以通过取得选项的引用并将其 selected
属性设置为 true
来选中。
selectbox.options[0].selected = true
与 selectedIndex
不同,设置选项的 selected
属性不会在多选时移除其他选项,从而可以动态选择任意多个选项。如果修改单选框中选项的 selected
属性,则其他选项会被移除。要注意的是,把 selected
属性设置为 false
对单选框没有影响。
19.3.2 添加选项
可以使用 JavaScript 动态创建选项并将它们添加到选择框。首先,可以使用 DOM 方法
let newOption = document.createElement('option')
newOption.appendChild(document.createTextNode('Option text'))
newOption.setAttribute('value', 'Option value')
selectbox.appendChild(newOption)
另外,也可以使用 Option
构造函数创建新选项,这个构造函数是 DOM
出现之前就已经得到浏览器支持的。Option
构造函数接收两个参数:text
和 value
,其中 value
是可选的。虽然这个构造函数通常会创建 Object
的实例,但 DOM
合规的浏览器都会返回一个<option>
元素。这意味着仍然可以使用 appendChild()
方法把这样创建的选项添加到选择框。
let newOption = new Option('Option text', 'Option value')
selectbox.appendChild(newOption) // 在IE8 及更低版本中有问题
另一种添加新选项的方式是使用选择框的 add()
方法。DOM
规定这个方法接收两个参数:要添加的新选项和要添加到其前面的参考选项。如果想在列表末尾添加选项,那么第二个参数应该是 null
。
let newOption = new Option('Option text', 'Option value')
selectbox.add(newOption, undefined) // 最佳方案
19.3.3 移除选项
第一种方式是使用 DOM
的 removeChild()
方法并传入要移除的选项。
selectbox.removeChild(selectbox.options[0]) // 移除第一项
第二种方式是使用选择框的 remove()
方法。这个方法接收一个参数,即要移除选项的索引。
selectbox.remove(0) // 移除第一项
最后一种方式是直接将选项设置为等于 null
。这同样也是 DOM
之前浏览器实现的方式。
selectbox.options[0] = null // 移除第一项
19.3.4 移动和重排选项
在 DOM
之前,从一个选择框向另一个选择框移动选项是非常麻烦的,要先从第一个选择框移除选项,然后以相同文本和值创建新选项,再将新选项添加到第二个选择框。DOM
方法则可以直接将某个选项从第一个选择框移动到第二个选择框,只要对相应选项使用 appendChild()
方法即可。如果给这个方法传入文档中已有的元素,则该元素会先从其父元素中移除,然后再插入指定位置。
let selectbox1 = document.getElementById('selLocations1')
let selectbox2 = document.getElementById('selLocations2')
selectbox2.appendChild(selectbox1.options[0])
移动选项和移除选项都会导致每个选项的 index
属性重置。
重排选项非常类似,DOM
方法同样是最佳途径。要将选项移动到选择框中的特定位置,insertBefore()
方法是最合适的。不过,要把选项移动到最后,还是 appendChild()
方法比较方便。
// 将一个选项在选择框中前移一个位置
let optionToMove = selectbox.options[1]
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index - 1])
// 将选项向下移动一个位置
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index + 2])
19.4 表单序列化
❑ 字段名和值是 URL 编码的并以和号(&)分隔。
❑ 禁用字段不会发送。
❑ 复选框或单选按钮只在被选中时才发送。
❑ 类型为"reset"或"button"的按钮不会发送。
❑ 多选字段的每个选中项都有一个值。
❑ 通过点击提交按钮提交表单时,会发送该提交按钮;否则,不会发送提交按钮。类型为"image"的<input>
元素视同提交按钮。
❑ <select>
元素的值是被选中<option>
元素的 value 属性。如果<option>
元素没有 value 属性,则该值是它的文本。
function serialize(form) {
let parts = []
let optValue
for (let field of form.elements) {
switch (field.type) {
case 'select-one':
case 'select-multiple':
if (field.name.length) {
for (let option of field.options) {
if (option.selected) {
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(field.value)}')
}
}
}
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('&')
}
19.5 富文本编辑
富文本编辑也就是所谓的“所见即所得”(WYSIWYG, What You See Is What You Get)编辑。基本的技术就是在空白 HTML 文件中嵌入一个 iframe。通过 designMode
属性,可以将这个空白文档变成可以编辑的,实际编辑的则是<body>
元素的 HTML。designMode
属性有两个可能的值:"off
"(默认值)和"on
"。设置为"on
"时,整个文档都会变成可以编辑的(显示插入光标),从而可以像使用文字处理程序一样编辑文本,通过键盘将文本标记为粗体、斜体,等等。
<!DOCTYPE html>
<html>
<head>
<title>Blank Page for Rich Text Editing</title>
</head>
<body>
<iframe name="richedit" style="height: 100px; width: 100px"></iframe>
<script>
window.addEventListener('load', () => {
frames['richedit'].document.designMode = 'on'
})
</script>
</body>
</html>
19.5.1 使用 contenteditable
可以给页面中的任何元素指定 contenteditable
属性,然后该元素会立即被用户编辑。
contentEditable
属性有 3 个可能的值:"true
"表示开启,"false
"表示关闭,"inherit
"表示继承父元素的设置(因为在 contenteditable
元素内部会创建和删除元素)。
19.5.2 与富文本交互
与富文本编辑器交互的主要方法是使用 document.execCommand()
。这个方法在文档上执行既定的命令,可以实现大多数格式化任务。document.execCommand()
可以接收 3 个参数:要执行的命令、表示浏览器是否为命令提供用户界面的布尔值和执行命令必需的值(如果不需要则为 null
)。为跨浏览器兼容,第二个参数应该始终为 false
,因为 Firefox 会在其为 true 时抛出错误。
命令 | 值(第三个参数) | 说明 |
---|---|---|
backcolor | 颜色字符串 | 设置文档背景颜色 |
bold | null | 切换选中文本的粗体样式 |
copy | null | 将选中文本复制到剪切板 |
createlink | URL 字符串 | 将当前选中文本转换为指向给定 URL 的链接 |
cut | null | 将选中文本剪切到剪切板 |
delete | null | 删除当前选中的文本 |
fontname | 字体名 | 将选中的文本改为使用指定字体 |
fonsize | 1~7 | 将选中文本改为指定字体大小 |
forecolor | 颜色字符串 | 将选中文本改为指定颜色 |
formatblock | HTML 标签,如 <h1> | 将选中文本包含在指定的 HTML 标签中 |
indent | null | 缩进文本 |
inserthorizontalrule | null | 在光标位置插入 <hr> 元素 |
insertimage | 图片 URL | 在光标位置插入图片 |
insertorderedlist | null | 在光标位置插入 <ol> 元素 |
insertoparagraph | null | 在光标位置插入 <p> 元素 |
insertunorderedlist | null | 在光标位置插入 <ul> 元素 |
italic | null | 切换选中文本的斜体样式 |
justifycenter | null | 在光标位置居中文本块 |
justifyleft | null | 在光标位置左对齐文本块 |
outdent | null | 减少缩进 |
paste | null | 在选中文本上粘贴剪切板内容 |
removeformat | null | 移除包含光标所在位置块的 HTML 标签。这是 formatblock 的反操作 |
selectall | null | 选中文档中所有文本 |
underline | null | 切换选中文本的下划线样式 |
unlink | null | 移除文本链接。这是 createlink 的反操作 |
// 在内嵌窗格中切换粗体文本样式
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')
// 在内嵌窗格中为内容添加<h1>标签
frames['richedit'].document.execCommand('formatblock', false, '<h1>')
同样的方法也可以用于页面中添加了 contenteditable
属性的元素,只不过要使用当前窗口而不是内嵌窗格中的 document
对象。
// 切换粗体文本样式
document.execCommand('bold', false, null)
// 切换斜体文本样式
document.execCommand('italic', false, null)
// 创建指向www.wrox.com的链接
document.execCommand('createlink', false, 'http://www.wrox.com')
// 为内容添加<h1>标签
document.execCommand('formatblock', false, '<h1>')
queryCommandEnabled()
方法用于确定对当前选中文本或光标所在位置是否可以执行相关命令。它只接收一个参数,即要检查的命令名。如果可编辑区可以执行该命令就返回 true
,否则返回 false
。
let result = frames['richedit'].document.queryCommandEnabled('bold')
queryCommandState()
用于确定相关命令是否应用到了当前文本选区。
let isBold = frames['richedit'].document.queryCommandState('bold')
queryCommandValue()
方法可以返回执行命令时使用的值(即前面示例的 execCommand()
中的第三个参数)。
let fontSize = frames['richedit'].document.queryCommandValue('fontsize')
19.5.3 富文件选择
在内嵌窗格中使用 getSelection()
方法,可以获得富文本编辑器的选区。这个方法暴露在 document
和 window
对象上,返回表示当前选中文本的 Selection
对象。每个 Selection
对象都拥有以下属性。
❑ anchorNode:选区开始的节点。
❑ anchorOffset:在 anchorNode 中,从开头到选区开始跳过的字符数。
❑ focusNode:选区结束的节点。
❑ focusOffset:focusNode 中包含在选区内的字符数。
❑ isCollapsed:布尔值,表示选区起点和终点是否在同一个地方。
❑ rangeCount:选区中包含的 DOM 范围数量。
Selection
的属性并没有包含很多有用的信息。好在它的以下方法提供了更多信息,并允许操作选区。
❑ addRange(range):把给定的 DOM 范围添加到选区。
❑ collapse(node, offset):将选区折叠到给定节点中给定的文本偏移处。
❑ collapseToEnd():将选区折叠到终点。
❑ collapseToStart():将选区折叠到起点。
❑ containsNode(node):确定给定节点是否包含在选区中。
❑ deleteFromDocument():从文档中删除选区文本。与执行 execCommand("delete", false, null)命令结果相同。
❑ extend(node, offset):通过将 focusNode 和 focusOffset 移动到指定值来扩展选区。
❑ getRangeAt(index):返回选区中指定索引处的 DOM 范围。
❑ removeAllRanges():从选区中移除所有 DOM 范围。这实际上会移除选区,因为选区中至少要包含一个范围。
❑ removeRange(range):从选区中移除指定的 DOM 范围。
❑ selectAllChildren(node):清除选区并选择给定节点的所有子节点。
❑ toString():返回选区中的文本内容。
let selection = frames['richedit'].getSelection()
// 取得选中的文本
let selectedText = selection.toString()
// 取得表示选区的范围
let range = selection.getRangeAt(0)
// 高亮选中的文本
let span = frames['richedit'].document.createElement('span')
span.style.backgroundColor = 'yellow'
range.surroundContents(span)
getSelection()
方法在 HTML5 中进行了标准化,IE9 以及 Firefox、Safari、Chrome 和 Opera 的所有现代版本中都实现了这个方法。
IE8 及更早版本不支持 DOM
范围,不过它们允许通过专有的 selection
对象操作选中的文本。如本章前面所讨论的,这个 selection
对象是 document
的属性。要取得富文本编辑器中选中的文本,必须先创建一个文本范围,然后再访问其 text
属性:
let range = frames['richedit'].document.selection.createRange()
let selectedText = range.text
range.pasteHTML('<span style="background-color:yellow">${range.htmlText}</span>')
19.5.4 通过表单提交富文本
// 内嵌窗格实现富文本编辑
form.addEventListener('submit', (event) => {
let target = event.target
target.elements['comments'].value = frames['richedit'].document.body.innerHTML
})
// contenteditable
form.addEventListener('submit', (event) => {
let target = event.target
target.elements['comments'].value = document.getElementById('richedit').innerHTML
})