本章主要介紹:表單、文本框驗(yàn)證與交互、使用其他表單控制。這一章會(huì)繼續(xù)沿用上一章 封裝的 EventUtil 對(duì)象(具體參考前面的事件)
JavaScript最初的一個(gè)應(yīng)用,就是分擔(dān)服務(wù)器處理表單的責(zé)任,打破處處依賴服務(wù)器的局面
一、表單的基礎(chǔ)知識(shí)
JavaScript中,表單對(duì)應(yīng)的是 HTMLFormElement 類型。HTMLFormElement 繼承了 HTMLElement。HTMLFormElement 有自己下列獨(dú)有的屬性和方法
- acceptCharset:服務(wù)器能夠處理的字符集:等價(jià)于HTML中的 accpet-charset 特性
- action:接受請(qǐng)求的URL:等價(jià)于HTML中的 action 特性。
- elements:表單中所有控件的集合(HTMLCollection)
- enctype:請(qǐng)求的編碼類型;等價(jià)于HTML 中的 enctype 特性
- length:表單中控件的數(shù)量
- method:要發(fā)送的HTTP請(qǐng)求類型,通常是“get” 或 “post”;等價(jià)于 HTML 的method特性。
- name:表單的名稱;等價(jià)于HTML 的name 特性
- reset():將所有表單域重置為默認(rèn)值
- submit():提交表單
- target:用于發(fā)送請(qǐng)求和接受響應(yīng)的窗口名稱;等價(jià)于 HTML 的target 特性。
取得<form>元素 引用的方式有好幾種:最常見(jiàn)的方式是 使用 getElementById() 方法找到它
var form = document.getElementById('myForm')
其次,通過(guò)document.forms 獲取頁(yè)面中所有的表單,再通過(guò) 索引 或 name值 來(lái)訪問(wèn)
var firstForm = document.forms[0] // 獲取頁(yè)面中的第一個(gè)表單
var myForm = document.forms['form2'] // 獲取頁(yè)面中 名稱為 “form2” 的表單
在較早的瀏覽器或者那些支持向后兼容的瀏覽器中,也會(huì)把每個(gè)設(shè)置了 name 特性的 表單作為屬性保存在 document 對(duì)象中。通過(guò)document.form2 可以訪問(wèn)到名為 "form2" 的表單。
1.1、提交表單
使用 <input> 或 <button> 都可以定義提交按鈕,只要將其 type 特性的值設(shè)置為 "submit" 即可,而圖像按鈕則是通過(guò)將 <input> 的 type 特性值設(shè)置為 “image” 來(lái)定義
<!-- 通用提交按鈕 -->
<input type="submit" value="Submit Form" />
<!-- 自定義提交按鈕 -->
<button type="submit">Submit Form</button>
<!-- 圖像按鈕 -->
<input type="image" src="graphic.gif" />
以這種方式提交表單時(shí),瀏覽器會(huì)在將請(qǐng)求發(fā)送給服務(wù)器之前觸發(fā) submit 事件。這樣,我們就有機(jī)會(huì)驗(yàn)證表單數(shù)據(jù),并決定是否允許提交表單。
var form = document.getElementById('myForm')
EventUtil.addHandler(form, 'submit', function(event){
// 取得事件對(duì)象
event = EventUtil.getEvent(event)
// if () {
// 阻止默認(rèn)事件
EventUtil.preventDefault(event)
// }
})
在JavaScript中,以編程方式調(diào)用 submit() 方法也可以提交表單。這種方式無(wú)需表單包含提交按鈕。任何時(shí)候都可以正常提交表單。
var from = document.getElementById('myForm')
// 提交表單
form.submit()
調(diào)用 submit() 方法提交表單的形式,不會(huì)觸發(fā) submit事件,因此要記得在調(diào)用此方法之前先驗(yàn)證表單數(shù)據(jù)。
提交表單時(shí)可能出現(xiàn)的最大問(wèn)題,就是重復(fù)提交表單。為此,可以在在第一次提交表單后就禁用提交按鈕,或者利用 onsubmit 事件處理程序取消后續(xù)的表單提交操作。
1.2、重置表單
使用 type 特性值為 “reset”的 <input> 或 <button> 都可以創(chuàng)建重置按鈕,
<!-- 通用重置按鈕 -->
<input type="reset" value="Reset Form">
<!-- 自定義重置按鈕 -->
<button type="reset">Reset From</button>
也可以使用 JavaScript 的方式 來(lái)重置表單
var form = document.getElementById('myForm')
// 重置表單
from.reset()
用戶單擊重置按鈕重置表單時(shí),會(huì)觸發(fā) reset 事件。我們可以在必要時(shí)取消重置操作
/* 阻止重置表單操作 */
var form = document.getElementById('myForn')
form.onreset = function(event) {
event.preventDefault()
}
1.3、表單字段
每個(gè)表單都有一個(gè) elements 屬性,該屬性是表單中所有表單元素(字段)的集合。可以按照位置和 name 特性來(lái)訪問(wèn)它們,
var form = document.getElementById('myForm')
// 取得表單中的第一個(gè)字段
var field1 = form.elements[0]
// 取得表單中name 為 textbox1的字段
var field2 = form.elements['textbox1']
// 取得表單中包含的字段的數(shù)量
var fieldCount = from.elements.length
如果使用 name 實(shí)現(xiàn)訪問(wèn)元素時(shí),多個(gè)表單控件的 name 相同,就會(huì)返回一個(gè) 以改name 命名的 NodeList,然后可以通過(guò) 索引 訪問(wèn)這個(gè)集合中的元素
如下
<input type="radio" name="color" value="red"> red
<input type="radio" name="color" value="cyan"> cyan
<input type="radio" name="color" value="violet"> violet
<script>
var colors = document.forms[0]['color']
console.log(Object.prototype.toString.call(colors))
console.log(colors[0]) // 第一個(gè) 單選框
</script>
1.3.1、共有的表單字段屬性
除了 <fieldset> 元素之外,所有的表單字段都擁有相同的一組屬性。
表單字段共有的屬性如下。
- disabled:布爾值,表示當(dāng)前字段是否被禁用
- form:指向當(dāng)前字段所屬表單的指針;只讀。
- name:當(dāng)前字段的名稱
- readOnly:布爾值,表示當(dāng)前字段是否只讀
- tabIndex:表示當(dāng)前字段的切換(tab)序號(hào)
- type:當(dāng)前字段的類型,入“checkbox“、”radio“,等等。
- value:當(dāng)前字段將被提交給服務(wù)器的值。對(duì)文件字段來(lái)說(shuō),這個(gè)屬性是只讀的,包含文件在計(jì)算機(jī)中的路徑
超級(jí) form屬性之外,可以通過(guò) JavaScript 動(dòng)態(tài)的修改其他屬性的值。
var form = document.getElementById('myForm')
var field = form.elements[0]
// 修改 value 屬性
field.value = "Another value"
// 檢查 form 屬性值
alert(field.form == form) // true
// 把焦點(diǎn)設(shè)置當(dāng)前字段
field.focus()
// 禁用當(dāng)前字段
field.disabled = true
// 修改 type 屬性(不推薦,但對(duì)于 <input> 元素來(lái)說(shuō)是可行的)
field.type = "checkbox"
避免多次提交,最常見(jiàn)的解決方案,就是在第一次單機(jī)后就禁用提交按鈕。
// 避免多次提交表單
EventUtil.addHandler(form, 'submit', function(event) {
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event)
// 取得提交按鈕
var btn = target.elements['submit-btn']
// 禁用它
btn.disabled = true
})
注意不能通過(guò) onclick 事件處理程序來(lái)實(shí)現(xiàn)這個(gè)功能,原因是不同瀏覽器之間存在”時(shí)差“:有點(diǎn)瀏覽器會(huì)在觸發(fā)表單的 submit 事件之前觸發(fā) click 事件,意味著會(huì)在提交發(fā)生之間禁用按鈕,結(jié)果永遠(yuǎn)都不會(huì)提交表單
除了 <fieldset> 之外,所有表單字段都有 type 屬性。對(duì)于 <input> 元素,這個(gè)值等于 HTML 特性 type 的值。對(duì)于其他元素,這個(gè) type 屬性的值如下表所列:
| 說(shuō)明 | 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 屬性是可以動(dòng)態(tài)修改的,而 <select> 元素的 type 屬性則是只讀的。
1.3.2、共有的表單字段方法
每個(gè)表單字段都有兩個(gè)方法:focus() 和 blur()。
focus() 方法用于將瀏覽器的焦點(diǎn)設(shè)置到表單字段,級(jí)激活表單字段,使其可以響應(yīng)鍵盤事件。為此,可以偵聽(tīng)頁(yè)面的 load 事件,并在該事件發(fā)生時(shí)在表單的第一個(gè)字段上調(diào)用 focus() 方法,如下:
EventUtil.addHandler(window, 'load' , function(event) {
document.forms[0],elements[0].focus()
})
需要注意的是:如果 focus() 方法的對(duì)象是一個(gè) type="hidden" 的 <input> 元素的話,那么以上代碼會(huì)導(dǎo)致錯(cuò)誤。另外,如果使用 CSS 的 display 和 visibility 屬性隱藏了該字段,同樣也會(huì)導(dǎo)致錯(cuò)誤
HTML5 為表單字段新增了一個(gè) autofocus 屬性。在支持這個(gè)屬性的瀏覽器中,只要設(shè)置這個(gè)屬性不用 JavaScript就能自動(dòng)把焦點(diǎn)移動(dòng)到相應(yīng)字段。例如:
<input type="text" autofocus />
可以通過(guò) autofocus 屬性獲取 元素上面的 autofocus 的狀態(tài),在支持的瀏覽器中如果設(shè)置了為true
blur() 作用是從元素上移走焦點(diǎn)
document.forms[0].elements[0].blur()
1.3.3、共有的表單字段事件
所有的表單字段都支持下列三個(gè)事件
- blur:當(dāng)前字段市區(qū)焦點(diǎn)時(shí)觸發(fā)
- change:對(duì)于<input> 和 <textarea> 元素,它們失去焦點(diǎn)且 value 值改變時(shí)觸發(fā);對(duì)于 <select> 元素,在其選項(xiàng)改變時(shí)觸發(fā)
- focus:當(dāng)前字段獲得焦點(diǎn)時(shí)觸發(fā)
change 事件經(jīng)曾用于驗(yàn)證用戶在字段中輸入的數(shù)據(jù)。
下面例子中,對(duì)上訴三個(gè)事件簡(jiǎn)單使用
var textbox = document.forms[0].elements[0]
EventUtil.addHandler(textbox, "focus", function(event) { // 獲得焦點(diǎn)時(shí) 編程黃色
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event)
if (target.style.backgroundColor != 'red') {
target.style.backgroundColor = 'yellow'
}
})
EventUtil.addHandler(textbox, "blur", function(event) { // 失去焦點(diǎn)
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event)
// 驗(yàn)證用戶是否輸入的是數(shù)字
if (/[^\d]/.test(target.value)) {
target.style.backgroundColor = 'red'
} else {
target.style.backgroundColor = ''
}
})
EventUtil.addHandler(textbox, "input", function(event) { // value 值變化時(shí)
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event)
console.log(target.value)
// 驗(yàn)證用戶是否輸入的是數(shù)字
if (/[^\d]/.test(target.value)) {
console.log(1)
target.style.backgroundColor = 'red'
} else {
target.style.backgroundColor = ''
}
})
因?yàn)?change 事件必須 在value值改變且失去焦點(diǎn)才會(huì)觸發(fā),所以這里用 input 事件來(lái)實(shí)時(shí)監(jiān)聽(tīng)
二、文本框腳本
在 HTML 中,有兩種方式來(lái)表現(xiàn)文本框,<input> 、<textarea> 兩種元素
<input>
- 通過(guò)設(shè)置 size 特性,可以指定文本框中能夠顯示的字符數(shù)
- 通過(guò)value特性,可以設(shè)置文本框的初始值
- 通過(guò)maxlength特性則用于指定文本框 可以接受的最大字符數(shù)
<textarea>
- rows特性指定文本框的字符行數(shù)
- cols 特性指定文本框的字符列數(shù)
與 <Input> 元素不同,<textarea>的初始值必須要放在 <textarea></textarea>之間
<textarea rows="25" cols="5"> initial value </textarea>
值得注意的時(shí):在操作 value 屬性時(shí),不建議使用 標(biāo)準(zhǔn)的 DOM 方法(setAttribute()),這樣對(duì) value 屬性所作的修改,不一定會(huì)反映在 DOM 中
2.1、選擇文本
上訴兩種文本框都支持 select() 方法,這個(gè)方法用于選擇文本框中的所有文本。在調(diào)用 select() 方法時(shí),大多數(shù)瀏覽器都會(huì)將焦點(diǎn)設(shè)置到 文本框中
var textbox = document.forms[0].elements["textarea"]
textbox.select()
在文本框獲得焦點(diǎn)時(shí)選擇其所有文本,這是一種非常常見(jiàn)的做法,特別是在 文本框包含默認(rèn)值的時(shí)候。因?yàn)檫@樣做可以讓用戶不必一個(gè)一個(gè)地刪除文本。
EventUtil.addHandler(textbox, "focus", function(event) {
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event)
target.select()
})
2.1.1、select 事件
與 select() 方法對(duì)應(yīng)的,有一個(gè) select 事件,在選擇了文本框的文本是,就會(huì)觸發(fā)select事件,而在IE8及更早版本中,只要用戶選擇了一個(gè)字母(不必釋放鼠標(biāo)),就會(huì)觸發(fā)select 事件。另外,在調(diào)用 select() 方法時(shí)也會(huì)觸發(fā) select 事件。
var textbox = document.forms[0].elements['textbox1']
EventUtil.addHandler(textbox, "select", function(event) {
console.log("select text")
})
當(dāng)用戶選擇 textbox 中的文本時(shí),就會(huì)觸發(fā)這個(gè)事件 ,輸出 select text
2.1.2、取得選擇的文本
通過(guò) select 事件我們可以知道用戶什么時(shí)候選擇了文本,但任然不知道用戶選擇了什么文本。 HTML5 通過(guò)了一些 擴(kuò)展方案解決了這個(gè)問(wèn)題。添加了兩個(gè)屬性:selectionStart 和 selectionEnd。 這兩個(gè)屬性中保存的是基于 0 的數(shù)值,表示所選擇文本的范圍(即文本選區(qū)開(kāi)頭和結(jié)尾的偏移量)。
function getSelectedText(textbox) {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd)
}
// 獲取選區(qū)的文本
var textbox = document.forms[0].elements["textarea"]
EventUtil.addHandler(textbox, "select", function(event) {
// 調(diào)用方法
console.log("select Text :" + getSelectedText(textbox))
})
IE8及之前不支持這兩個(gè)屬性而是提供了另一種方案
IE8及更早的版本中有一個(gè) document.selection 對(duì)象,其中保存著用戶在整個(gè)文檔范圍內(nèi)選擇的文本信息,與 select 事件一起使用的時(shí)候,可以假定是用戶選擇了文本框中的文本。要取得選擇的文本,首先必須創(chuàng)建一個(gè)范圍,然后再將文本從其中提取出來(lái)。
function getSelectedText(textbox) {
if (typeof textbox.selectionstart === 'number') {
return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd)
} else if(document.selection) { // 針對(duì) IE
return document.selection.createRange().text
}
}
2.1.3、選擇部分文本
HTML5 也為選擇文本框中的部分文本提供了解決方案,所有文本款都有一個(gè) setSelectionRange() 方法,這個(gè)方法接收兩個(gè)參數(shù):要選擇的第一個(gè)字符的索引、選擇的最后一個(gè)字符之后的字符的索引
var textbox = document.forms[0].elements["textarea"]
textbox.value = 'Hello World!'
textbox.select() // 使當(dāng)前 獲取焦點(diǎn)
// 選擇所有文本
textbox.setSelectionRange(0, textbox.value.length)
// 選擇前3個(gè)字符
textbox.setSelectionRange(0, 3)
// 選擇第 4-6 個(gè)字符
textbox.setSelectionRange(4, 7)
要看到選擇的文本,必須在調(diào)用 setSelectionRange() 之前或之后立即將焦點(diǎn)設(shè)置到文本框
IE8及更早版本,要選擇文本框中的部分文本,
- 首先使用 IE 在所有文本框是 提供的 createTextRange() 方法創(chuàng)建一個(gè)范圍,
- 然后 使用 moveStart() 和 moveEnd() 這兩個(gè)范圍方法將范圍移動(dòng)到位
- 在調(diào)用這兩個(gè)方法之前,還必須使用 collapse() 將范圍折疊到文本框的開(kāi)始位置
- 使用 范圍的 select() 方法選擇文本
var textbox = document.forms[0].elements["textarea"]
textbox.value = 'Hello World!'
var range = textbox.createTextRange()
// 選擇所有文本
range.collapse(true)
range.moveStart('character', 0)
range.moveEnd('character', textbox.value.length)
range.select()
// 選擇前3個(gè)字符
range.collapse(true)
range.moveStart('character', 0)
range.moveEnd('character', 3)
range.select()
實(shí)現(xiàn)跨瀏覽器編程,結(jié)合上訴兩種方案組合
function selectText(textbox, startIndex, endIndex) {
if (textbox.setSelectionRange) {
textbox.setSelectionRange(startIndex, endIndex)
} else if(textbox.createTextRange) { // IE
var range = textbox.createTextRange() // 創(chuàng)建范圍
range.collapse(true) // 折疊范圍
// 移動(dòng)范圍
range.moveStart('character', startIndex)
range.moveEnd('character', endIndex)
range.select() // 選擇文本
}
// 獲取焦點(diǎn)
textbox.focus()
}
可以像下面這樣使用這個(gè)方法
var textbox = document.forms[0].elements['textarea']
textbox.value = 'Hello World'
// 選擇所有文本
selectText(textbox, 0, textbox.value.length)
2.2、過(guò)濾輸入
我們經(jīng)常會(huì)要求用戶在文本框中輸入特定的數(shù)據(jù),或者輸入特定格式的數(shù)據(jù)。綜合運(yùn)用事件和DOM手段,可以將普通的文本框轉(zhuǎn)換成能夠理解用戶輸入數(shù)據(jù)的功能型控件。
2.2.1、屏蔽字段
響應(yīng)向文本框中插入字符操作的是 keypress 事件。因此,可以通過(guò)阻止這個(gè)事件的默認(rèn)行為來(lái)屏蔽此類字符。
EventUtil.addHandler(textbox, 'keypress', function(event) {
event = EventUtil.getEvent(event)
EventUtil.preventDefault(event)
})
如果只想屏蔽特定字符,則需要檢測(cè) keypress 事件對(duì)應(yīng)的字符編碼,然后再?zèng)Q定如何響應(yīng)。
// 只允許輸入數(shù)值。
EventUtil.addHandler(textbox, 'keypress', function(event) {
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event)
var charCode = EventUtil.getCharCode(event) // 獲取當(dāng)前的 鍵碼
if (! (/\d/.test(String.fromCharCode(charCode)))) { // 獲取鍵碼對(duì)應(yīng)字符在進(jìn)行正則檢測(cè) 取反
EventUtil.preventDefault(event)
}
})
僅考慮屏蔽不是數(shù)值的字符還不夠,還要避免屏蔽一些極為常用和必要的鍵。所幸的是,要檢測(cè)這些鍵并不困難。
在Firefox中,所有由非字符鍵觸發(fā)的 keypress 事件對(duì)應(yīng)的字符編碼為0
在 Safari 以前的版本中,對(duì)應(yīng)的字符編碼全部為8
為了讓代碼更通用,只要不屏蔽那些字符編碼小于10的鍵即可
// ...
if (!(/\d/.test(String.fromCharCode(charCode))) && charCode > 9) {
EventUtil.preventDefault(event)
}
除此之外還有一個(gè)文本需要處理:復(fù)制、粘貼及其他操作還有用到 Ctrl 鍵。在除IE之外的所有瀏覽器中,前面的代碼也會(huì)屏蔽 Ctrl + C、Ctrl + V,以及其他使用 Ctrl 的組合鍵。因此最后還要添加一個(gè)檢測(cè)條件,以確保用戶沒(méi)有按下啊 Ctrl 鍵。
if (! (/\d/.test(String.fromCharCode(charCode))) && charCode > 9 && !event.ctrlKey) {
EventUtil.preventDefault(event)
}
2.2.2、操作剪貼板
IE 是第一個(gè)支持與剪貼板有關(guān)事件,以及通過(guò) JavaScript 訪問(wèn)剪貼板數(shù)據(jù)的瀏覽器, HTML5后來(lái)也把剪貼板事件納入了規(guī)范
- beforecopy:在發(fā)生復(fù)制操作當(dāng)前觸發(fā)。
- copy:在發(fā)生復(fù)制操作時(shí)觸發(fā)
- beforecut:在發(fā)生剪切操作前觸發(fā)
- cut:在發(fā)生剪切操作時(shí)觸發(fā)
- beforepaste:在發(fā)生粘貼操作前觸發(fā)
- paste:在發(fā)生粘貼操作時(shí)觸發(fā)
由于沒(méi)有針對(duì)剪貼板操作的標(biāo)準(zhǔn),這些事件及相關(guān)對(duì)象會(huì)因?yàn)g覽器而異。在Safari、Chrome 和 Firefox中,
beforecopy、beforecut、beforepaste 事件只會(huì)在顯示在針對(duì)文本框的上下文菜單(預(yù)期發(fā)生剪貼板事件)的情況下觸發(fā)。但是,IE則會(huì)在觸發(fā) copy、cut和paste事件之前先觸發(fā)這些事件。至于 copy、cut和paste事件,只要是在上下文菜單中選擇了相應(yīng)選項(xiàng),或者使用了相應(yīng)的鍵盤組合鍵,所有瀏覽器都會(huì)觸發(fā)它們。
在實(shí)際的事件發(fā)生之前,通過(guò) beforecopy、beforecut、beforepaste事件可以在像剪貼板發(fā)送數(shù)據(jù),或者從剪貼板取得數(shù)據(jù)之前修改數(shù)據(jù)。不過(guò),取消這些事件并不會(huì)取消對(duì)剪切板的操作——只有取消 copy、cut、paste 事件,才能阻止響應(yīng)操作發(fā)生。
要訪問(wèn)剪貼板中的數(shù)據(jù),可以使用 clipboardData 對(duì)象:在IE中,這個(gè)對(duì)象是 window 對(duì)象的 屬性;而在 Firefox 4+、Safari 和 Chrome 中,這個(gè)對(duì)象是相應(yīng) event 對(duì)象的屬性。但是,在Firefox、Safari、Chrome 中,只有在處理剪貼板事件期間,clipboardData對(duì)象才有效,這是為了防止對(duì)剪貼板的未授權(quán)訪問(wèn);在IE中,則可以隨時(shí)訪問(wèn) clipboardData 對(duì)象。為了確??鐬g覽器兼容,最好只在發(fā)生剪貼板事件期間使用這個(gè)對(duì)象。
clipboardData 對(duì)象有三個(gè)方法
-
getDate() 用于從剪貼板中獲取數(shù)據(jù),
- 接受一個(gè)參數(shù),即要取得的數(shù)據(jù)格式,
在IE中,有兩種數(shù)據(jù)格式:“text”、“URL”
在Firefox、Safari、Chrome中,這個(gè)參數(shù)是一種MIME類型,可以使用“text”代表“text/plain”
- 接受一個(gè)參數(shù),即要取得的數(shù)據(jù)格式,
-
setData() 設(shè)置數(shù)據(jù)
- 數(shù)據(jù)類型
IE支持:text、URL
而 Safaru 和 Chrome 只支持 MIME 類型,不能代替 - 放在剪貼板中的文本。
- 數(shù)據(jù)類型
clearData():清除數(shù)據(jù)
在成功將文本放到剪貼板中后,都會(huì)返回true;否則返回false
為了彌補(bǔ)這些差異,我們可以項(xiàng) EventUtil 中再添加下列方法:
var EventUtil = {
// 獲取剪貼板中的 文本
getClipboardText: function(event) {
var clipboardData = (event.clipboardData || window.clipboardData)
return clipboardData.getData('text')
},
// 設(shè)置 剪貼板中的文本
setClipboardText: function(event, value) {
if (event.clipboardData) return event.clipboardData.setData('text/plain', value)
else if(window.clipboardData) return window.clipboardData.setData('text', value)
}
}
在需要確保粘貼到文本框中的文本中包含某些字符,或者符合某種格式要求時(shí),能夠訪問(wèn)剪貼板是非常有用的。在paste事件中,可以確定剪貼板中的事件是否有效
EventUtil.addHandler(textbox, 'paste', function(event) {
event = EventUtil.getEvent(event)
var text = EventUtil.getClipboardText(event)
console.log(1)
if (!/^\d*$/.test(text)) {
EventUtil.preventDefault(event)
}
})
如上代碼只會(huì)在,粘貼板中文本內(nèi)容只包含數(shù)字的時(shí)候,才能粘貼
由于并非所有瀏覽器都支持訪問(wèn)剪貼板,所以更簡(jiǎn)單的做法是屏蔽一或多個(gè)剪貼板操作
2.3、自動(dòng)切換焦點(diǎn)
使用 JavaScript 可以從多個(gè)方面增強(qiáng)表單字段的易用性。其中,最常用的一種方式就是在用戶填寫完當(dāng)前字段時(shí),自動(dòng)將焦點(diǎn)切換到下一個(gè)字段。
例如,美國(guó)的電話號(hào)碼通常分為三個(gè)部分:區(qū)號(hào)、局號(hào)、另外4位數(shù)字。為取得完整的電話號(hào)碼,很多網(wǎng)頁(yè)中都會(huì)提供下列3個(gè)文本框,如下DOM結(jié)構(gòu)
<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">
為了增強(qiáng)易用性,同時(shí)加快數(shù)據(jù)輸入,可以在前一個(gè)文本框中的字符達(dá)到最大數(shù)量后,自動(dòng)將焦點(diǎn)切換到下一個(gè)文本框
function tabForward(event) {
event = EventUtil.getEvent(event)
var target = EventUtil.getTarget(event) // 獲取當(dāng)前DOM元素
if (target.value.length == target.maxLength) { // 如果 輸入完畢(最大長(zhǎng)度)
var form = target.form // 獲取 當(dāng)前字段的 form
for(var i = 0, len = form.elements.length; i < len; i++){ // 遍歷
if (form.elements[i] == target) { // 當(dāng)前元素
if (form.elements[i + 1]) { // 如果存在 下一個(gè)元素
form.elements[i + 1].focus() // 下一個(gè)元素獲得焦點(diǎn)
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)
不過(guò)請(qǐng)記住,這些代碼只適用于前面給出的標(biāo)記,沒(méi)有考慮隱藏字段
2.4、HTML5約束驗(yàn)證API
為了在將表單提交到服務(wù)器之前驗(yàn)證數(shù)據(jù),HTML5新增了一些功能。有了這些功能,幾遍JavaScript被禁用或者由于種種原因未能加載,也可以確?;镜尿?yàn)證
2.4.1、必填字段
required 屬性
<input type="text" name="username" required />
在JavaScript中,通過(guò)對(duì)應(yīng)的required 屬性,可以檢查某個(gè)表單字段是否為必填字段
var isUsernameRequired = document.forms[0].elements["username"].required
另外,使用下面這行代碼可以測(cè)試瀏覽器是否支持required屬性
var isRequiredSupported = "required" in document.createElement("input")
對(duì)于空著的必填字段,不同瀏覽器有不同的處理方式。Firefox4和Opera11會(huì)阻止表單提交并在相應(yīng)字段下發(fā)彈出幫助框,而Safari5-、Chrome9-則什么也不做,而且也不會(huì)阻止表單提交。現(xiàn)在的瀏覽器以便都會(huì)阻止提交
2.4.2、其他輸入類型
HTML5 為<input> 元素的 type 屬性又增加了幾個(gè)值。“email”、“url”是兩個(gè)得到支持最多的類型,各瀏覽器也都給他們?cè)黾恿硕ㄖ频尿?yàn)證機(jī)制。
<input type="email" name="email" />
<input type="url" name="homepage" />
"email" 類型要求輸入的文本必須符合電子郵件地址的模式,而"url"類型要求輸入的文本必須符合 URL 模式。
要檢查瀏覽器是否支持這些類型,可以在 JavaScript中創(chuàng)建一個(gè) <input>元素的type設(shè)置為其中一個(gè),再檢測(cè)這個(gè)屬性的值
var input = document.createElement("input")
input.type = "email"
var isEmailSupported = (input.type == "email")
設(shè)置特定的輸入類型并不能阻止用戶輸入無(wú)效的值,只是應(yīng)用某些默認(rèn)的驗(yàn)證而已。
2.4.3、數(shù)值范圍
除了上訴兩個(gè)之外,HTML5 還定義了另外幾個(gè)輸入元素。這幾個(gè)元素都要求填寫某種基于數(shù)字的值:number、range、datetime、datetime-local、date、month、week、time
對(duì)所有這些數(shù)值類型的輸入元素,可以指定 min 屬性(最小的可能值)、max屬性(最大的可能值)、step(兩個(gè)刻度之間的差值)。想讓用戶只能輸入0到100的值,而且這個(gè)值必須是5的倍數(shù),可以這些代碼:
<input type="number" min="0" max="100" step="5" name="count" />
在不同的瀏覽器中,可能會(huì)也可能不會(huì)看到能夠自動(dòng)遞增和遞減的數(shù)值調(diào)節(jié)按鈕(向上和向下按鈕)
2.4.4、輸入模式
HTML5 為文本字段新增了 pattern 屬性。這個(gè)屬性的值是一個(gè)正則表達(dá)式,用于匹配文本框中的值。
例如:只允許在文本空中輸入數(shù)值
<input type="text" pattern="\d+" name="count" />
注意,模式的開(kāi)頭和末尾不用加 ^ 和 $符號(hào)(假定已經(jīng)有了)。這兩個(gè)符號(hào)表示輸入的值必須從頭到尾與模式匹配。
與其他輸入類型相似,指定 pattern 也不能阻止用戶輸入無(wú)效的文本。這個(gè)模式應(yīng)用給值,瀏覽器來(lái)判斷值是有效,還是無(wú)效。
在JavaScript中可以通過(guò) pattern 屬性訪問(wèn)
var pattern = document.forms[0].elements['count'].pattern
使用以下代碼可以檢測(cè)瀏覽器是否支持 pattern 屬性
var isPatternSupported = "pattern" in document.createElement("input")
2.4.5、檢測(cè)有效性
使用 checkValidity() 方法可以檢測(cè)表單中的某個(gè)字段是否有效。所有表單字段都有這個(gè)方法,如果字段的值有效,這個(gè)方法返回 true,否則返回false。字段的值是否有效的判斷依據(jù)是本節(jié)前面介紹過(guò)的那些約束。
if (document.forms[0].elements[0].checkValidity()) {
// 字段有效
} else {
// 字段無(wú)效
}
要檢測(cè)整個(gè)表單是否有效,可以在表單自身調(diào)用 checkValidity() 方法。如果所有表單字段都有效,這個(gè)方法返回 true;即使有一個(gè)字段無(wú)效,這個(gè)方法也返回false
if (document.form[0].checkValidity()) {
// 表單有效
} else {
// 表單無(wú)效
}
與 checkValidity() 方法簡(jiǎn)單地告訴你字段是否有效相比,validity 屬性則會(huì)告訴你為什么字段有效或無(wú)效。這個(gè)對(duì)象中包含一系列屬性,每個(gè)屬性會(huì)返回一個(gè) 布爾值。
- customError:如果設(shè)置了 setCustomValidity(),則返回 true,否則返回 false。
- patternMismatch:如果值與指定的pattern 屬性不匹配,返回 true
- rangeOverflow:如果值比max值大,返回 true
- rangeUnderflow:如果值比 min 值小,則返回 true
- stepMisMatch:如果 min 和 max 之間的步長(zhǎng)值不合理,返回true
- tooLong:如果值的長(zhǎng)度超過(guò)了 maxlength 屬性指定的長(zhǎng)度,返回 true。有的瀏覽器(如Firefox4)會(huì)自動(dòng)約束字符數(shù)量,因此這個(gè)值可能永遠(yuǎn)返回 false
- typeMismatch:如果值不是"mail" 或 "url" 要求的格式,返回 true
- valid:如果這里的其他屬性都是false,返回true。cjeckValidity() 也要求相同的值。
- valueMissing:如果標(biāo)注為 required 的字段中沒(méi)有值,則返回 true。
因此,要想得到更具體的信息,就應(yīng)該使用 validity 屬性來(lái)檢測(cè)表單的有效性。
if (input.validity.valueMissing) {
alert('please specify a value')
} else if (inout.validity.typeMismatch) {
alert('please enter an email address.')
} else {
alert('value is invalid')
}
2.4.6、禁用驗(yàn)證
通過(guò)設(shè)置 novalidate 屬性,可以告訴表單不進(jìn)行驗(yàn)證。
<form method="post" action="signup.php" novalidate>
</form>
在 JavaScript中使用 noValidate 屬性可以去的或設(shè)置這個(gè)值,如果這個(gè)屬性存在,值為 true
如果不存在,值為 false。
document.forms[0].noValidate = true // 禁用表單
如果一個(gè)表單中有多個(gè)提交按鈕,為了指定點(diǎn)擊某個(gè)提交按鈕不必驗(yàn)證表單,可以在相應(yīng)的按鈕上添加 formnovalidate 屬性
<form method="post" action="signup.php">
<input type="submit" value="submit" />
<input type="submit" value="Non-validating Submit" formnovalidate />
</form>
三、選擇框腳本
選擇框是通過(guò)<select>、<option> 元素創(chuàng)建的。為了方便與這個(gè)控件交互,除了所有表單字段共有的屬性和方法外,HTMLSelectElement 類型還提供了下列屬性和方法。
- add(newOption, relOption):向控件中插入 新<option> 元素,其位置在相關(guān)項(xiàng)(relOption)之前。
- multiple:布爾值,表示是否允許多項(xiàng)選擇;等介于HTML中的 multiple 特性
- options:控件中所有 <option>元素的 HTMLCollection
- remove(index):移除給定位置的選項(xiàng)
- selectedIndex:基于0的選項(xiàng)中的索引,如果沒(méi)有選中項(xiàng),則值為 -1。對(duì)于支持多選的控件,只保存選中項(xiàng)中第一項(xiàng)的索引值。
- size:選擇框中可見(jiàn)的行數(shù);等價(jià)于HTML 中的 size特性
選擇框的 type 屬性 不是 "select-one",就是"select-mutiple",這取決于 HTML中有沒(méi)有 multiple 特性。選擇框的 value 屬性由當(dāng)前選中項(xiàng)決定,相應(yīng)規(guī)則如下:
- 如果沒(méi)有選中的項(xiàng),則選擇框的 value 屬性保存空字符串
- 如果有一個(gè)選中項(xiàng),而且該項(xiàng)的 value 特性已經(jīng)在 HTML 中指定,則選擇框的 value 屬性等于選中項(xiàng)的 value 特定。即使 value 特性的值是空字符串,也同樣遵循此條規(guī)則
- 如果有一個(gè)選中項(xiàng),但該項(xiàng)的 value 特性在HTML 中未指定,則選擇框的 value 屬性等于該項(xiàng)的文本
- 如果有多個(gè)選中項(xiàng),則選擇框的 value 屬性將依據(jù)前兩條規(guī)則取得第一個(gè)選中項(xiàng)的值。
<select name="locaation" id="selLocation">
<option value="Sunnyvale, CA">Sunnyvale</option>
<option value="">Chine</option>
<option>Australia</option>
</select>
如上DOM結(jié)構(gòu):
- 如果用戶選中了第一項(xiàng)那么值就是 Sunnyvale, CA
- 如果用戶選中了第二項(xiàng),則選擇框的值是 空字符串
- 如果選中第三個(gè),則選擇框的值是 Austraila
在DOM中,每個(gè)<option>元素都有一個(gè) HTMLOptionElement 對(duì)象表示,以便于訪問(wèn)數(shù)據(jù),HTMLOptionElement對(duì)象添加了下列屬性:
- index:當(dāng)前選項(xiàng)在 options 集合中的索引
- label:當(dāng)前選項(xiàng)的標(biāo)簽;等價(jià)于 HTML 中的 label 特性
- selected:布爾值,表示當(dāng)前選項(xiàng)是否被選中。將這個(gè)屬性設(shè)置為 true 可以選中當(dāng)前選項(xiàng)
- text:選項(xiàng)的文本。
- value:選項(xiàng)的值(等價(jià)于HTML中的value 特性)
大部分屬性的目的,都是為了方便對(duì)選項(xiàng)數(shù)據(jù)的訪問(wèn)。
var selectbox = document.forms[0].elements['location']
var text = selectbox.options[0].text
var value = selectbox.options[0].value
選擇框的 change 事件 與其他表單字段的 change 事件觸發(fā)的條件不一樣。其他表單字段的 change 事件是在值被修改且焦點(diǎn)離開(kāi)當(dāng)前字段時(shí)觸發(fā),而選擇寬的 change 事件只要選中了選項(xiàng)就會(huì)觸發(fā)。
3.1、選擇選項(xiàng)
對(duì)于只允許選擇一項(xiàng)的選擇框,訪問(wèn)選中項(xiàng)的最簡(jiǎn)單方式,就是使用選擇框的 selectedIndex 屬性,
var selectedOption = selectbox.options[selectbox.selectedIndex]
獲取后可以像下面這樣顯示該選項(xiàng)的信息
var selectbox = document.forms[0].elements[0]
var selectedIndex = selectbox.selectedIndex
var selectedOption = selectbox.options[selectedIndex]
var selectedValue = selectedOption.value
對(duì)于 可以選擇多項(xiàng)的選擇框,selectedIndex 屬性就好像只允許選擇一項(xiàng)一樣。設(shè)置selectedIndex 會(huì)導(dǎo)致取消以前的所有選項(xiàng)并選擇指定的那一項(xiàng),而讀取 selectedIndex 則只會(huì)返回選擇第一項(xiàng)的索引值。
另一種選擇選項(xiàng)的方式,就是去的對(duì)某一項(xiàng)的引用,然后將其 selected 屬性設(shè)置為 true。
selectbox.options[0].selected = true
設(shè)置選項(xiàng)的selected 屬性,不會(huì)取消對(duì)其他選中項(xiàng)的選擇,因而可以動(dòng)態(tài)選中任意多個(gè)項(xiàng)。需要注意的是,將selected 屬性設(shè)置為 false對(duì)單選選擇框沒(méi)有影響
實(shí)際上,selected屬性的作用主要是確定用戶選擇了選擇框中的哪一項(xiàng)。要取得所有選中項(xiàng),可以循環(huán)遍歷選項(xiàng)集合,然后測(cè)試每個(gè)選項(xiàng)的selected 屬性
function getSelectedOptions(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) {
return.push(option)
}
}
return result;
}
下面是使用getSelectedOptions() 函數(shù)取得選中項(xiàng)的實(shí)例。
var selectbox = document.getElementById('selLocation')
var selectedOptions = getSelectedOptions(selectbox)
var message = ''
for(var i = 0, len = selectedOptions.length; i < len; i++) {
message += 'selectedIndex:' + selectedOptions[i].index + '\nSelected value:' + selectedOptions[i].value
}
console.log(message)
3.2、添加選項(xiàng)
可以使用 JavaScript 動(dòng)態(tài)創(chuàng)建選項(xiàng),并將它們添加到選擇框中。
第一種方式就是使用如下所示的DOM方法
var newOption = document.createElement('option')
newOption.appendChild(document.createTextNode('option tex'))
newOption.setAttribute('value', 'option value')
selectbox.appendChild(newOption)
第二種方式是使用 Option 構(gòu)造函數(shù)來(lái)創(chuàng)建新選項(xiàng),構(gòu)造函數(shù)接受兩個(gè)參數(shù):文本(text)、值(value)
var newOption = new Option('Option text', 'Option value')
selectbox.appendChild(newOption) // 在 IE8 及指甲鉗版本中有問(wèn)題
第三種方式是使用 選擇框的 add() 方法,接受兩個(gè)參數(shù):要添加的新選項(xiàng)、將位于新選項(xiàng)之后的選項(xiàng)。如果想在最后添加一個(gè)選項(xiàng),第二個(gè)參數(shù)設(shè)置為 null;在IE對(duì) add() 方法的實(shí)現(xiàn)中,第二個(gè)參數(shù)是可選的,如果指定,該參數(shù)必須是新選項(xiàng)之后的選項(xiàng)的索引。兼容DOM的瀏要求必須指定第二個(gè)參數(shù),因此如果要編寫跨瀏覽器代碼,需要為第二個(gè)參數(shù)傳入 undefined,即可。
var newOption = new Option('option text', 'option value')
selectbox.add(newOPtion, undefined) // 最佳方案
3.3、移除選項(xiàng)
與添加選項(xiàng)類似,移除選項(xiàng)的方式也有很多種
- 使用 removeChild() 的方法
selectbox.removeChild(selectbox,options[0]) // 移除第一個(gè)選項(xiàng)
- 使用選擇框的 remove() 方法。這個(gè)方法接受一個(gè)參數(shù),即要移除選項(xiàng)的索引
selectbox.remove(0) // 移除第一個(gè)選項(xiàng)
- 將相應(yīng)選項(xiàng)設(shè)置為 null。(這種方式也是 DOM 之前瀏覽器的遺留機(jī)制)
selectbox.options[0] = null // 移除第一個(gè)選項(xiàng)
要移除選擇框中所有的項(xiàng),可以迭代所有選項(xiàng)并移除它們
function clearSelectbox(selectbox) {
for (var i = 0, len = selectbox.options.length; i < len; i++) {
selectbox.remove(0)
}
}
3.4、移動(dòng)和重排選項(xiàng)
在DOM標(biāo)準(zhǔn)出現(xiàn)之前,將一個(gè)選擇框中的選項(xiàng)移動(dòng)到另一個(gè)選擇框中是非常麻煩的。使用DOM的appendChild() 方法,就可以將第一個(gè)選擇框中的選項(xiàng)直接移動(dòng)到第二個(gè)選擇框中。如果為 appendChild() 方法傳入一個(gè)文檔中已有的元素,那么就會(huì)先從該元素的父節(jié)點(diǎn)中移除它,再把它添加到指定的位置。
var selectbox1 = document.getElementById('selLocations1')
var selectbox2 = document.getElementById('selLocations2')
selectbox2.appendChild(selectbox1.options[0])
移動(dòng)選項(xiàng)與移除選項(xiàng)有一個(gè)共同之處,即會(huì)重置每一個(gè)選項(xiàng)的 index 屬性
重排 選項(xiàng)次序的過(guò)程也十分類似,要將選擇框中的某一項(xiàng)移動(dòng)到特定位置,最合適的 DOM 方法就是 insertBefore();appendChild() 方法只適用于將選項(xiàng)添加到選擇框的最后。
在選擇框中向前移動(dòng)一個(gè)選項(xiàng)的位置
var optionToMove = selectbox.options[1]
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index-1]
將選擇框中的選項(xiàng)向后移動(dòng)一個(gè)位置
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index+2])
四、序列化表單
隨著 Ajax 的出現(xiàn),表單序列化已經(jīng)成為一種常見(jiàn)需求。在JavaScript中,可以利用表單字段的 type 屬性,連同name 和 value 屬性一起實(shí)現(xiàn)對(duì)表單的序列化。
在表單提交期間,瀏覽器是怎樣將數(shù)據(jù)發(fā)給服務(wù)器的
- 對(duì)表單字段的名稱和值進(jìn)行 URL 編碼,使用 和號(hào)(&) 分隔。
- 不發(fā)送禁用的表單字段
- 只發(fā)送勾選的復(fù)選框和單選按鈕
- 不發(fā)送 type 為 "reset" 和 "button" 的按鈕
- 多選選擇框中的每一個(gè)選中的值單獨(dú)一個(gè)條目
- 在單擊按鈕提交表單的情況下,也會(huì)發(fā)送提交按鈕;否則,不發(fā)送提交按鈕。也包括 type 為 "image" 的 <input> 元素
- <select> 元素的值,就是選中 <option> 元素的 value 特性的值。如果 <option> 元素沒(méi)有 value特性,則是 <option>元素的文本值
在表單序列化過(guò)程中,一般不包括任何按鈕字段,因?yàn)榻Y(jié)果字符串很可能是通過(guò)其它方式提交的。除此之外的其他上訴規(guī)則都應(yīng)該遵循。
// 表單序列化
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) { // 如果有 name 屬性 并且 有值
for (j = 0, optLen = field.options.length; j < optLen; j++) { // 遍歷 所有 option
option = field.options[j]
if (option.selected) { // 如果當(dāng)前選項(xiàng)被 選擇
optValue = ''
if (option.hasAttribute) { // 如果支持 hasAttribute() 方法
optValue = (option.hasAttribute('value') ? option.value : option.text) // 查找是否存在 value 屬性
} else { // 不支持 hasAttribute()
optValue = (option.attributes['value'].specified ? option.value : option.text) // 查明是否已規(guī)定 value 屬性
}
parts.push(encodeURIComponent(field.name) + '=' + encodeURIComponent(optValue))
}
}
}
break;
case undefined: // 字段集
case 'file': // 文件輸入
case 'submit': // 提交按鈕
case 'reset': // 重置按鈕
case 'button': // 自掉頭按鈕
break;
case 'radio': // 單選按鈕
case 'checkbox': // 復(fù)選框
if (!field.checked) {
break;
}
default: // 默認(rèn)操作
if (field.name.length) { // 不包含沒(méi)有 name 的表單字段
parts.push(encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value))
}
}
}
return parts.join('&')
}
以下是這個(gè) 方法的簡(jiǎn)單使用
<form action="www.xxx.xxx" id="myForm">
地區(qū):<select name="locaation" id="selLocation">
<option value="Sunnyvale, CA">Sunnyvale</option>
<option value="Chine">Chine</option>
<option value="Ireland">Ireland</option>
<option>Asutralia</option>
</select>
<p>用戶名:<input type="text" name="username"></p>
<p>郵箱:<input type="email" name="email"></p>
<p><input type="submit" value="submit"></p>
</form>
<script>
var form = document.forms[0]
form.onsubmit = function(event) {
event.preventDefault()
var fieldsStr = serialize(form)
console.log(fieldsStr)
}
</script>
五、富文本編輯
富文本編輯,又稱為 WYSIWYG(What You See Is What You Get,所見(jiàn)即所得)。在網(wǎng)頁(yè)中編輯富文本內(nèi)容,是人本對(duì)web應(yīng)用程序最大的期待之一。這一技術(shù)的本質(zhì),就是在頁(yè)面中嵌入一個(gè)包含空HTML的頁(yè)面 iframe。通過(guò)設(shè)置 designMode 屬性,這個(gè)空白的 HTML 頁(yè)面可以被編輯,而編輯對(duì)象則是該頁(yè)面 <body> 元素的 HTML 代碼。designMode 屬性有兩個(gè)可能的值:"off"(默認(rèn)值)和 "on"。在設(shè)置為 "on" 時(shí),整個(gè)文檔都會(huì)變得可以編輯
可以給 iframe 指定一個(gè) 非常簡(jiǎn)單的HTML 頁(yè)面作為其內(nèi)容來(lái)源。
<!DOCTYPE>
<html>
<head>
<title> Black Page for Rich Text Editing</title>
</head>
<body></body>
</html>
在包含頁(yè)面中,需要使用 onload 事件處理程序來(lái)在恰當(dāng)?shù)臅r(shí)刻設(shè)置 designMode, 如下:
<iframe name="richedit" style="width: 100px; height: 100px;" src="./blank.html"></iframe>
<script>
window.onload = function() {
frames['richedit'].document.designMode = 'on'
}
</script>
需要注意的是,不能以文件方式在本地直接用瀏覽器打開(kāi)的,地址欄是file:///??梢栽囍诒镜丶茉O(shè)服務(wù)器來(lái)調(diào)試
5.1、使用 contenteditable 屬性
另一種編輯富文本內(nèi)容的方式是使用名為 contenteditable 的特殊屬性,這個(gè)屬性也是由 IE 最早實(shí)現(xiàn)的??梢园?contenteditable 屬性應(yīng)用給頁(yè)面中的任何元素,然后用戶立即就可以編輯該元素。這種方法之所以受到歡迎,是因?yàn)樗恍枰?iframe、空白頁(yè) 和 JavaScript ,只要為元素設(shè)置 contenteditable屬性即可
<div class="editable" id="richedit" contenteditable></div>
如上,元素中包含的任何文本內(nèi)容就都可以編輯了,就好像這個(gè)元素變成了 <textarea> 元素一樣。通過(guò)在這個(gè)元素上設(shè)置 contenteditable 元素,也能開(kāi)打或關(guān)閉編輯模式
var div = document.getElementById('richedit')
div.contentEditable = true
div.contentEditable = false
contenteditable 屬性有三個(gè)可能的值:"true"表示打開(kāi),"false"表示關(guān)閉,"inherit"表示從父元素那里繼承
5.2、操作富文本
與富文本編輯器交互的主要方式,就是使用 document.execCommand()。這個(gè)方法可以對(duì)文檔指向預(yù)定義的命令,而且可以應(yīng)用大多格式。這個(gè)方法接受三個(gè)參數(shù):
- 要執(zhí)行的命令名稱
- 表示瀏覽器是否應(yīng)該為當(dāng)前命令提供用戶界面的一個(gè)布爾值
- 指向命令必須的一個(gè)值(如果不需要值,則傳遞 null)
為了確保跨瀏覽器的兼容性,第二個(gè)參數(shù)應(yīng)該始終設(shè)置為 false,因?yàn)?Firefox 會(huì)在改參數(shù)為 true時(shí) 拋出錯(cuò)誤
不同瀏覽器支持的預(yù)定義命令也不一樣。下表列出了那些被支持最多的命令
| 命令 | 值(第三個(gè)參數(shù)) | 說(shuō)明 |
|---|---|---|
| backcolor | 顏色字符串 | 設(shè)置文檔的背景顏色 |
| bold | null | 將選擇的文本轉(zhuǎn)換為粗體 |
| copy | null | 將選擇的文本復(fù)制到剪貼板 |
| createlink | URL字符串 | 將選擇的文本轉(zhuǎn)換成一個(gè)鏈接,指向指定的URL |
| cut | null | 將選擇的文本剪切到剪貼板 |
| delete | null | 刪除選擇的文本 |
| fontname | 字體名稱 | 將選擇的文本修改為指定字體 |
| fontsize | 1~7 | 將選擇的文本修改為指定字體大小 |
| forecolor | 顏色字符串 | 將選擇的文本修改為指定的顏色 |
| formatblock | 要包圍當(dāng)前文本的HTML標(biāo)簽;如<h1> | 使用指定的HTML標(biāo)簽來(lái)格式化選擇的文本快 |
| indent | null | 縮進(jìn)文本 |
| inserthorizontalrule | null | 在插入字符處插入一個(gè)<hr>元素 |
| insertimage | 圖像的URL | 在插入字符處插入一個(gè)圖像 |
| insertorderedlist | null | 在插入字符處插入一個(gè)<ol>元素 |
| insertunorderedlist | null | 在擦汗如字符處插入一個(gè)<ul>元素 |
| insertparagraph | null | 在插入字符處插入一個(gè)<p>元素 |
| italic | null | 將選擇的文本轉(zhuǎn)換成斜體 |
| justifycenter | null | 將插入光標(biāo)所在文本塊居中對(duì)齊 |
| justifyleft | null | 將插入光標(biāo)所在文本快左對(duì)齊 |
| outden | null | 凸排文本(減少縮進(jìn)) |
| paste | null | 將剪貼板中的文本粘貼到選擇的文本 |
| removeformat | null | 移除插入光標(biāo)所在文本塊的塊級(jí)格式。這是撤銷 formatblock 命令的操作 |
| selectall | null | 選擇文檔中的所有文本 |
| underline | null | 為選擇的文本添加下劃線 |
| unlink | null | 移除文本的鏈接。這是冊(cè)小 createlink的命令的操作 |
其中,與剪貼板有關(guān)的命令在不同瀏覽器中的差異極大。Opera 根本沒(méi)有實(shí)現(xiàn)任何剪貼板命令,即使不能通過(guò) document.execCommand() 來(lái)執(zhí)行這些命令,但卻可以通過(guò)相應(yīng)的快捷鍵來(lái)實(shí)現(xiàn)同樣的操作。
使用上訴屬性,來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)富文本
<button id="boldBtn">bold </button>
<button id="italicBtn">italic</button>
<button id="linkBtn">a</button>
<button id="h1Btn">H1</button>
<iframe name="richedit" style="width: 1000px; height: 300px;display: block" src="blank.html"></iframe>
<script>
window.onload = function() {
frames['richedit'].document.designMode = 'on'
document.getElementById('boldBtn').onclick = function() {
frames['richedit'].document.execCommand('bold', false, null)
}
document.getElementById('italicBtn').onclick = function() {
frames['richedit'].document.execCommand('italic', false, null)
}
document.getElementById('linkBtn').onclick = function() {
frames['richedit'].document.execCommand('createlink', false, 'http://www.baidu.com') // 默認(rèn)地址,簡(jiǎn)單模擬
}
document.getElementById('h1Btn').onclick = function() {
frames['richedit'].document.execCommand('formatblock', false, '<h1>')
}
}
</script>

