表單腳本

本章主要介紹:表單、文本框驗(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及更早版本,要選擇文本框中的部分文本,

  1. 首先使用 IE 在所有文本框是 提供的 createTextRange() 方法創(chuàng)建一個(gè)范圍,
  2. 然后 使用 moveStart() 和 moveEnd() 這兩個(gè)范圍方法將范圍移動(dòng)到位
  3. 在調(diào)用這兩個(gè)方法之前,還必須使用 collapse() 將范圍折疊到文本框的開(kāi)始位置
  4. 使用 范圍的 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”
  • setData() 設(shè)置數(shù)據(jù)

    • 數(shù)據(jù)類型
      IE支持:text、URL
      而 Safaru 和 Chrome 只支持 MIME 類型,不能代替
    • 放在剪貼板中的文本。
  • 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è)表單字段中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、表單基礎(chǔ)知識(shí) 在 JavaScript 中,表單對(duì)應(yīng)的是 HTMLFormElement 類型。 HTMLFo...
    LemonnYan閱讀 1,017評(píng)論 0 3
  • 本章內(nèi)容 理解表單 文本框驗(yàn)證與交互 使用其他表單控制 14.1 表單的基礎(chǔ)知識(shí) 通過(guò)document.forms...
    悶油瓶小張閱讀 421評(píng)論 0 0
  • 14.1 表單的基礎(chǔ)知識(shí) 表單由 元素來(lái)表示,繼承自HTMLElement類型,除具有HTML元素相同的默認(rèn)屬性外...
    Elevens_regret閱讀 441評(píng)論 0 0
  • 表單基礎(chǔ)知識(shí) 在HTML中,表單是使用form元素來(lái)表示的,JS中對(duì)應(yīng)的是HTMLFormElement類型。它同...
    More_5897閱讀 397評(píng)論 0 1
  • 富文本編輯 富文本編輯,又稱為WYSIWYG(What Tou See Is What You Get,所見(jiàn)即所得...
    泡杯感冒靈閱讀 347評(píng)論 0 0

友情鏈接更多精彩內(nèi)容