十三、表單腳本

??JavaScript 最初的一個應用,就是分擔服務器處理表單的責任,打破處處依賴服務器的局面。

??盡管目前的 Web 和 JavaScript 已經有了長足的發展,但 Web 表單的變化并不明顯。

??由于 Web 表單沒有為許多常見任務提供現成的解決手段,很多開發人員不僅會在驗證表單時使用 JavaScirpt,而且還增強了一些標準表單控件的默認行為。

1、表單的基礎知識

??在 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>元素引用的方式有好幾種。其中最常見的方式就是將它看成與其他元素一樣,并為其添加 id 特性,然后再像下面這樣使用 getElementById() 方法找到它。

var form = document.getElementById("form1");

??其次,通過 document.forms 可以取得頁面中所有的表單。在這個集合中,可以通過數值索引或 name 值來取得特定的表單,如下面的例子所示。

var firstForm = document.forms[0]; // 取得頁面中的第一個表單
var myForm = document.forms["form2"]; // 取得頁面中名稱為"form2"的表單

??另外,在較早的瀏覽器或者那些支持向后兼容的瀏覽器中,也會把每個設置了 name 特性的表單作為屬性保存在 document 對象中。
??例如,通過 document.form2 可以訪問到名為"form2"的表單。不過,我們不推薦使用這種方式:一是容易出錯,二是將來的瀏覽器可能會不支持。
??注意,可以同時為表單指定 id 和 name 屬性,但它們的值不一定相同。

1.1、提交表單

??用戶單擊提交按鈕或圖像按鈕時,就會提交表單。使用<input>或<button>都可以定義提交按鈕,只要將其 type 特性的值設置為"submit"即可,而圖像按鈕則是通過將<input>的 type 特性值設置為"image"來定義的。因此,只要我們單擊以下代碼生成的按鈕,就可以提交表單。

<!-- 通用提交按鈕 -->
<input type="submit" value="Submit Form">

<!-- 自定義提交按鈕 -->
<button type="submit">Submit Form</button>

<!-- 圖像按鈕 -->
<input type="image" src="graphic.gif"> 

??只要表單中存在上面列出的任何一種按鈕,那么在相應表單控件擁有焦點的情況下,按回車鍵就可以提交該表單。(textarea 是一個例外,在文本區中回車會換行。)如果表單里沒有提交按鈕,按回車鍵不會提交表單。
??以這種方式提交表單時,瀏覽器會在將請求發送給服務器之前觸發 submit 事件。這樣,我們就有機會驗證表單數據,并據以絕決定是否允許表單提交。阻止這個事件的默認行為就可以取消表單提交。例如:下列代碼會阻止表單提交。

var form = document.getElementById('myForm');
EventUtil.addHandler(form, "submit", function(event) {
    
    // 取得事件對象
    event = EventUtil.getEvent(event);

    // 阻止默認事件
    EventUtil.preventDefault(event);
});

??調用 preventDefault() 方法組織了表單提交。一般來說,在表單數據無效而不能發送給服務器時,可以使用這一技術。
??在 JavaScript 中,以編程方式調用 submit() 方法也可以提交表單。而且,這種方式無需表單包含提交按鈕,任何時候都可以正常提交表單。示例:

var form = document.getElementById("myForm");

// 提交表單
form.submit();

??在以調用 submit() 方法的形式提交表單時,不會觸發 submit 事件,因此要記得在調用此方法之前先驗證表單數據。
??提交表單時可能出現的最大問題,就是重復提交表單。在第一次提交表單后,如果長時間沒有反應,用戶可能會變得不耐煩。這時候,他們也許會反復單擊提交按鈕。結果往往很麻煩(因為服務器要處理重復的請求),或者會造成錯誤(如果用戶是下訂單,那么可能會多訂好幾份)。
??解決這一問題的辦法有兩個:在第一次提交表單后就禁用提交按鈕,或者利用 onsubmit 事件處理程序取消后續的表單提交操作。

1.2、重置表單

??在用戶點擊重置按鈕時,表單會被重置。使用 type 特性值為"reset"的<input>或<button>都可以創建重置按鈕,示例:

<!-- 通用重置按鈕 -->
<input type="reset" value="Reset Form">

<!-- 自定義重置按鈕 -->
<button type="reset">Reset Form</button>

??上述兩個按鈕都可以用來重置表單。在重置表單時,所有表單字段都會恢復到頁面剛加載完畢時的初始值。如果某個字段的初始值為空,就會恢復為空;而帶有默認值的字段,也會恢復為默認值。
??用戶單擊重置按鈕重置表單時,會觸發 reset 事件。利用這個機會,我們可以在必要時取消重置操作。例如,下面展示了阻止重置表單的代碼。

var form = document.getElementById("myForm");
EventUtil.addHandler(form, "reset", function(event) {
    
    // 取得事件對象
    event = EventUtil.getEvent(event);

    // 阻止表單重置
    EventUtil.preventDefault(event);
});

??與提交表單一樣,也可以通過 JavaScript 來重置表單,示例:

var form = document.getElementById("myForm");

// 重置表單
form.reset();

??與調用 submit() 方法不同,調用 reset() 方法會像點擊重置按鈕一樣處罰 reset 事件。

??在 Web 表單設計中,重置表單通常意味著對已經填寫的數據不滿意。重置表單經常會導致用戶摸不著頭腦,如果意外地觸發了表單重置事件,那么用戶甚至會很惱火。
??事實上,重置表單的需求是很少見的。更常見的做法是提供一個取消按鈕,讓用戶能夠回到前一個頁面,而不是不分青紅皂白地重置表單中的所有值。

1.3、 表單字段

??可以像訪問頁面中的其他元素一樣,使用原生 DOM 方法訪問表單元素。此外,每個表單都有elements 屬性,該屬性是表單中所有表單元素(字段)的集合。
??這個 elements 集合是一個有序列表,其中包含著表單中的所有字段,例如<input>、<textarea>、<button> 和 <fieldset>。每個表單字段在 elements 集合中的順序,與它們出現在標記中的順序相同,可以按照位置和 name 特性來訪問它們。示例:

var form = document.getElementById("form1");

// 取得表單中的第一個字段
var field1 = form.elements[0];

// 取得名為"textbox1"的字段
var field2 = form.elements["textbox1"];

// 取得表單中包含的字段的數量
var fieldCount = form.elements.length;

??如果有多個表單控件都在使用一個 name(如單選按鈕),那么就會返回以該 name 命名的一個 NodeList。例如,以下面的 HTML 代碼片段為例。

<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>

??在這個 HTML 表單中,有 3 個單選按鈕,它們的 name 都是"color",意味著這 3 個字段是一起的。在訪問 elements["color"] 時,就會返回一個 NodeList,其中包含這 3 個元素;不過,如果訪問 elements[0],則只會返回第一個元素。示例:

var form = document.getElementById("myForm");

var colorFields = form.elements["color"];
alert(colorFields.length); // 3

var firstColorField = colorFields[0];
var firstFormField = form.elements[0];
alert(firstColorField === firstFormField); // true

??上述代碼顯示,通過 form.elements[0] 訪問到的第一個表單字段,與包含在 form.elements["color"] 中的第一個元素相同。

??也可以通過訪問表單的屬性來訪問元素,例如 form[0] 可以取得第一個表單字段,而 form["color"] 則可以取得第一個命名字段。這些屬性與通過 elements 集合訪問到的元素是相同的。但是,我們應該盡可能使用 elements,通過表單屬性訪問元素只是為了與舊瀏覽器向后兼容而保留的一種過渡方式。

1. 共有的表單字段屬性

