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