這裡特指下拉框,select。但select有兩種形態,由multiple屬性決定。在多選形態下,使用者按住shift鍵就能實現多選,但用得不多,主要是佔空間。那我們著重說說單選形態及其結構。
下拉框是由多種元素組成,通常我們見過select套著option元素,這中間還能夾一層,optgroup就是對option元素進行分組。option元素裡面不能放置其他元素節點,option元素間除了空白或註釋節點,也不能放其他東西。
optgroup只是裝飾用,對提交資料沒有影響,當我們選中某個option元素時,它的selected屬性就變成true,之前被選中的元素的selected屬性變成false,select元素中selectedIndex的值會變成被選元素的序號(它在所有option元素的位置 )。此外,還有一個鮮為人知的屬性selectedOptions,它是對應一個陣列,裝著被選中的元素,那麼它就換成被選中元素。因此DOM操作是一種非常複雜與高消耗的行為,這導致基於虛擬DOM的react庫的誕生。減少不必要的DOM操作,就能大幅提高效能。
select的值就是被選中的option元素的值,如果使用者定義value屬性,那麼這值就是option.value,否則就是option的innerHTML,也就是option.text。這當中存在相容問題,比如有的瀏覽器會對innerHTML進行兩端空白trim操作,有的不會,建議統一使用trim操作。
1 2 3 4 5 6 |
<select name="aaa"> <option value="volvo">Volvo</option> <option value="saab">Saab</option> <option value="opel">Opel</option> <option value="audi">Audi</option> </select> |
option.value的提取方法如下:
1 2 3 4 5 6 7 8 9 10 11 |
//by 司徒正美 var roption = /^<option(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s+value[\s=]/i var valHooks = { "option:get": IEVersion ? function (node) { //在IE11及W3C,如果沒有指定value,那麼node.value預設為node.text(存在trim作),但IE9-10則是取innerHTML(沒trim操作) //specified並不可靠,因此通過分析outerHTML判定使用者有沒有顯示定義value return roption.test(node.outerHTML) ? node.value : node.text.trim() } : function (node) { return node.value } } |
我們在看看如何動態新增option元素。這有兩種方式,1是使用W3C的createElement與appendChild,2是使用new Option及options.add方法。
1. 直接使用select.innerHTML
1 2 |
//by 司徒正美 select.innerHTML = ''; |
執行發現標準瀏覽器如chrome, firefox執行正常,DOM樹為
IE(678)全家都呵呵了:
原因在於IE使用innerHTML給select賦值時會根據/^&/(尖括號中間的字母、數字,引號,空格)匹配的字元都幹掉,無力吐槽。
2. 使用new Option建立select的options,這是比較推薦的方法。
我們先來看看Option構造器是怎麼用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//by 司徒正美 new option(text,value,defaultSelected,selected) /* text:字串,指定option物件的text屬性(即之間的文字) value:字串,指定option物件的value屬性 defaultSelected:布林值,指定option物件的defaultSelected屬性 返回下面selected的預設值 selected:布林值,指定option物件的selected屬性 ture/false 是否選擇 http://www.cnblogs.com/SpringSmallGrass/archive/2013/04/14/3019837.html */ |
除了第一個引數,其他都是可選的,相當於
1 2 3 4 5 |
//by 司徒正美 var option = document.createElement("option") option.text = "xxxx" option.value = "aaa" option.selected = true |
再看options.add方法。options是select元素的一個陣列屬性,裡面裝著所有option元素。add是其上面的一個方法( IE中它也能出現在select元素上),此方法存在相容問題。
1 2 3 4 |
var objSelect = document.getElementById('mySelect'); //新增一個選項 objSelect.add(new Option("文字","值")); //這個只能在IE中有效 objSelect.options.add(new Option("text","value")); //這個相容IE與firefox |
options.add有兩種傳參方式,第一種要來傳入兩個元素,第一個是新option元素,第二個是已有的option元素,新元素會插入到舊元素之前。問題出現在第二個引數預設的情況下:
1 |
objSelect.add(new Option("Label", "Value"), null); |
請注意,在IE6及IE7下請使用不帶null引數的add方法,在FF下請使用帶null引數的方法,IE8下帶不帶都可以。
很奇怪為什麼一定要加null,我猜測add方法裡一定使用了’=== null’來判斷第二的引數或者沒有對引數陣列的長度做驗證。
還有一種傳參方法,第一個是新option元素,第二個是插入位置,不寫預設插入到末尾。
1 2 |
objSelect.add( new Option(txt, val)); //加在末尾 objSelect.add( new Option(txt, val),0);//加在開頭 |
早期IE是不支援傳入兩個元素,只支援傳入元素與插入位置的方式。IE8是兩種方式都支援,對於普通瀏覽器,如果傳入的是索引數值,它不會認為是出錯,還是會新增在最後。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <script type="text/javascript"> //try{先執行的程式碼} catch(err){出錯時執行的程式碼} //select的add方法,第一個引數是需要被新增的option元素,第二個引數決定了被新增的位置 //普通瀏覽器中,第二個引數是一個既有的option物件,新增的option元素被插入到它之前. //ie7瀏覽器中,第二個引數是一個索引,表示第n個既有的option物件. //ie8以上版本,傳入兩種物件它都能識別. //如果沒有第二個引數,則插入在最後. //對於普通瀏覽器,如果傳入的是索引數值,它不會認為是出錯,還是會新增在最後,所以,使用try{}catch{}方法解決這個問題,必須把普通瀏覽器的適用方法放在try裡面. window.onload = function () { var select = document.getElementById("select") var btn = document.getElementById("btn") btn.onclick = function () { //通過下標可以把jquery物件轉換為javascript物件 try { //普通瀏覽器和ie8以上版本執行以下程式碼 select.add(new Option('2.5'), select.options[2]) } catch (err) { //ie7執行以下程式碼 select.add(new Option('2.5'), 2) } } } </script> </head> <body> <select id="select" multiple="multiple" size="5"> <option value="1">One</option> <option value="2">Two</option> <option value="3">Three</option> <option value="4">Four</option> </select> <div> <button type="button" id="btn">Click me!</button> </div> </body> </html> |
3. 使用document.createElement與appendChild。
這是標準DOM API,基本上無所不能。 在以前的IE4中, document只能建立img, area, option三種元素,到了IE5,一般可以程式設計建立幾乎所以元素, 除了frame和iframe。 而且這些新的建立的元素的屬性都是可讀寫的,並且可以程式設計隨意訪問。但是你必須得先把他們先回到他們相應的集合中或者當前文件中你才能使用, 否則會報錯。
看下一個課題,如何找到可以提交的option元素。因為決定一個option能否提交,除了selected屬性外,還有disabled屬性,由於disabled屬性可能出現在select或optgroup元素上,這問題就複雜了。此外selectedOptions陣列屬性並不可靠,不是所有瀏覽器都支援。jQuery在處理這裡也花了不少程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
var valHooks = { "option:get": function (node) { // 見上 }, "select:get": function (node, value) { var option, options = node.options, index = node.selectedIndex, getter = valHooks["option:get"], one = node.type === "select-one" || index < 0, values = one ? null : [], max = one ? index + 1 : options.length, i = index < 0 ? max : one ? index : 0 for (; i < max; i++) { option = options[i] //IE6-9在reset後不會改變selected,需要改用i === index判定 //我們過濾所有disabled的option元素,但在safari5下, //如果設定optgroup為disable,那麼其所有孩子都disable //因此當一個元素為disable,需要檢測其是否顯式設定了disable及其父節點的disable情況 if ((option.selected || i === index) && !option.disabled && (!option.parentNode.disabled || option.parentNode.tagName !== "OPTGROUP") ) { value = getter(option) if (one) { return value } //收集所有selected值組成陣列返回 values.push(value) } } return values } } |
option還有兩個重要的屬性,index是返回當前option元素在此select下所有option元素的位置。label是顯示其文字,行為有點像text,優化級比text高,但有點相容性問題。
1 2 3 4 |
<select id="test"> <option label="Label1">TextContent1</option> <option label="Label2">TextContent2</option> </select> |
通常情況下,IE,opera,safari是顯示Label1與Label2,而chrome, firefox(即使是4.01的版本)是顯示TextContent1與TextContent2,這個古老的bug(見這裡) 至今沒修復。