??除了<fieldset>元素之外,所有表單字段都擁有相同的一組屬性。由于<input>類型可以表示多種表單字段,因此有些屬性只適用于某些字段,但還有一些屬性是所有字段所共有的。表單字段共有的屬性如下。

  • disabled:布爾值,表示當前字段是否被禁用。
  • form:指向當前字段所屬表單的指針;只讀。
  • name:當前字段的名稱。
  • readOnly:布爾值,表示當前字段是否只讀。
  • tabIndex:表示當前字段的切換(tab)序號。
  • type:當前字段的類型,如"checkbox"、"radio",等等。
  • value:當前字段將被提交給服務器的值。對文件字段來說,這個屬性是只讀的,包含著文件在計算機中的路徑。

??除了 form 屬性之外,可以通過 JavaScript 動態修改其他任何屬性。示例:

var form = document.getElementById("myForm");
var field = form.elements[0];

// 修改 value 屬性
field.value = "Another value";

//檢查 form 屬性的值
alert(field.form === form); // true

// 把焦點設置到當前字段
field.focus();

// 禁用當前字段
field.disabled = true;

// 修改 type 屬性(不推薦,但對<input>來說是可行的)
field.type = "checkbox";

??能夠動態修改表單字段屬性,意味著我們可以在任何時候,以任何方式來動態操作表單。例如,很多用戶可能會重復單擊表單的提交按鈕。在涉及信用卡消費時,這就是個問題:因為會導致費用翻番。
??為此,最常見的解決方案,就是在第一次單擊后就禁用提交按鈕。只要偵聽 submit 事件,并在該事件發生時禁用提交按鈕即可。示例:

// 避免多次提交表單
EventUtil.addHandler(form, "submit", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);

    // 取得提交按鈕
    var btn = target.elements["submit-btn"];

    // 禁用它
    btn.disabled = true;
}); 

??以上代碼為表單的 submit 事件添加了一個事件處理程序。事件觸發后,代碼取得了提交按鈕并將其 disabled 屬性設置為 true。
??注意,不能通過 onclick 事件處理程序來實現這個功能,原因是不同瀏覽器之間存在“時差”:有的瀏覽器會在觸發表單的 submit 事件之前觸發 click 事件,而有的瀏覽器則相反。對于先觸發 click 事件的瀏覽器,意味著會在提交發生之前禁用按鈕,結果永遠都不會提交表單。因此,最好是通過 submit 事件來禁用提交按鈕。
??不過,這種方式不適合表單中不包含提交按鈕的情況;如前所述,只有在包含提交按鈕的情況下,才有可能觸發表單的 submit 事件。

??除了<fieldset>之外,所有表單字段都有 type 屬性。對于<input>元素,這個值等于 HTML 特性 type 的值。對于其他元素,這個 type 屬性的值如下表所列。

說 明 HTML示例 type屬性的值
單選列表 <select>...</select> "select-one"
多選列表 <select multiple>...</select> "select-multiple"
自定義按鈕 <button>...</button> "submit"
自定義非提交按鈕 <button type="button">...</button> "button"
自定義重置按鈕 <button type="reset">...</buton> "reset"
自定義提交按鈕 <button type="submit">...</buton> "submit"

??此外,<input>和<button>元素的 type 屬性是可以動態修改的,而<select>元素的 type 屬性則是只讀的。

2. 共有的表單字段方法

??每個表單字段都有兩個方法:focus() 和 blur()。

??focus() 方法用于將瀏覽器的焦點設置到表單字段,即激活表單字段,使其可以響應鍵盤事件。例如,接收到焦點的文本框會顯示插入符號,隨時可以接收輸入。
??使用 focus() 方法,可以將用戶的注意力吸引到頁面中的某個部位。例如,在頁面加載完畢后,將焦點轉移到表單中的第一個字段。為此,可以偵聽頁面的 load 事件,并在該事件發生時在表單的第一個字段上調用 focus()方法,如下面的例子所示。

EventUtil.addHandler(window, "load", function(event){
    document.forms[0].elements[0].focus();
});

??要注意的是,如果第一個表單字段是一個<input>元素,且其 type 特性的值為"hidden",那么以上代碼會導致錯誤。另外,如果使用 CSS 的 display 和 visibility 屬性隱藏了該字段,同樣也會導致錯誤。

??HTML5 為表單字段新增了一個 autofocus 屬性。在支持這個屬性的瀏覽器中,只要設置這個屬性,不用 JavaScript 就能自動把焦點移動到相應字段。例如:

<input type="text" autofocus>

??為了保證前面的代碼在設置 autofocus 的瀏覽器中正常運行,必須先檢測是否設置了該屬性,如果設置了,就不用再調用 focus() 了。

EventUtil.addHandler(window, "load", function(event){
    var element = document.forms[0].elements[0]; 
    if (element.autofocus !== true){
        element.focus(); 
        console.log("JS focus");
    }
}); 

??因為 autofocus 是一個布爾值屬性,所以在支持的瀏覽器中它的值應該是 true。(在不支持的瀏覽器中,它的值將是空字符串。)??為此,上面的代碼只有在 autofocus 不等于 true 的情況下才會調用 focus(),從而保證向前兼容。支持 autofocus 屬性的瀏覽器有 Firefox 4+、Safari 5+、Chrome 和 Opera9.6。

??在默認情況下,只有表單字段可以獲得焦點。對于其他元素而言,如果先將其 tabIndex 屬性設置為 -1,然后再調用 focus() 方法,也可以讓這些元素獲得焦點。只有 Opera 不支持這種技術。

??與 focus() 方法相對的是 blur() 方法,它的作用是從元素中移走焦點。

??在調用 blur() 方法時,并不會把焦點轉移到某個特定的元素上;僅僅是將焦點從調用這個方法的元素上面移走而已。
??在早期 Web 開發中,那時候的表單字段還沒有 readonly 特性,因此就可以使用 blur() 方法來創建只讀字段。現在,雖然需要使用 blur() 的場合不多了,但必要時還可以使用的。用法如下:

document.forms[0].elements[0].blur();
3. 共有的表單字段事件

??除了支持鼠標、鍵盤、更改和 HTML 事件之外,所有表單字段都支持下列 3 個事件。

  • blur:當前字段失去焦點時觸發。
  • change:對于<input>和<textarea>元素,在它們失去焦點且 value 值改變時觸發;對于<select>元素,在其選項改變時觸發。
  • focus:當前字段獲得焦點時觸發。

??當用戶改變了當前字段的焦點,或者我們調用了 blur() 或 focus() 方法時,都可以觸發 blur 和 focus 事件。這兩個事件在所有表單字段中都是相同的。

??但是,change 事件在不同表單控件中觸發的次數會有所不同。對于<input>和<textarea>元素,當它們從獲得焦點到失去焦點且 value 值改變時,才會觸發 change 事件。對于<select>元素,只要用戶選擇了不同的選項,就會觸發 change 事件;換句話說,不失去焦點也會觸發 change 事件。

??通常,可以使用 focus 和 blur 事件來以某種方式改變用戶界面,要么是向用戶給出視覺提示,要么是向界面中添加額外的功能(例如,為文本框顯示一個下拉選項菜單)。
??而 change 事件則經常用于驗證用戶在字段中輸入的數據。

??例如,假設有一個文本框,我們只允許用戶輸入數值。此時,可以利用 focus 事件修改文本框的背景顏色,以便更清楚地表明這個字段獲得了焦點。可以利用 blur 事件恢復文本框的背景顏色,利用 change 事件在用戶輸入了非數值字符時再次修改背景顏色。下面就給出了實現上述功能的代碼。

var textbox = document.forms[0].elements[0];

EventUtil.addHandler(textbox, "focus", function(event){
    event = EventUtil.getEvent(event); 
    var target = EventUtil.getTarget(event);

    if (target.style.backgroundColor != "red"){
        target.style.backgroundColor = "yellow";
    }
});

