dojo/dom模塊作為一個基礎模塊,最常用的就是byId方法。除此之外還有isDescendant和setSelectable方法。
dom.byId(myId)方法:
各種前端類庫都免不了與DOM節點打交道,操作DOM的方法千變萬化最終還是要回到原生的那幾個方法中,因為類庫再快也快不過原生。所以在dom.byId方法中,還是要依靠document.getElementById('myId')方法。假如沒有ie,假如不要考慮兼容性,getElementById方法可以完全滿足我們的需求。但是,ie毀了一切,借用美國同事的一句話:Fuck the stupid IE! 兼容性問題有兩條:
- ie8及較低版本中,myId不區分大小寫,所以myid跟myId會返回同樣的結果
- ie7及較低版本中,如果name名稱與給定ID相同的表單元素且表單元素在給定ID元素的前面,那么IE就會返回那個表單元素
這就要求我們在必須判斷一下得到的元素的id是否真與傳入參數相同。判斷方法是利用id屬性或id特性節點:
var te = id && document.getElementById(id) te && (te.attributes.id.value == id || te.id == id)
如果上帝為你關上了一扇門,他一定會為你打開另一扇門(好矯情,就行天無絕人之路嘛)。ie4開始提供了document.all,它是一個代表所有元素的集合。document.all[myId]返回id為myId的一個元素或者包含name為myId的一個類數組。我們可以循環判斷其中的元素是否滿足要求:
var eles = document.all[id]; if(!eles || eles.nodeName){ eles = [eles]; } // if more than 1, choose first with the correct id var i = 0; while((te = eles[i++])){ if((te.attributes && te.attributes.id && te.attributes.id.value == id) || te.id == id){ return te; } }
所以,dojo/dom中的實現根據瀏覽器的不同,有不同的實現:
if(has("ie")){ dom.byId = function(id, doc){ if(typeof id != "string"){ return id; } var _d = doc || win.doc, te = id && _d.getElementById(id); // attributes.id.value is better than just id in case the // user has a name=id inside a form if(te && (te.attributes.id.value == id || te.id == id)){ return te; }else{ var eles = _d.all[id]; if(!eles || eles.nodeName){ eles = [eles]; } // if more than 1, choose first with the correct id var i = 0; while((te = eles[i++])){ if((te.attributes && te.attributes.id && te.attributes.id.value == id) || te.id == id){ return te; } } } }; }else{ dom.byId = function(id, doc){ // inline'd type check. // be sure to return null per documentation, to match IE branch. return ((typeof id == "string") ? (doc || win.doc).getElementById(id) : id) || null; // DOMNode }; }
dom.isDescendant(node, ancestor)方法:
這個方法用來判斷node是否是ancestor的一個子節點,其實就是孩子找父親。孩子找父親比較簡單,而父親找孩子是比較難的,因為子節點一定有父節點,所以只要一級一級的找上去即可。
dom.isDescendant = function(/*DOMNode|String*/ node, /*DOMNode|String*/ ancestor){ try{ node = dom.byId(node); ancestor = dom.byId(ancestor); while(node){ if(node == ancestor){ return true; // Boolean } node = node.parentNode; } }catch(e){ /* squelch, return false */ } return false; // Boolean };
其實還有一個原生的函數也可以滿足要求:element.contains方法,不過這個方法並沒有被納入規范中。但是幾乎所有的瀏覽器都支持,包括IE(最初就是ie增加的該方法,總算做了件好事。。)。所以該方法也可以這樣實現:
dom.isDescendant = function(/*DOMNode|String*/ node, /*DOMNode|String*/ ancestor){ try{ node = dom.byId(node); ancestor = dom.byId(ancestor); return ancestor.contains(node); }catch(e){ /* squelch, return false */ } return false; // Boolean };
dom.setSelectable(node, selectable)方法:
看名字也知道是用來設置一個節點及其自己點是否可選中的。css屬性中可以通過設置“user-select”來控制一個元素是否可選擇,但這個屬性並未被納入標准中去,所以各個瀏覽器中都需要加瀏覽器前綴,如:-webkit、-moz、-ms、-o等;所以我們可以通過設置元素的style屬性中的相應屬性來控制元素的可選擇性。但是,ie總是太操蛋,大多數情況下,ms前綴都可以解決問題,但是如果一個將一個frame作為編輯器使用時,設置msUserSelect為none時無法達到效果,所以在ie中我們利用unselectable特性來解決這個問題。ie下存在的這個特性:unselectable, 設為on則不可選中,移除這個屬性則表示可選中。
dojo的實現中,首先判斷userSelect屬性是否能使用:
has.add("css-user-select", function(global, doc, element){ // Avoid exception when dom.js is loaded in non-browser environments if(!element){ return false; } var style = element.style; var prefixes = ["Khtml", "O", "Moz", "Webkit"], i = prefixes.length, name = "userSelect", prefix; // Iterate prefixes from most to least likely do{ if(typeof style[name] !== "undefined"){ // Supported; return property name return name; } }while(i-- && (name = prefixes[i] + "UserSelect")); // Not supported if we didn't return before now return false; });
這里省略了ms前綴。
然后根據對"css-user-select"的支持,使用不同的實現:
var cssUserSelect = has("css-user-select"); dom.setSelectable = cssUserSelect ? function(node, selectable){ // css-user-select returns a (possibly vendor-prefixed) CSS property name dom.byId(node).style[cssUserSelect] = selectable ? "" : "none"; } : function(node, selectable){ node = dom.byId(node); // (IE < 10 / Opera) Fall back to setting/removing the // unselectable attribute on the element and all its children var nodes = node.getElementsByTagName("*"), i = nodes.length; if(selectable){ node.removeAttribute("unselectable"); while(i--){ nodes[i].removeAttribute("unselectable"); } }else{ node.setAttribute("unselectable", "on"); while(i--){ nodes[i].setAttribute("unselectable", "on"); } } };
ie中,循環改變所有子節點的unselectable特性來控制選擇性。
分享一條小經驗:設置一個元素不可選中時,最好在能滿足需求的最遠祖先上設置,如果僅僅在一個元素上設置未必能夠達到效果;比如:設置一個圖片不可選中,但是祖先可以選中,用戶可能會祖先選中時會變藍,看起來好像圖片依然能夠被選中。
如果您覺得這篇文章對您有幫助,請不吝點擊下方的推薦按鈕,您的鼓勵是我分享的動力!