1 /** 2 * @fileOverview dom-traversal 3 * @author lifesinger@gmail.com,yiminghe@gmail.com 4 */ 5 KISSY.add('dom/traversal', function (S, DOM, undefined) { 6 7 var doc = S.Env.host.document, 8 CONTAIN_MASK = 16, 9 __contains = doc.documentElement.contains ? 10 function (a, b) { 11 if (a.nodeType == DOM.TEXT_NODE) { 12 return false; 13 } 14 var precondition; 15 if (b.nodeType == DOM.TEXT_NODE) { 16 b = b.parentNode; 17 // a 和 b父亲相等也就是返回 true 18 precondition = true; 19 } else if (b.nodeType == DOM.DOCUMENT_NODE) { 20 // b === document 21 // 没有任何元素能包含 document 22 return false; 23 } else { 24 // a 和 b 相等返回 false 25 precondition = a !== b; 26 } 27 // !a.contains => a===document 28 // 注意原生 contains 判断时 a===b 也返回 true 29 return precondition && (a.contains ? a.contains(b) : true); 30 } : ( 31 doc.documentElement.compareDocumentPosition ? 32 function (a, b) { 33 return !!(a.compareDocumentPosition(b) & CONTAIN_MASK); 34 } : 35 // it can not be true , pathetic browser 36 0 37 ); 38 39 40 S.mix(DOM, 41 /** 42 * @lends DOM 43 */ 44 { 45 46 /** 47 * Get the first element that matches the filter, 48 * beginning at the first element of matched elements and progressing up through the DOM tree. 49 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 50 * @param {String|Function} filter Selector string or filter function 51 * @param {HTMLElement|String|Document|HTMLElement[]} [context] Search bound element 52 * @returns {HTMLElement} 53 */ 54 closest:function (selector, filter, context, allowTextNode) { 55 return nth(selector, filter, 'parentNode', function (elem) { 56 return elem.nodeType != DOM.DOCUMENT_FRAGMENT_NODE; 57 }, context, true, allowTextNode); 58 }, 59 60 /** 61 * Get the parent of the first element in the current set of matched elements, optionally filtered by a selector. 62 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 63 * @param {String|Function} [filter] Selector string or filter function 64 * @param {HTMLElement|String|Document|HTMLElement[]} [context] Search bound element 65 * @returns {HTMLElement} 66 */ 67 parent:function (selector, filter, context) { 68 return nth(selector, filter, 'parentNode', function (elem) { 69 return elem.nodeType != DOM.DOCUMENT_FRAGMENT_NODE; 70 }, context, undefined); 71 }, 72 73 /** 74 * Get the first child of the first element in the set of matched elements. 75 * If a filter is provided, it retrieves the next child only if it matches that filter. 76 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 77 * @param {String|Function} [filter] Selector string or filter function 78 * @returns {HTMLElement} 79 */ 80 first:function (selector, filter, allowTextNode) { 81 var elem = DOM.get(selector); 82 return nth(elem && elem.firstChild, filter, 'nextSibling', 83 undefined, undefined, true, allowTextNode); 84 }, 85 86 /** 87 * Get the last child of the first element in the set of matched elements. 88 * If a filter is provided, it retrieves the previous child only if it matches that filter. 89 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 90 * @param {String|Function} [filter] Selector string or filter function 91 * @returns {HTMLElement} 92 */ 93 last:function (selector, filter, allowTextNode) { 94 var elem = DOM.get(selector); 95 return nth(elem && elem.lastChild, filter, 'previousSibling', 96 undefined, undefined, true, allowTextNode); 97 }, 98 99 /** 100 * Get the immediately following sibling of the first element in the set of matched elements. 101 * If a filter is provided, it retrieves the next child only if it matches that filter. 102 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 103 * @param {String|Function} [filter] Selector string or filter function 104 * @returns {HTMLElement} 105 */ 106 next:function (selector, filter, allowTextNode) { 107 return nth(selector, filter, 'nextSibling', undefined, 108 undefined, undefined, allowTextNode); 109 }, 110 111 /** 112 * Get the immediately preceding sibling of the first element in the set of matched elements. 113 * If a filter is provided, it retrieves the previous child only if it matches that filter. 114 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 115 * @param {String|Function} [filter] Selector string or filter function 116 * @returns {HTMLElement} 117 */ 118 prev:function (selector, filter, allowTextNode) { 119 return nth(selector, filter, 'previousSibling', 120 undefined, undefined, undefined, allowTextNode); 121 }, 122 123 /** 124 * Get the siblings of the first element in the set of matched elements, optionally filtered by a filter. 125 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 126 * @param {String|Function} [filter] Selector string or filter function 127 * @returns {HTMLElement[]} 128 */ 129 siblings:function (selector, filter, allowTextNode) { 130 return getSiblings(selector, filter, true, allowTextNode); 131 }, 132 133 /** 134 * Get the children of the first element in the set of matched elements, optionally filtered by a filter. 135 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 136 * @param {String|Function} [filter] Selector string or filter function 137 * @returns {HTMLElement[]} 138 */ 139 children:function (selector, filter) { 140 return getSiblings(selector, filter, undefined); 141 }, 142 143 /** 144 * Get the childNodes of the first element in the set of matched elements (includes text and comment nodes), 145 * optionally filtered by a filter. 146 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 147 * @param {String|Function} [filter] Selector string or filter function 148 * @returns {Node[]} 149 */ 150 contents:function (selector, filter) { 151 return getSiblings(selector, filter, undefined, 1); 152 }, 153 154 /** 155 * Check to see if a DOM node is within another DOM node. 156 * @param {HTMLElement|String|Element} container The DOM element that may contain the other element. 157 * @param {HTMLElement|String|Element} contained The DOM element that may be contained by the other element. 158 * @returns {Boolean} 159 */ 160 contains:function (container, contained) { 161 container = DOM.get(container); 162 contained = DOM.get(contained); 163 if (container && contained) { 164 return __contains(container, contained); 165 } 166 return false; 167 }, 168 169 /** 170 * Check to see if a DOM node is equal with another DOM node. 171 * @param {HTMLElement|String|Element} n1 172 * @param {HTMLElement|String|Element} n2 173 * @returns {Boolean} 174 */ 175 equals:function (n1, n2) { 176 n1 = DOM.query(n1); 177 n2 = DOM.query(n2); 178 if (n1.length != n2.length) { 179 return false; 180 } 181 for (var i = n1.length; i >= 0; i--) { 182 if (n1[i] != n2[i]) { 183 return false; 184 } 185 } 186 return true; 187 } 188 }); 189 190 // 获取元素 elem 在 direction 方向上满足 filter 的第一个元素 191 // filter 可为 number, selector, fn array ,为数组时返回多个 192 // direction 可为 parentNode, nextSibling, previousSibling 193 // context : 到某个阶段不再查找直接返回 194 function nth(elem, filter, direction, extraFilter, context, includeSef, allowTextNode) { 195 if (!(elem = DOM.get(elem))) { 196 return null; 197 } 198 if (filter === 0) { 199 return elem; 200 } 201 if (!includeSef) { 202 elem = elem[direction]; 203 } 204 if (!elem) { 205 return null; 206 } 207 context = (context && DOM.get(context)) || null; 208 209 if (filter === undefined) { 210 // 默认取 1 211 filter = 1; 212 } 213 var ret = [], 214 isArray = S.isArray(filter), 215 fi, 216 flen; 217 218 if (S.isNumber(filter)) { 219 fi = 0; 220 flen = filter; 221 filter = function () { 222 return ++fi === flen; 223 }; 224 } 225 226 // 概念统一,都是 context 上下文,只过滤子孙节点,自己不管 227 while (elem && elem != context) { 228 if (( 229 elem.nodeType == DOM.ELEMENT_NODE || 230 elem.nodeType == DOM.TEXT_NODE && allowTextNode 231 ) && 232 testFilter(elem, filter) && 233 (!extraFilter || extraFilter(elem))) { 234 ret.push(elem); 235 if (!isArray) { 236 break; 237 } 238 } 239 elem = elem[direction]; 240 } 241 242 return isArray ? ret : ret[0] || null; 243 } 244 245 function testFilter(elem, filter) { 246 if (!filter) { 247 return true; 248 } 249 if (S.isArray(filter)) { 250 for (var i = 0; i < filter.length; i++) { 251 if (DOM.test(elem, filter[i])) { 252 return true; 253 } 254 } 255 } else if (DOM.test(elem, filter)) { 256 return true; 257 } 258 return false; 259 } 260 261 // 获取元素 elem 的 siblings, 不包括自身 262 function getSiblings(selector, filter, parent, allowText) { 263 var ret = [], 264 elem = DOM.get(selector), 265 parentNode = elem; 266 267 if (elem && parent) { 268 parentNode = elem.parentNode; 269 } 270 271 if (parentNode) { 272 ret = S.makeArray(parentNode.childNodes); 273 if (!allowText) { 274 ret = DOM.filter(ret, function (el) { 275 return el.nodeType == 1; 276 }); 277 } 278 if (filter) { 279 ret = DOM.filter(ret, filter); 280 } 281 } 282 283 return ret; 284 } 285 286 return DOM; 287 }, { 288 requires:["./base"] 289 }); 290 291 /** 292 * 2012-04-05 yiminghe@gmail.com 293 * - 增加 contents 方法 294 * 295 * 296 * 2011-08 yiminghe@gmail.com 297 * - 添加 closest , first ,last 完全摆脱原生属性 298 * 299 * NOTES: 300 * - jquery does not return null ,it only returns empty array , but kissy does. 301 * 302 * - api 的设计上,没有跟随 jQuery. 一是为了和其他 api 一致,保持 first-all 原则。二是 303 * 遵循 8/2 原则,用尽可能少的代码满足用户最常用的功能。 304 * 305 */ 306