EventUtil.addHandler(textbox, "blur", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);

    if (/[^\d]/.test(target.value)){
        target.style.backgroundColor = "red";
    } else {
        target.style.backgroundColor = "";
    }
});

EventUtil.addHandler(textbox, "change", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);

    if (/[^\d]/.test(target.value)){
        target.style.backgroundColor = "red";
    } else {
        target.style.backgroundColor = "";
    }
});

??在此,onfocus 事件處理程序將文本框的背景顏色修改為黃色,以清楚地表明當前字段已經激活。
??隨后,onblur 和 onchange 事件處理程序則會在發現非數值字符時,將文本框背景顏色修改為紅色。
??為了測試用戶輸入的是不是非數值,這里針對文本框的 value 屬性使用了簡單的正則表達式。而且,為確保無論文本框的值如何變化,驗證規則始終如一,onblur 和 onchange 事件處理程序中使用了相同的正則表達式。

??關于 blur 和 change 事件的關系,并沒有嚴格的規定。在某些瀏覽器中,blur 事件會先于 change 事件發生;而在其他瀏覽器中,則恰好相反。為此,不能假定這兩個事件總會以某種順序依次觸發,這一點要特別注意。

2、 文本框腳本

??在 HTML 中,有兩種方式來表現文本框:一種是使用<input>元素的單行文本框,另一種是使用<textarea>的多行文本框。這兩個控件非常相似,而且多數時候的行為也差不多。不過,它們之間仍然存在一些重要的區別。

??要表現文本框,必須將<input>元素的 type 特性設置為"text"。而通過設置 size 特性,可以指定文本框中能夠顯示的字符數。通過 value 特性,可以設置文本框的初始值,而 maxlength 特性則用于指定文本框可以接受的最大字符數。如果要創建一個文本框,讓它能夠顯示 25 個字符,但輸入不能超過 50 個字符,可以使用以下代碼:

<input type="text" size="25" maxlength="50" value="initial value">

??相對而言,<textarea>元素則始終會呈現為一個多行文本框。要指定文本框的大小,可以使用 rows和 cols 特性。其中,rows 特性指定的是文本框的字符行數,而 cols 特性指定的是文本框的字符列數
(類似于<inpu>元素的 size 特性)。
??與<input>元素不同,<textarea>的初始值必須要放在 <textarea> 和 </textarea> 之間,如下面的例子所示。

<textarea rows="25" cols="5">initial value</textarea>

??另一個與<input>的區別在于,不能在 HTML 中給<textarea>指定最大字符數。

??無論這兩種文本框在標記中有什么區別,但它們都會將用戶輸入的內容保存在 value 屬性中。可以通過這個屬性讀取和設置文本框的值,如下面的例子所示:

var textbox = document.forms[0].elements["textbox1"];
alert(textbox.value);
textbox.value = "Some new value";

??我們建議讀者像上面這樣使用 value 屬性讀取或設置文本框的值,不建議使用標準的 DOM 方法。
??換句話說,不要使用 setAttribute() 設置<input>元素的 value 特性,也不要去修改<textarea>元素的第一個子節點。原因很簡單:對 value 屬性所作的修改,不一定會反映在 DOM 中。因此,在處理文本框的值時,最好不要使用 DOM 方法。

2.1、 選擇文本

??上述兩種文本框都支持 select() 方法,這個方法用于選擇文本框中的所有文本。
??在調用 select() 方法時,大多數瀏覽器(Opera 除外)都會將焦點設置到文本框中。這個方法不接受參數,可以在任何時候被調用。示例:

var textbox = document.forms[0].elements["textbox1"];
textbox.select();

??在文本框獲得焦點時選擇其所有文本,這是一種非常常見的做法,特別是在文本框包含默認值的時候。因為這樣做可以讓用戶不必一個一個地刪除文本。下面展示了實現這一操作的代碼。

EventUtil.addHandler(textbox, "focus", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);

    target.select();
});

??將上面的代碼應用到文本框之后,只要文本框獲得焦點,就會選擇其中所有的文本。這種技術能夠較大幅度地提升表單的易用性。

1. 選擇(select)事件

??與 select() 方法對應的,是一個 select 事件。在選擇了文本框中的文本時,就會觸發 select 事件。
??不過,到底什么時候觸發 select 事件,還會因瀏覽器而異。在 IE9+、Opera、Firefox、Chrome 和 Safari 中,只有用戶選擇了文本(而且要釋放鼠標),才會觸發 select 事件。而在 IE8 及更早版本中,
只要用戶選擇了一個字母(不必釋放鼠標),就會觸發 select 事件。
??另外,在調用 select() 方法時也會觸發 select 事件。下面是一個簡單的例子。

var textbox = document.forms[0].elements["textbox1"];

EventUtil.addHandler(textbox, "select", function(event){
    alert("Text selected" + textbox.value);
});
2. 取得選擇的文本

??雖然通過select 事件我們可以知道用戶什么時候選擇了文本,但仍然不知道用戶選擇了什么文本。HTML5 通過一些擴展方案解決了這個問題,以便更順利地取得選擇的文本。
??該規范采取的辦法是添加兩個屬性:selectionStart 和 selectionEnd。這兩個屬性中保存的是基于 0 的數值,表示所選擇文本的范圍(即文本選區開頭和結尾的偏移量)。因此,要取得用戶在文本框中選擇的文本,可以使用如下代碼。

function getSelectedText(textbox){
    return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
}

??因 為 substring() 方法基于字符串的偏移量執行操作,所以將 selectionStart 和 selectionEnd 直接傳給它就可以取得選中的文本。

??IE9+、Firefox、Safari、Chrome 和 Opera 都支持這兩個屬性。IE8 及之前版本不支持這兩個屬性,而是提供了另一種方案。
??IE8 及更早的版本中有一個 document.selection 對象,其中保存著用戶在整個文檔范圍內選擇的文本信息;也就是說,無法確定用戶選擇的是頁面中哪個部位的文本。不過,在與 select 事件一起使用的時候,可以假定是用戶選擇了文本框中的文本,因而觸發了該事件。要取得選擇的文本,首先必須創建一個范圍,然后再將文本從其中提取出來,如下面的例子所示。

function getSelectedText(textbox){
    if (typeof textbox.selectionStart == "number"){
        return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
    } else if (document.selection){
        return document.selection.createRange().text;
    }
}

??這里修改了前面的函數,包括了在 IE 中取得選擇文本的代碼。注意,調用 document.selection 時,不需要考慮 textbox 參數。

3. 選擇部分文本

??HTML5 也為選擇文本框中的部分文本提供了解決方案,即最早由 Firefox 引入的 setSelectionRange() 方法。
??現在除 select() 方法之外,所有文本框都有一個 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 支持這種方案。
??IE8 及更早版本支持使用范圍選擇部分文本。要選擇文本框中的部分文本,必須首先使用 IE 在所有文本框上提供的 createTextRange() 方法創建一個范圍,并將其放在恰當的位置上。然后,再使用 moveStart() 和 moveEnd() 這兩個范圍方法將范圍移動到位。
??不過,在調用這兩個方法以前,還必須使用 collapse() 將范圍折疊到文本框的開始位置。此時,moveStart() 將范圍的起點和終點移動到了相同的位置,只要再給 moveEnd() 傳入要選擇的字符總數即可。最后一步,就是使用范圍的 select() 方法選擇文本,如下面的例子所示。

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", 3);
range.select(); //"o w"

??與在其他瀏覽器中一樣,要想在文本框中看到文本被選擇的效果,必須讓文本框獲得焦點。為了實現跨瀏覽器編程,可以將上述兩種方案組合起來,如下面的例子所示。

