/** * @ignore * dom-traversal * @author lifesinger@gmail.com, yiminghe@gmail.com */ KISSY.add('dom/base/traversal', function (S, DOM, undefined) { var NodeType = DOM.NodeType, CONTAIN_MASK = 16; S.mix(DOM, /** * @override KISSY.DOM * @class * @singleton */ { _contains: function (a, b) { return !!(a.compareDocumentPosition(b) & CONTAIN_MASK); }, /** * Get the first element that matches the filter, * beginning at the first element of matched elements and progressing up through the DOM tree. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function|String[]|Function[]} filter Selector string or filter function or array * @param {HTMLElement|String|HTMLDocument|HTMLElement[]} [context] Search bound element * @return {HTMLElement|HTMLElement[]} * if filter is array, return all ancestors (include this) which match filter. * else return closest parent (include this) which matches filter. */ closest: function (selector, filter, context, allowTextNode) { return nth(selector, filter, 'parentNode', function (elem) { return elem.nodeType != NodeType.DOCUMENT_FRAGMENT_NODE; }, context, true, allowTextNode); }, /** * Get the parent of the first element in the current set of matched elements, optionally filtered by a selector. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function|String[]|Function[]} [filter] Selector string or filter function or array * @param {HTMLElement|String|HTMLDocument|HTMLElement[]} [context] Search bound element * @return {HTMLElement|HTMLElement[]} * if filter is array, return all ancestors which match filter. * else return closest parent which matches filter. */ parent: function (selector, filter, context) { return nth(selector, filter, 'parentNode', function (elem) { return elem.nodeType != NodeType.DOCUMENT_FRAGMENT_NODE; }, context, undefined); }, /** * Get the first child of the first element in the set of matched elements. * If a filter is provided, it retrieves the next child only if it matches that filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement} */ first: function (selector, filter, allowTextNode) { var elem = DOM.get(selector); return nth(elem && elem.firstChild, filter, 'nextSibling', undefined, undefined, true, allowTextNode); }, /** * Get the last child of the first element in the set of matched elements. * If a filter is provided, it retrieves the previous child only if it matches that filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement} */ last: function (selector, filter, allowTextNode) { var elem = DOM.get(selector); return nth(elem && elem.lastChild, filter, 'previousSibling', undefined, undefined, true, allowTextNode); }, /** * Get the immediately following sibling of the first element in the set of matched elements. * If a filter is provided, it retrieves the next child only if it matches that filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement} */ next: function (selector, filter, allowTextNode) { return nth(selector, filter, 'nextSibling', undefined, undefined, undefined, allowTextNode); }, /** * Get the immediately preceding sibling of the first element in the set of matched elements. * If a filter is provided, it retrieves the previous child only if it matches that filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement} */ prev: function (selector, filter, allowTextNode) { return nth(selector, filter, 'previousSibling', undefined, undefined, undefined, allowTextNode); }, /** * Get the siblings of the first element in the set of matched elements, optionally filtered by a filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement[]} */ siblings: function (selector, filter, allowTextNode) { return getSiblings(selector, filter, true, allowTextNode); }, /** * Get the children of the first element in the set of matched elements, optionally filtered by a filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement[]} */ children: function (selector, filter) { return getSiblings(selector, filter, undefined); }, /** * Get the childNodes of the first element in the set of matched elements (includes text and comment nodes), * optionally filtered by a filter. * @param {HTMLElement[]|String|HTMLElement} selector Matched elements * @param {String|Function} [filter] Selector string or filter function * @return {HTMLElement[]} */ contents: function (selector, filter) { return getSiblings(selector, filter, undefined, 1); }, /** * Check to see if a DOM node is within another DOM node. * @param {HTMLElement|String} container The DOM element that may contain the other element. * @param {HTMLElement|String} contained The DOM element that may be contained by the other element. * @return {Boolean} */ contains: function (container, contained) { container = DOM.get(container); contained = DOM.get(contained); if (container && contained) { return DOM._contains(container, contained); } return false; }, /** * search for a given element from among the matched elements. * @param {HTMLElement|String} selector elements or selector string to find matched elements. * @param {HTMLElement|String} s2 elements or selector string to find matched elements. */ index: function (selector, s2) { var els = DOM.query(selector), c, n = 0, p, els2, el = els[0]; if (!s2) { p = el && el.parentNode; if (!p) { return -1; } c = el; while (c = c.previousSibling) { if (c.nodeType == NodeType.ELEMENT_NODE) { n++; } } return n; } els2 = DOM.query(s2); if (typeof s2 === 'string') { return S.indexOf(el, els2); } return S.indexOf(els2[0], els); }, /** * Check to see if a DOM node is equal with another DOM node. * @param {HTMLElement|String} n1 * @param {HTMLElement|String} n2 * @return {Boolean} * @member KISSY.DOM */ equals: function (n1, n2) { n1 = DOM.query(n1); n2 = DOM.query(n2); if (n1.length != n2.length) { return false; } for (var i = n1.length; i >= 0; i--) { if (n1[i] != n2[i]) { return false; } } return true; } }); // 获取元素 elem 在 direction 方向上满足 filter 的第一个元素 // filter 可为 number, selector, fn array ,为数组时返回多个 // direction 可为 parentNode, nextSibling, previousSibling // context : 到某个阶段不再查找直接返回 function nth(elem, filter, direction, extraFilter, context, includeSef, allowTextNode) { if (!(elem = DOM.get(elem))) { return null; } if (filter === 0) { return elem; } if (!includeSef) { elem = elem[direction]; } if (!elem) { return null; } context = (context && DOM.get(context)) || null; if (filter === undefined) { // 默认取 1 filter = 1; } var ret = [], isArray = S.isArray(filter), fi, filterLength; if (S.isNumber(filter)) { fi = 0; filterLength = filter; filter = function () { return ++fi === filterLength; }; } // 概念统一,都是 context 上下文,只过滤子孙节点,自己不管 while (elem && elem != context) { if (( elem.nodeType == NodeType.ELEMENT_NODE || elem.nodeType == NodeType.TEXT_NODE && allowTextNode ) && testFilter(elem, filter) && (!extraFilter || extraFilter(elem))) { ret.push(elem); if (!isArray) { break; } } elem = elem[direction]; } return isArray ? ret : ret[0] || null; } function testFilter(elem, filter) { if (!filter) { return true; } if (S.isArray(filter)) { var i, l = filter.length; if (!l) { return true; } for (i = 0; i < l; i++) { if (DOM.test(elem, filter[i])) { return true; } } } else if (DOM.test(elem, filter)) { return true; } return false; } // 获取元素 elem 的 siblings, 不包括自身 function getSiblings(selector, filter, parent, allowText) { var ret = [], tmp, i, el, elem = DOM.get(selector), parentNode = elem; if (elem && parent) { parentNode = elem.parentNode; } if (parentNode) { tmp = S.makeArray(parentNode.childNodes); for (i = 0; i < tmp.length; i++) { el = tmp[i]; if (!allowText && el.nodeType != NodeType.ELEMENT_NODE) { continue; } if (el == elem) { continue; } ret.push(el); } if (filter) { ret = DOM.filter(ret, filter); } } return ret; } return DOM; }, { requires: ['./api'] }); /* 2012-04-05 yiminghe@gmail.com - 增加 contents 方法 2011-08 yiminghe@gmail.com - 添加 closest , first ,last 完全摆脱原生属性 NOTES: - jquery does not return null ,it only returns empty array , but kissy does. - api 的设计上,没有跟随 jQuery. 一是为了和其他 api 一致,保持 first-all 原则。二是 遵循 8/2 原则,用尽可能少的代码满足用户最常用的功能。 */