選區(qū)文本后,點(diǎn)擊不同的快捷鍵,實(shí)現(xiàn)修改外觀
同樣的方法也適用于頁(yè)面中 contenteditable 屬性為 "true" 的區(qū)塊,只要把對(duì)框架的引用替換成當(dāng)前窗口的 document 對(duì)象即可。
<div style="width: 1000px; height: 300px;display: block;border: 1px solid violet" id="div" contenteditable>
</div>
<script>
window.onload = function() {
document.getElementById('boldBtn').onclick = function() {
document.execCommand('bold', false, null)
}
document.getElementById('italicBtn').onclick = function() {
document.execCommand('italic', false, null)
}
document.getElementById('linkBtn').onclick = function() {
document.execCommand('createlink', false, 'http://www.baidu.com') // 默認(rèn)地址,簡(jiǎn)單模擬
}
document.getElementById('h1Btn').onclick = function() {
document.execCommand('formatblock', false, '<h1>')
}
}
需要注意的是,雖然所有瀏覽器都支持這些命令,但這些命令所產(chǎn)生的 HTML 仍然有很大不同。由于各個(gè)瀏覽器實(shí)現(xiàn)命令的方式不同,加上它們通過(guò) innerHTML 實(shí)現(xiàn)轉(zhuǎn)換的方式也不一樣,因此不能指望富文本編輯器會(huì)產(chǎn)生一致的 HTML。
除了命令之外,還有一些于命令相關(guān)的方法
-
queryCommandEnabled()
用來(lái)檢測(cè)是否可以爭(zhēng)對(duì)當(dāng)前選擇的文本,或者當(dāng)前插入字符所在位置執(zhí)行某個(gè)命令。接受一個(gè)參數(shù),即要檢測(cè)的命令。如果當(dāng)前編輯區(qū)域允許執(zhí)行傳入的命令,這個(gè)方法返回 true,否則返回 false。
var result = frames['richedit'].document.queryCommandEnabled('bold')
如果能夠?qū)Ξ?dāng)前選擇的文本執(zhí)行"bold"命令,以上代碼就會(huì)返回 ture
-
queryCommandState()
方法用于確定是否已將指定命令應(yīng)用到了選擇的文本。
var isBold = frames['richedit'].document.queryCommandState('bold')
如果以前已經(jīng)對(duì)選擇的文本執(zhí)行了"bold"命令,那么上面的代碼會(huì)返回 ture。
-
queryCommandValue()
用于取得執(zhí)行命令時(shí)傳入的值(即前面例子中傳給document.execCommand() 第三個(gè)參數(shù)
var fontSize = frames['richedit'].document.queryCommandValue('fontsize')
通過(guò)這個(gè)方法可以確定某個(gè)命令是怎樣應(yīng)用到選擇的文本的,可以據(jù)以確定在對(duì)其應(yīng)用后續(xù)命令是否合適
5.3、富文本選區(qū)
在富文本編輯器中,適用 框架(iframe)的getSelection() 方法,可以確定實(shí)際選擇的文本。這個(gè)方法是 window 對(duì)象和 document對(duì)象的屬性,調(diào)用它會(huì)返回一個(gè)表示當(dāng)前選擇文本的 Selection 對(duì)象。每個(gè)Selection對(duì)象都有下列屬性。
- anchorNode:選區(qū)起點(diǎn)所在的節(jié)點(diǎn)。
- anchorOffset:在到達(dá)選區(qū)起點(diǎn)之前跳過(guò)的 anchorNode 中的字符數(shù)量
- focusNode:選區(qū)終點(diǎn)所在的節(jié)點(diǎn)
- focusOffset:focusNode中包含在選區(qū)之內(nèi)的字符數(shù)量。
- isCollapsed:布爾值,表示選區(qū)的起點(diǎn)和終點(diǎn)是否重合
- rangeCount:選區(qū)中包含的DOM范圍的數(shù)量
Selection 對(duì)象的這些這些屬性并沒(méi)有包含多少有用的信息。下列方法提供了更多信息,并且支持對(duì)選區(qū)的操作
- addRange(range):將指定的DOM范圍添加到選區(qū)中
- collapse(node, offset):將選區(qū)折疊到指定節(jié)點(diǎn)中的相應(yīng)的文本偏移位置。
- collapseToEnd():將選區(qū)折疊到終點(diǎn)
- collapseToStart():將選區(qū)折疊到起點(diǎn)位置
- **containsNode(node):確定指定的節(jié)點(diǎn)是否包含在候選區(qū)中
- deleteFromDocument():從文檔中刪除選區(qū)中的文本,document.execCommand('delete', false, null) 命令的結(jié)果相同
- extend(node, offset):將通過(guò) focusNode、focusOffset 移動(dòng)到指定的位置來(lái)擴(kuò)展選區(qū)。
- getRangeAt(index):返回索引對(duì)應(yīng)的選區(qū)中的DOM范圍
- removeAllRanges():從選區(qū)中移除所有 DOM 范圍。實(shí)際上,這樣會(huì)移除選區(qū),因?yàn)檫x區(qū)中至少要有一個(gè)范圍
- **removeRange(range):從選區(qū)中移除指定的 DOM 范圍
- selectAllChildren(node):清除選區(qū)并選擇指定節(jié)點(diǎn)的所有子節(jié)點(diǎn)
- toString():返回選區(qū)所包含的文本內(nèi)容
Selection 對(duì)象的這些方法都極為使用,它們利用了 前面講到的 DOM范圍來(lái)管理選區(qū)。由于可以直接操作選擇文本的DOM表現(xiàn),因此訪問(wèn)DOM范圍與 使用 execCommand() 相比,能夠?qū)Ω晃谋揪庉嬈鬟M(jìn)行更加細(xì)化的控制。
var selection = frames['richedit'].getSelection()
// 取得選擇的文本
var selectedText = selection.toString()
// 取得代表選區(qū)的范圍
var range = selection.getRangeAt(0)
// 突出顯示選擇的文本
var span = frames['richedit'].document.createElement('span')
span.style.backgroundColor = 'yellow'
range.surroundContents(span)
Firefox 3.6+ 中調(diào)用 document.getSelection() 會(huì)返回一個(gè)字符串。為此,可以在 Forefox 3.6+ 中該作調(diào)用 window.getSelection(), 從而范圍 selection 對(duì)象。Firefox 8修復(fù)了這個(gè)問(wèn)題
IE8 及更早的版本不支持DOM范圍,但我們可以通過(guò)它支持的 selection 對(duì)象操作選擇的文本。要取得富文本編輯器中選擇的文本,首先必須創(chuàng)建一個(gè)文本范圍,然后再像下面這樣訪問(wèn)其 text 屬性。
var range = frames['richedit'].document.selection.createRange()
var selectedText = range.text
雖然使用 IE 的文本范圍來(lái)啊執(zhí)行 HTML 操作并不像使用 DOM 范圍那么可靠,但也不失為一種有效的途徑。要像前面使用 DOM 范圍那樣實(shí)現(xiàn)相同的文本高亮效果,可以組合使用 **htmlText **屬性和 pasteHTML() 方法
var range = frames['richedit'].document.selection.createRange()
range.pasteHTML('<span style="background-color: yellow">'+ range.htmlText + '</span>');
5.4、表單與富文本
富文本編輯器中的HTML不會(huì)被自動(dòng)提交給 服務(wù)器,而需要我們手工來(lái)提交并提交HTML。為此,通??梢蕴砑右粋€(gè)隱藏的表單字段,讓他的值等于 從 iframe 中提取的 HTML。
form.onsubmit = function() {
this.elements['comments'].value = frames['richedit'].document.body.innerHTML
}
對(duì)于 contenteditable 元素,也可以執(zhí)行類似的操作
form.onsubmit = function() {
this.elements['comments'].value = document.getElementById('richedit').innerHTML
}
六、小結(jié)
使用 JavaScript 可以增強(qiáng)已有的表單字段,從而創(chuàng)造出新的功能,或者提升表單的易用性。
- 可以使用一些標(biāo)準(zhǔn) 或 非標(biāo)準(zhǔn)的方法選擇文本框的全部或部分文本
- 在文本框的內(nèi)容變化時(shí),可以通過(guò)偵聽(tīng)鍵盤事件以及檢測(cè)插入的字符,來(lái)允許或禁止用戶輸入
- 除了Opera 其他主流瀏覽器都允許 通過(guò) JavaScript 訪問(wèn) 剪貼板中的數(shù)據(jù)。
- IE允許在任何時(shí)候訪問(wèn)剪貼板的相關(guān)信息,而其他瀏覽器只能按在發(fā)生剪貼板事件時(shí)才能訪問(wèn)。
富文本編輯功能是通過(guò)一個(gè)包含空 HTML 文檔的 iframe 元素來(lái)實(shí)現(xiàn)。通過(guò)將空文檔的 designMode 屬性設(shè)置為 'on',就可以將頁(yè)面轉(zhuǎn)換為 可以編輯狀態(tài),此時(shí)其表現(xiàn)如同字處理軟件。另外,也可以將某個(gè)元素設(shè)置為 contenteditable。在默認(rèn)情況下,可以將字體加粗或者將文本轉(zhuǎn)換為斜體,還可以使用剪貼板。
JavaScript通過(guò)使用 execCommand() 方法也可以實(shí)現(xiàn)相同的一些功能。另外,使用 queryCommandEnabled()、queryCommandState()、queryCommandValue() 方法則可以取得有關(guān)文本選區(qū)的信息。由于以這種方式構(gòu)建的富文本編輯器并不是一個(gè)表單字段,因此在將其內(nèi)容提交給服務(wù)器之前,必須將 iframe 或 contenteditable 元素中的 HTML 復(fù)制到一個(gè)表單字段中。