function selectText(textbox, startIndex, stopIndex){
    if (textbox.setSelectionRange){
        textbox.setSelectionRange(startIndex, stopIndex);
    } else if (textbox.createTextRange){
        var range = textbox.createTextRange();
        range.collapse(true);
        range.moveStart("character", startIndex);
        range.moveEnd("character", stopIndex - startIndex);
        range.select();
     } 
     textbox.focus();
 } 

??這個 selectText() 函數接收三個參數:要操作的文本框、要選擇文本中第一個字符的索引和要選擇文本中最后一個字符之后的索引。
??首先,函數測試了文本框是否包含 setSelectionRange() 方法。如果有,則使用該方法。否則,檢測文本框是否支持 createTextRange() 方法。如果支持,則通過創建范圍來實現選擇。最后一步,就是為文本框設置焦點,以便用戶看到文本框中選擇的文本。可以像下面這樣使用 selectText() 方法。

textbox.value = "Hello world!"
// 選擇所有文本
selectText(textbox, 0, textbox.value.length); // "Hello world!"
// 選擇前 3 個字符
selectText(textbox, 0, 3); // "Hel"
// 選擇第 4 到第 6 個字符
selectText(textbox, 4, 7); // "o w"

??選擇部分文本的技術在實現高級文本輸入框時很有用,例如提供自動完成建議的文本框就可以使用這種技術。

2.2、過濾輸入

??我們經常會要求用戶在文本框中輸入特定的數據,或者輸入特定格式的數據。例如:必須包含某些字符,或者必須匹配某種模式。由于文本框在默認情況下沒有提供多少驗證數據的手段,因此必須使用 JavaScript 來完成此類過濾輸入的操作。
??而綜合運用事件和 DOM 手段,就可以將普通的文本框轉換成能夠理解用戶輸入數據的功能型空間。

1. 屏蔽字符

??有時候,我們需要用戶輸入的文本中包含或不包含某些字符。例如,電話號碼中不能包含非數值字符。
??如前所述,響應向文本框中插入字符操作的是 keypress 事件。因此,可以通過阻止這個事件的默認行為來屏蔽此類字符。在極端情況下,可以通過下列代碼屏蔽所有案件操作。

EventUtil.addHandler(textbox, "keypress", function(event) {
    event = EventUtil.getEvent(event);
    EventUtil.preventDefault(event);
});

??運行以上代碼后,由于所有案件操作都將被屏蔽,結果會導致文本框變成只讀的。如果只想屏蔽特定的字符,則需要檢測 keypress 事件對象的字符編碼,然后再決定如何相應。例如:下列代碼只允許用戶輸入數值。

EventUtil.addHandler(textbox, "keypress", function(event) {
    event = EventUtil.getEvent(event);
    var charCode = EventUtil.getCharCode(event);

    if (!/\d/.test(String.fromCharCode(charCode))) {
        EventUtil.preventDefault(event);
    }
});

??在上述例子中,我們使用 EventUtil.getCharCode() 實現了跨瀏覽器取得字符編碼。然后,使用 String.fromCharCode() 將字符編碼轉換成字符串,在使用正則表達式 /\d/ 來測試該字符串,從而確定用戶輸入的是不是數值。如果測試失敗,那么就是用 EventUtil.preventDefault() 屏蔽按鍵事件。結果,文本框就會忽略所有輸入的非數值。

??雖然理論上只應該在用戶按下字符鍵時才觸發 keypress 事件,但有些瀏覽器也會對其他鍵觸發此事件。
??Firefox 和 Safari(3.1 版本以前)會對向上鍵、向下鍵、退格鍵和刪除鍵觸發 keypress 事件;
??Safari 3.1 及更新版本則不會對這些鍵觸發 keypress 事件。這意味著,僅考慮到屏蔽不是數值的字符還不夠,還要避免屏蔽這些極為常用和必要的鍵。所幸的是,要檢測這些鍵并不困難。
??在 Firefox 中,所有由非字符鍵觸發的 keypress 事件對應的字符編碼為 0,而在 Safari 3 以前的版本中,對應的字符編碼全部為 8。為了讓代碼更通用,只要不屏蔽那些字符編碼小于 10 的鍵即可。故而,可以將上面的函數重寫成如下所示。

EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);

    if (!/\d/.test(String.fromCharCode(charCode)) && charCode > 9){
        EventUtil.preventDefault(event);
    }
});

??這樣,我們的事件處理程序就可以適用所有瀏覽器了,即可以屏蔽非數值字符,但不屏蔽那些也會觸發 keypress 事件的基本按鍵。

??除此之外,還有一個問題需要處理:復制、粘貼及其他操作還要用到 Ctrl 鍵。在除 IE 之外的所有瀏覽器中,前面的代碼也會屏蔽 Ctrl+C、Ctrl+V,以及其他使用 Ctrl 的組合鍵。因此,最后還要添加一個檢測條件,以確保用戶沒有按下 Ctrl 鍵,如下面的例子所示。

EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);

    if (!/\d/.test(String.fromCharCode(charCode)) && charCode > 9 && !event.ctrlKey){
        EventUtil.preventDefault(event);
    }
});

??經過最后一點修改,就可以確保文本框的行為完全正常了。在這個例子的基礎上加以修改和調整,就可以將同樣的技術運用于放過和屏蔽任何輸入文本框的字符。

2. 操作剪貼板

??IE 是第一個支持與剪貼板相關事件,以及通過 JavaScript 訪問剪貼板數據的瀏覽器。IE 的實現成為了事實上的標準,不僅 Safari 2、Chrome 和 Firefox 3 也都支持類似的事件和剪貼板訪問(Opera 不支持通過 JavaScript 訪問剪貼板),HTML 5 后來也把剪貼板事件納入了規范。下列就是 6 個剪貼板事件。

  • beforecopy:在發生復制操作前觸發。
  • copy:在發生復制操作時觸發。
  • beforecut:在發生剪切操作前觸發。
  • cut:在發生剪切操作時觸發。
  • beforepaste:在發生粘貼操作前觸發。
  • paste:在發生粘貼操作時觸發。

??由于沒有針對剪貼板操作的標準,這些事件及相關對象會因瀏覽器而異。在 Safari、Chrome 和 Firefox 中,beforecopy、beforecut 和 beforepaste 事件只會在顯示針對文本框的上下文菜單(預期將發生剪貼板事件)的情況下觸發。
??但是,IE 則會在觸發 copy、cut 和 paste 事件之前先行觸發這些事件。
??至于 copy、cut 和 paste 事件,只要是在上下文菜單中選擇了相應選項,或者使用了相應的鍵盤組合鍵,所有瀏覽器都會觸發它們。
??在實際的事件發生之前,通過 beforecopy、beforecut 和 beforepaste 事件可以在向剪貼板發送數據,或者從剪貼板取得數據之前修改數據。不過,取消這些事件并不會取消對剪貼板的操作——只有取消 copy、cut 和 paste 事件,才能阻止相應操作發生。
??要訪問剪貼板中的數據,可以使用 clipboardData 對象:在 IE 中,這個對象是 window 對象的屬性;而在 Firefox 4+、Safari 和 Chrome 中,這個對象是相應 event 對象的屬性。
??但是,在 Firefox、Safari 和 Chorme 中,只有在處理剪貼板事件期間 clipboardData 對象才有效,這是為了防止對剪貼板的未授權訪問;在 IE 中,則可以隨時訪問 clipboardData 對象。為了確保跨瀏覽器兼容性,最好只在發生剪貼板事件期間使用這個對象。

??這個 clipboardData 對象有三個方法:getData()、setData() 和 clearData()。

??getData() 用于從剪貼板中取得數據,它接受一個參數,即要取得的數據的格式。在 IE 中,有兩種數據格式:"text" 和"URL"。在 Firefox、Safari 和 Chrome 中,這個參數是一種 MIME 類型;不過,可以用"text"代表 "text/plain"。

??setData() 方法的第一個參數也是數據類型,第二個參數是要放在剪貼板中的文本。對于第一個參數,IE 照樣支持"text"和"URL",而 Safari 和 Chrome 仍然只支持 MIME 類型。
??但是,與 getData()方法不同的是,Safari 和 Chrome 的 setData() 方法不能識別"text"類型。這兩個瀏覽器在成功將文本放到剪貼板中后,都會返回 true;否則,返回 false。為了彌合這些差異,我們可以向 EventUtil 中再添加下列方法。

var EventUtil = {
    // 省略的代碼
    getClipboardText: function(event){
        var clipboardData = (event.clipboardData || window.clipboardData);
        return clipboardData.getData("text");
    },
    // 省略的代碼

    setClipboardText: function(event, value){
        if (event.clipboardData){
            return event.clipboardData.setData("text/plain", value); 
        } else if (window.clipboardData){
            return window.clipboardData.setData("text", value);
        }
    },
    // 省略的代碼
};

??這里的 getClipboardText() 方法相對簡單;它只要確定 clipboardData 對象的位置,然后再以"text"類型調用 getData() 方法即可。
??相應地,setClipboardText() 方法則要稍微復雜一些。在
取得 clipboardData 對象之后,需要根據不同的瀏覽器實現為 setData() 傳入不同的類型(對于 Safari 和 Chrome,是"text/plain";對于 IE,是"text")。

??在需要確保粘貼到文本框中的文本中包含某些字符,或者符合某種格式要求時,能夠訪問剪貼板是非常有用的。例如,如果一個文本框只接受數值,那么就必須檢測粘貼過來的值,以確保有效。在 paste 事件中,可以確定剪貼板中的值是否有效,如果無效,就可以像下面示例中那樣,取消默認的行為。

EventUtil.addHandler(textbox, "paste", function(event){
    event = EventUtil.getEvent(event);
    var text = EventUtil.getClipboardText(event);

    if (!/^\d*$/.test(text)){
        EventUtil.preventDefault(event);
    }
});

??在這里,onpaste 事件處理程序可以確保只有數值才會被粘貼到文本框中。如果剪貼板的值與正則表達式不匹配,則會取消粘貼操作。
??Firefox、Safari 和 Chrome 只允許在 onpaste 事件處理程序中訪問 getData() 方法。
??由于并非所有瀏覽器都支持訪問剪貼板,所以更簡單的做法是屏蔽一或多個剪貼板操作。在支持 copy、cut 和 paste 事件的瀏覽器中(IE、Safari、Chrome 和 Firefox 3 及更高版本),很容易阻止這些事件的默認行為。在 Opera 中,則需要阻止那些會觸發這些事件的按鍵操作,同時還要阻止在文本框中顯示上下文菜單。

2.3、 自動切換焦點

??使用 JavaScript 可以從多個方面增強表單字段的易用性。其中,最常見的一種方式就是在用戶填寫完當前字段時,自動將焦點切換到下一個字段。通常,在自動切換焦點之前,必須知道用戶已經輸入了
既定長度的數據(例如電話號碼)。例如,美國的電話號碼通常會分為三部分:區號、局號和另外 4 位數字。為取得完整的電話號碼,很多網頁中都會提供下列 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"> 

??為增強易用性,同時加快數據輸入,可以在前一個文本框中的字符達到最大數量后,自動將焦點切換到下一個文本框。換句話說,用戶在第一個文本框中輸入了 3 個數字之后,焦點就會切換到第二個文本框,再輸入 3 個數字,焦點又會切換到第三個文本框。這種“自動切換焦點”的功能,可以通過下列代碼實現:

(function(){

    function tabForward(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);

        if (target.value.length == target.maxLength){
            var form = target.form;
            for (var 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;
                }
            }
        }
    }

    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);
})();

??開始的 tabForward() 函數是實現“自動切換焦點”的關鍵所在。這個函數通過比較用戶輸入的值與文本框的 maxlength 特性,可以確定是否已經達到最大長度。如果這兩個值相等(因為瀏覽器最終會強制它們相等,因此用戶絕不會多輸入字符),則需要查找表單字段集合,直至找到下一個文本框。
??找到下一個文本框之后,則將焦點切換到該文本框。然后,我們把這個函數指定為每個文本框的 onkeyup 事件處理程序。由于 keyup 事件會在用戶輸入了新字符之后觸發,所以此時是檢測文本框中內容長度的最佳時機。這樣一來,用戶在填寫這個簡單的表單時,就不必再通過按制表鍵切換表單字段和提交表單了。
??不過請記住,這些代碼只適用于前面給出的標記,而且沒有考慮隱藏字段。

2.4、 HTML5 約束驗證API

??為了在將表單提交到服務器之前驗證數據,HTML5 新增了一些功能。
??有了這些功能,即便 JavaScript 被禁用或者由于種種原因未能加載,也可以確保基本的驗證。
??換句話說,瀏覽器自己會根據標記中的規則執行驗證,然后自己顯示適當的錯誤消息(完全不用 JavaScript 插手)。當然,這個功能只有在支持 HTML5 這部分內容的瀏覽器中才有效,這些瀏覽器有 Firefox 4+、Safari 5+、Chrome 和 Opera 10+。
??只有在某些情況下表單字段才能進行自動驗證。具體來說,就是要在 HTML 標記中為特定的字段指定一些約束,然后瀏覽器才會自動執行表單驗證。

1. 必填字段

??第一種情況是在表單字段中指定了 required 屬性,如下面的例子所示:

<input type="text" name="username" required>

??任何標注有 required 的字段,在提交表單時都不能空著。

??這個屬性適用于<input>、<textarea>和<select>字段(Opera 11 及之前版本還不支持<select>的 required 屬性)。在 JavaScript 中,通過對應的 required 屬性,可以檢查某個表單字段是否為必填字段。

var isUsernameRequired = document.forms[0].elements["username"].required;

??另外,使用下面這行代碼可以測試瀏覽器是否支持 required 屬性。

var isRequiredSupported = "required" in document.createElement("input");

??以上代碼通過特性檢測來確定新創建的<input>元素中是否存在 required 屬性。
??對于空著的必填字段,不同瀏覽器有不同的處理方式。Firefox 4 和 Opera 11 會阻止表單提交并在相應字段下方彈出幫助框,而 Safari(5 之前)和 Chrome(9 之前)則什么也不做,而且也不阻止表單提交。

2. 其他輸入類型

??HTML5 為<input>元素的 type 屬性又增加了幾個值。這些新的類型不僅能反映數據類型的信息,而且還能提供一些默認的驗證功能。
??其中,"email"和"url"是兩個得到支持最多的類型,各瀏覽器也都為它們增加了定制的驗證機制。例如:

<input type="email" name ="email">
<input type="url" name="homepage">

??顧名思義,"email"類型要求輸入的文本必須符合電子郵件地址的模式,而"url"類型要求輸入的文本必須符合 URL 的模式。
??不過,本節前面提到的瀏覽器在恰當地匹配模式方面都存在問題。最明顯的是"-@-"會被當成一個有效的電子郵件地址。瀏覽器開發商還在解決這些問題。

??要檢測瀏覽器是否支持這些新類型,可以在 JavaScript 創建一個<input>元素,然后將 type 屬性設置為"email"或"url",最后再檢測這個屬性的值。不支持它們的舊版本瀏覽器會自動將未知的值設置為"text",而支持的瀏覽器則會返回正確的值。例如:

var input = document.createElement("input");
input.type = "email";
var isEmailSupported = (input.type == "email");

??要注意的是,如果不給<input>元素設置 required 屬性,那么空文本框也會驗證通過。另一方面,設置特定的輸入類型并不能阻止用戶輸入無效的值,只是應用某些默認的驗證而已。

3. 數值范圍

??除了"email"和"url",HTML5 還定義了另外幾個輸入元素。這幾個元素都要求填寫某種基于數字的值:"number"、"range"、"datetime"、"datetime-local"、 "date"、"month"、"week",還有"time"。
??瀏覽器對這幾個類型的支持情況并不好,因此如果真想選用的話,要特別小心。目前,瀏覽器開發商主要關注更好的跨平臺兼容性以及更多的邏輯功能。
??因此,本節介紹的內容某種程度上有些超前,不一定馬上就能在實際開發中使用。
??對所有這些數值類型的輸入元素,可以指定 min 屬性(最小的可能值)、max 屬性(最大的可能值)和 step 屬性(從 min 到 max 的兩個刻度間的差值)。例如,想讓用戶只能輸入 0 到 100 的值,而且這
個值必須是 5 的倍數,可以這樣寫代碼:

<input type="number" min="0" max="100" step="5" name="count">

??在不同的瀏覽器中,可能會也可能不會看到能夠自動遞增和遞減的數值調節按鈕(向上和向下按鈕)。

??以上這些屬性在 JavaScript 中都能通過對應的元素訪問(或修改)。

??此外,還有兩個方法:stepUp() 和 stepDown(),都接收一個可選的參數:要在當前值基礎上加上或減去的數值。(默認是加或減 1。)
??這兩個方法還沒有得到任何瀏覽器支持,但下面的例子演示了它們的用法。

input.stepUp(); // 加 1
input.stepUp(5); // 加 5
input.stepDown(); // 減 1
input.stepDown(10); // 減 10
4. 輸入模式

??HTML5 為文本字段新增了 pattern 屬性。這個屬性的值是一個正則表達式,用于匹配文本框中的值。例如,如果只想允許在文本字段中輸入數值,可以像下面的代碼一樣應用約束:

<input type="text" pattern="\d+" name="count">

??注意,模式的開頭和末尾不用加^和$符號(假定已經有了)。這兩個符號表示輸入的值必須從頭到尾都與模式匹配。

??與其他輸入類型相似,指定 pattern 也不能阻止用戶輸入無效的文本。這個模式應用給值,瀏覽器來判斷值是有效,還是無效。在 JavaScript 中可以通過 pattern 屬性訪問模式。

var pattern = document.forms[0].elements["count"].pattern;

??使用以下代碼可以檢測瀏覽器是否支持 pattern 屬性。

var isPatternSupported = "pattern" in document.createElement("input");
5. 檢測有效性

??使用 checkValidity() 方法可以檢測表單中的某個字段是否有效。所有表單字段都有個方法,如果字段的值有效,這個方法返回 true,否則返回 false。
??字段的值是否有效的判斷依據是本節前面介紹過的那些約束。換句話說,必填字段中如果沒有值就是無效的,而字段中的值與 pattern 屬性不匹配也是無效的。例如:

if (document.forms[0].elements[0].checkValidity()){
    // 字段有效,繼續
} else {
    // 字段無效
} 

??要檢測整個表單是否有效,可以在表單自身調用 checkValidity() 方法。如果所有表單字段都有效,這個方法返回 true;即使有一個字段無效,這個方法也會返回 false。

if(document.forms[0].checkValidity()){
    // 表單有效,繼續
} else {
    // 表單無效
}

??與 checkValidity() 方法簡單地告訴你字段是否有效相比,validity 屬性則會告訴你為什么字段有效或無效。這個對象中包含一系列屬性,每個屬性會返回一個布爾值。

  • customError :如果設置了setCustomValidity(),則為true,否則返回false。
  • patternMismatch:如果值與指定的 pattern 屬性不匹配,返回true。
  • rangeOverflow:如果值比max 值大,返回true。
  • rangeUnderflow:如果值比min 值小,返回true。
  • stepMisMatch:如果 min 和 max 之間的步長值不合理,返回true。
  • tooLong:如果值的長度超過了maxlength 屬性指定的長度,返回true。有的瀏覽器(如Firefox 4)會自動約束字符數量,因此這個值可能永遠都返回false。
  • typeMismatch:如果值不是"mail"或"url"要求的格式,返回true。
  • valid:如果這里的其他屬性都是false,返回 true。checkValidity() 也要求相同的值。
  • valueMissing:如果標注為 required 的字段中沒有值,返回true。

??因此,要想得到更具體的信息,就應該使用 validity 屬性來檢測表單的有效性。下面是一個例子。

if (input.validity && !input.validity.valid){
    if (input.validity.valueMissing){
        alert("Please specify a value.")
    } else if (input.validity.typeMismatch){
        alert("Please enter an email address.");
    } else {
        alert("Value is invalid.");
    }
}
6. 禁用驗證

??通過設置 novalidate 屬性,可以告訴表單不進行驗證。

<form method="post" action="signup.php" novalidate>
    <!--這里插入表單元素-->
</form>

??在 JavaScript 中使用 noValidate 屬性可以取得或設置這個值,如果這個屬性存在,值為 true,如果不存在,值為 false。

document.forms[0].noValidate = true; // 禁用驗證

??如果一個表單中有多個提交按鈕,為了指定點擊某個提交按鈕不必驗證表單,可以在相應的按鈕上添加 formnovalidate 屬性。

<form method="post" action="foo.php">
    <!--這里插入表單元素-->
    <input type="submit" value="Regular Submit"> 

    <input type="submit" formnovalidate name="btnNoValidate"
 value="Non-validating Submit">

</form>

??在上述例子中,點擊第一個提交按鈕會像往常一樣驗證表單,而點擊第二個按鈕則會不經過驗證而提交表單。使用 JavaScript 也可以設置這個屬性。

// 禁用驗證
document.forms[0].elements["btnNoValidate"].formNoValidate = true;

3、 選擇框腳本

??選擇框是通過<select>和<option>元素創建的。為了方便與這個控件交互,除了所有表單字段共有的屬性和方法外,HTMLSelectElement 類型還提供了下列屬性和方法。

  • add(newOption, relOption):向控件中插入新<option>元素,其位置在相關項(relOption)之前。
  • multiple:布爾值,表示是否允許多項選擇;等價于 HTML 中的 multiple 特性。
  • options:控件中所有<option>元素的 HTMLCollection。
  • remove(index):移除給定位置的選項。
  • selectedIndex:基于 0 的選中項的索引,如果沒有選中項,則值為-1。對于支持多選的控件,只保存選中項中第一項的索引。
  • size:選擇框中可見的行數;等價于 HTML 中的 size 特性。

??選擇框的 type 屬性不是"select-one",就是"select-multiple",這取決于 HTML 代碼中有沒有 multiple 特性。選擇框的 value 屬性由當前選中項決定,相應規則如下。

  • 如果沒有選中的項,則選擇框的 value 屬性保存空字符串。
  • 如果有一個選中項,而且該項的 value 特性已經在 HTML 中指定,則選擇框的 value 屬性等于選中項的 value 特性。即使 value 特性的值是空字符串,也同樣遵循此條規則。
  • 如果有一個選中項,但該項的 value 特性在 HTML 中未指定,則選擇框的 value 屬性等于該項的文本。
  • 如果有多個選中項,則選擇框的 value 屬性將依據前兩條規則取得第一個選中項的值。

??以下面的選擇框為例:

<select name="location" id="selLocation">
    <option value="Sunnyvale, CA">Sunnyvale</option>
    <option value="Los Angeles, CA">Los Angeles</option>
    <option value="Mountain View, CA">Mountain View</option>
    <option value="">China</option>
    <option>Australia</option>
</select>

??如果用戶選擇了其中第一項,則選擇框的值就是"Sunnyvale, CA"。如果文本為"China"的選項被選中,則選擇框的值就是一個空字符串,因為其 value 特性是空的。如果選擇了最后一項,那么由于<option>中沒有指定 value 特性,則選擇框的值就是"Australia"。

??在 DOM 中,每個<option>元素都有一個 HTMLOptionElement 對象表示。為便于訪問數據,HTMLOptionElement 對象添加了下列屬性:

  • index:當前選項在 options 集合中的索引。
  • label:當前選項的標簽;等價于 HTML 中的 label 特性。
  • selected:布爾值,表示當前選項是否被選中。將這個屬性設置為 true 可以選中當前選項。
  • text:選項的文本。
  • value:選項的值(等價于 HTML 中的 value 特性)。

??其中大部分屬性的目的,都是為了方便對選項數據的訪問。雖然也可以使用常規的 DOM 功能來訪問這些信息,但效率是比較低的,如下面的例子所示:

var selectbox = document.forms[0].elements["location"];

// 不推薦
var text = selectbox.options[0].firstChild.nodeValue; // 選項的文本
var value = selectbox.options[0].getAttribute("value"); // 選項的值

??以上代碼使用標準 DOM 方法,取得了選擇框中第一項的文本和值。可以與下面使用選項屬性的代碼作一比較:

var selectbox = document.forms[0]. elements["location"];

// 推薦
var text = selectbox.options[0].text; // 選項的文本
var value = selectbox.options[0].value; // 選項的值

??在操作選項時,我們建議最好是使用特定于選項的屬性,因為所有瀏覽器都支持這些屬性。在將表單控件作為 DOM 節點的情況下,實際的交互方式則會因瀏覽器而異。我們不推薦使用標準 DOM 技術
修改<option>元素的文本或者值。

??最后,我們還想提醒讀者注意一點:選擇框的 change 事件與其他表單字段的 change 事件觸發的條件不一樣。其他表單字段的 change 事件是在值被修改且焦點離開當前字段時觸發,而選擇框的
change 事件只要選中了選項就會觸發。
??不同瀏覽器下,選項的 value 屬性返回什么值也存在差別。但是,在所有瀏覽器中,value 屬性始終等于 value 特性。在未指定 value 特性的情況下,IE8 會返回空字符串,而 IE9+、Safari、Firefox、Chrome 和 Opera 則會返回與 text 特性相同的值。

3.1、 選擇選項

??對于只允許選擇一項的選擇框,訪問選中項的最簡單方式,就是使用選擇框的 selectedIndex 屬性,如下面的例子所示:

var selectedOption = selectbox.options[selectbox.selectedIndex];

??取得選中項之后,可以像下面這樣顯示該選項的信息:

var selectedIndex = selectbox.selectedIndex;
var selectedOption = selectbox.options[selectedIndex];
alert("Selected index: " + selectedIndex + 
      "\nSelected text: " + selectedOption.text +
      "\nSelected value: " + selectedOption.value); 

??這里,我們通過一個警告框顯示了選中項的索引、文本和值。

??對于可以選擇多項的選擇框,selectedfIndex 屬性就好像只允許選擇一項一樣。設置 selectedIndex 會導致取消以前的所有選項并選擇指定的那一項,而讀取 selectedIndex 則只會返回選中項中第一項的索引值。

??另一種選擇選項的方式,就是取得對某一項的引用,然后將其 selected 屬性設置為 true。例如,下面的代碼會選中選擇框中的第一項:

selectbox.options[0].selected = true;

??與 selectedIndex 不同,在允許多選的選擇框中設置選項的 selected 屬性,不會取消對其他選中項的選擇,因而可以動態選中任意多個項。但是,如果是在單選選擇框中,修改某個選項的 selected 屬性則
會取消對其他選項的選擇。需要注意的是,將 selected 屬性設置為 false 對單選選擇框沒有影響。

??實際上,selected 屬性的作用主要是確定用戶選擇了選擇框中的哪一項。要取得所有選中的項,可以循環遍歷選項集合,然后測試每個選項的 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){
            result.push(option);
        }
    }

    return result;
}

??這個函數可以返回給定選擇框中選中項的一個數組。首先,創建一個將包含選中項的數組,然后使用 for 循環迭代所有選項,同時檢測每一項的 selected 屬性。如果有選項被選中,則將其添加到 result 數組中。最后,返回包含選中項的數組。下面是一個使用 getSelectedOptions() 函數取得選中項的示例。

var selectbox = document.getElementById("selLocation");
var selectedOptions = getSelectedOptions(selectbox);
var message = "";

for (var i=0, len=selectedOptions.length; i < len; i++){
    message += "Selected index: " + selectedOptions[i].index +
               "\nSelected text: " + selectedOptions[i].text +
               "\nSelected value: " + selectedOptions[i].value + "\n\n";
 }
alert(message);

??在上述例子中,我們首先從一個選擇框中取得了選中項。然后,使用 for 循環構建了一條消息,包含所有選中項的信息:每一項的索引、文本和值。這種技術適用于單選和多選選擇框。

3.2、 添加選項

??可以使用 JavaScript 動態創建選項,并將它們添加到選擇框中。添加選項的方式有很多,第一種方式就是使用如下所示的 DOM 方法。

var newOption = document.createElement("option");
newOption.appendChild(document.createTextNode("Option text"));
newOption.setAttribute("value", "Option value");

selectbox.appendChild(newOption);

??以上代碼創建了一個新的<option>元素,然后為它添加了一個文本節點,并設置其 value 特性,最后將它添加到了選擇框中。添加到選擇框之后,用戶立即就可以看到新選項。

??第二種方式是使用 Option 構造函數來創建新選項,這個構造函數是 DOM 出現之前就有的,一直遺留到現在。Option 構造函數接受兩個參數:文本(text)和值(value);第二個參數可選。雖然這個構造函數會創建一個 Object 的實例,但兼容 DOM 的瀏覽器會返回一個<option>元素。
??換句話說,在這種情況下,我們仍然可以使用 appendChild() 將新選項添加到選擇框中。來看下面的例子。

var newOption = new Option("Option text", "Option value");
selectbox.appendChild(newOption); // 在 IE8 及之前版本中有問題

??這種方式在除 IE 之外的瀏覽器中都可以使用。由于存在 bug,IE 在這種方式下不能正確設置新選項的文本。

??第三種添加新選項的方式是使用選擇框的 add() 方法。DOM 規定這個方法接受兩個參數:要添加的新選項和將位于新選項之后的選項。
??如果想在列表的最后添加一個選項,應該將第二個參數設置為 null。
??在 IE 對 add() 方法的實現中,第二個參數是可選的,而且如果指定,該參數必須是新選項之后選項的索引。兼容 DOM 的瀏覽器要求必須指定第二個參數,因此要想編寫跨瀏覽器的代碼,就不能只傳入一個參數。這時候,為第二個參數傳入 undefined,就可以在所有瀏覽器中都將新選項插入到列表最后了。來看一個例子。

var newOption = new Option("Option text", "Option value");
selectbox.add(newOption, undefined); // 最佳方案

??在 IE 和兼容 DOM 的瀏覽器中,上面的代碼都可以正常使用。

??如果你想將新選項添加到其他位置(不是最后一個),就應該使用標準的 DOM 技術和 insertBefore() 方法。就和在 HTML 中一樣,此時也不一定要為選項指定值。換句話說,只為 Option 構造函數傳入一個參數(選項的文本)也沒有問題。

3.3、 移除選項

??與添加選項類似,移除選項的方式也有很多種。

??首先,可以使用 DOM 的 removeChild() 方法,為其傳入要移除的選項,如下面的例子所示:

selectbox.removeChild(selectbox.options[0]); // 移除第一個選項

??其次,可以使用選擇框的 remove() 方法。這個方法接受一個參數,即要移除選項的索引,如下面的例子所示:

selectbox.remove(0); // 移除第一個選項

??最后一種方式,就是將相應選項設置為 null。這種方式也是 DOM 出現之前瀏覽器的遺留機制。例如:

selectbox.options[0] = null; // 移除第一個選項

??要清除選擇框中所有的項,需要迭代所有選項并逐個移除它們,如下面的例子所示:

function clearSelectbox(selectbox){
    for(var i=0, len=selectbox.options.length; i < len; i++){
        selectbox.remove(i);
    }
}

??這個函數每次只移除選擇框中的第一個選項。由于移除第一個選項后,所有后續選項都會自動向上移動一個位置,因此重復移除第一個選項就可以移除所有選項了。

3.4、 移動和重排選項

??在 DOM 標準出現之前,將一個選擇框中的選項移動到另一個選擇框中是非常麻煩的。整個過程要涉及從第一個選擇框中移除選項,然后以相同的文本和值創建新選項,最后再將新選項添加到第二個選擇框中。
??而使用 DOM 的 appendChild() 方法,就可以將第一個選擇框中的選項直接移動到第二個選擇框中。
??我們知道,如果為 appendChild() 方法傳入一個文檔中已有的元素,那么就會先從該元素的父節點中移除它,再把它添加到指定的位置。下面的代碼展示了將第一個選擇框中的第一個選項移動到第二個選擇框中的過程。

var selectbox1 = document.getElementById("selLocations1");
var selectbox2 = document.getElementById("selLocations2");

selectbox2.appendChild(selectbox1.options[0]);

??移動選項與移除選項有一個共同之處,即會重置每一個選項的 index 屬性。

??重排選項次序的過程也十分類似,最好的方式仍然是使用 DOM 方法。要將選擇框中的某一項移動到特定位置,最合適的 DOM 方法就是 insertBefore();
??appendChild() 方法只適用于將選項添加到選擇框的最后。要在選擇框中向前移動一個選項的位置,可以使用以下代碼:

var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index-1]); 

??以上代碼首先選擇了要移動的選項,然后將其插入到了排在它前面的選項之前。實際上,第二行代碼對除第一個選項之外的其他選項是通用的。類似地,可以使用下列代碼將選擇框中的選項向后移動一
個位置。

var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index+2]);

??以上代碼適用于選擇框中的所有選項,包括最后一個選項。

??IE7 存在一個頁面重繪問題,有時候會導致使用 DOM 方法重排的選項不能馬上正確顯示。

4、 表單序列化

??隨著 Ajax 的出現,表單序列化已經成為一種常見需求。

??在 JavaScript 中,可以利用表單字段的 type 屬性,連同 name 和 value 屬性一起實現對表單的序列化。在編寫代碼之前,有必須先搞清楚在表單提交期間,瀏覽器是怎樣將數據發送給服務器的。

  • 對表單字段的名稱和值進行 URL 編碼,使用和號(&)分隔。
  • 不發送禁用的表單字段。
  • 只發送勾選的復選框和單選按鈕。
  • 不發送 type 為"reset"和"button"的按鈕。
  • 多選選擇框中的每個選中的值單獨一個條目。
  • 在單擊提交按鈕提交表單的情況下,也會發送提交按鈕;否則,不發送提交按鈕。也包括 type為"image"的<input>元素。
  • <select>元素的值,就是選中的<option>元素的 value 特性的值。如果<option>元素沒有value 特性,則是<option>元素的文本值。

??在表單序列化過程中,一般不包含任何按鈕字段,因為結果字符串很可能是通過其他方式提交的。除此之外的其他上述規則都應該遵循。以下就是實現表單序列化的代碼。

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){
                    for (j=0, optLen = field.options.length; j < optLen; j++){ 
                       option = field.options[j];
                        if (option.selected){
                            optValue = "";
                            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(optValue));
                        }
                    }
                }
                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("&");
}

??上面這個 serialize() 函數首先定義了一個名為 parts 的數組,用于保存將要創建的字符串的各個部分。
??然后,通過 for 循環迭代每個表單字段,并將其保存在 field 變量中。在獲得了一個字段的引用之后,使用 switch 語句檢測其 type 屬性。
??序列化過程中最麻煩的就是<select>元素,它可能是單選框也可能是多選框。為此,需要遍歷控件中的每一個選項,并在相應選項被選中的情況下向數組中添加一個值。
??對于單選框,只可能有一個選中項,而多選框則可能有零或多個選中項。這里的代碼適用于這兩種選擇框,至于可選項的數量則是由瀏覽器控制的。
??在找到一個選中項之后,需要確定使用什么值。如果不存在 value 特性,或者雖然存在該特性,但值為空字符串,都要使用選項的文本來代替。
??為檢查這個特性,在 DOM 兼容的瀏覽器中需要使用 hasAttribute() 方法,而在 IE 中需要使用特性的 specified 屬性。
??如果表單中包含<fieldset>元素,則該元素會出現在元素集合中,但沒有 type 屬性。因此,如果 type 屬性未定義,則不需要對其進行序列化。
??同樣,對于各種按鈕以及文件輸入字段也是如此(文件輸入字段在表單提交過程中包含文件的內容;但是,這個字段是無法模仿的,序列化時一般都要忽略)。
??對于單選按鈕和復選框,要檢查其 checked 屬性是否被設置為 false,如果是則退出 switch 語句。如果 checked 屬性為 true,則繼續執行 default 語句,即將當前字段的名稱和值進行編碼,然后添加到 parts 數組中。
??函數的最后一步,就是使用 join() 格式化整個字符串,也就是用和號來分隔每一個表單字段。
??最后,serialize() 函數會以查詢字符串的格式輸出序列化之后的字符串。當然,要序列化成其他格式,也不是什么困難的事。

小結

??雖然 HTML 和 Web 應用自誕生以來已經發生了天翻地覆的變化,但 Web 表單相對卻沒有什么改變。
??使用 JavaScript 可以增強已有的表單字段,從而創造出新的功能,或者提升表單的易用性。為此,表單、表單字段都引入了相應的屬性和方法,以便 JavaScript 使用。下面是本章介紹的幾個概念。

  • 可以使用一些標準或非標準的方法選擇文本框中的全部或部分文本。
  • 大多數瀏覽器都采用了 Firefox 操作選擇文本的方式,但 IE 仍然堅持自己的實現。
  • 在文本框的內容變化時,可以通過偵聽鍵盤事件以及檢測插入的字符,來允許或禁止用戶輸入某些字符。

??除 Opera 之外的所有瀏覽器都支持剪貼板事件,包括 copy、cut 和 paste。其他瀏覽器在實現剪貼板事件時也可以分為幾種不同的情況。

  • IE、Firefox、Chrome 和 Safari 允許通過 JavaScript 訪問剪貼板中的數據,而 Opera 不允許這種訪問方式。
  • 即使是 IE、Chrome 和 Safari,它們各自的實現方式也不相同。
  • Firefox、Safari 和 Chrome 只允許在 paste 事件發生時讀取剪貼板數據,而 IE 沒有這個限制。
  • Firefox、Safari 和 Chrome 只允許在發生剪貼板事件時訪問與剪貼板相關的信息,而 IE 允許在任何時候訪問相關信息。

??在文本框內容必須限制為某些特定字符的情況下,就可以利用剪貼板事件來屏蔽通過粘貼向文本框中插入內容的操作。

??選擇框也是經常要通過 JavaScript 來控制的一個表單字段。由于有了 DOM,對選擇框的操作比以前要方便多了。添加選項、移除選項、將選項從一個選擇框移動到另一個選擇框,甚至對選項進行排序等操作,都可以使用標準的 DOM 技術來實現。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容