/** * @ignore * simple selector for dom * @author yiminghe@gmail.com, lifesinger@gmail.com */ KISSY.add('dom/base/selector', function (S, Dom, undefined) { var doc = S.Env.host.document, docElem = doc.documentElement, matches = docElem.matches || docElem['webkitMatchesSelector'] || docElem['mozMatchesSelector'] || docElem['oMatchesSelector'] || docElem['msMatchesSelector'], supportGetElementsByClassName = 'getElementsByClassName' in doc, isArray = S.isArray, makeArray = S.makeArray, isDomNodeList = Dom.isDomNodeList, SPACE = ' ', push = Array.prototype.push, rClassSelector = /^\.([\w-]+)$/, rIdSelector = /^#([\w-]+)$/, rTagSelector = /^([\w-])+$/, rTagIdSelector = /^([\w-]+)#([\w-]+)$/, rSimpleSelector = /^(?:#([\w-]+))?\s*([\w-]+|\*)?\.?([\w-]+)?$/, trim = S.trim; function query_each(f) { var els = this, l = els.length, i; for (i = 0; i < l; i++) { if (f(els[i], i) === false) { break; } } } function checkSelectorAndReturn(selector) { var name = selector.substr(1); if (!name) { throw new Error('An invalid or illegal string was specified for selector.') } return name; } function makeMatch(selector) { var s = selector.charAt(0); if (s == '#') { return makeIdMatch(checkSelectorAndReturn(selector)); } else if (s == '.') { return makeClassMatch(checkSelectorAndReturn(selector)); } else { return makeTagMatch(selector); } } function makeIdMatch(id) { return function (elem) { var match = Dom._getElementById(id, doc); return match && Dom._contains(elem, match) ? [ match ] : [ ]; }; } function makeClassMatch(className) { return function (elem) { return elem.getElementsByClassName(className); }; } function makeTagMatch(tagName) { return function (elem) { return elem.getElementsByTagName(tagName); }; } // 只涉及#id,.cls,tag的逐级选择 function isSimpleSelector(selector) { var complexReg = /,|\+|=|~|\[|\]|:|>|\||\$|\^|\*|\(|\)|[\w-]+\.[\w-]+|[\w-]+#[\w-]+/; return !selector.match(complexReg); } function query(selector, context) { var ret, i, el, simpleContext, isSelectorString = typeof selector == 'string', contexts = context !== undefined ? query(context) : (simpleContext = 1) && [doc], contextsLen = contexts.length; // 常见的空 if (!selector) { ret = []; } else if (isSelectorString) { selector = trim(selector); if (simpleContext) { // shortcut if (selector == 'body') { ret = [ doc.body ]; } // .cls else if (rClassSelector.test(selector) && supportGetElementsByClassName) { ret = doc.getElementsByClassName(RegExp.$1); } // tag#id else if (rTagIdSelector.test(selector)) { el = Dom._getElementById(RegExp.$2, doc); ret = el && el.nodeName.toLowerCase() == RegExp.$1 ? [el] : []; } // #id else if (rIdSelector.test(selector)) { el = Dom._getElementById(selector.substr(1), doc); ret = el ? [el] : []; } // tag else if (rTagSelector.test(selector)) { ret = doc.getElementsByTagName(selector); } // #id tag, #id .cls... else if (isSimpleSelector(selector) && supportGetElementsByClassName) { var parts = selector.split(/\s+/), partsLen, parents = contexts, parentIndex, parentsLen; for (i = 0, partsLen = parts.length; i < partsLen; i++) { parts[i] = makeMatch(parts[i]); } for (i = 0, partsLen = parts.length; i < partsLen; i++) { var part = parts[i], newParents = [ ], matches; for (parentIndex = 0, parentsLen = parents.length; parentIndex < parentsLen; parentIndex++) { matches = part(parents[parentIndex]); newParents.push.apply(newParents, S.makeArray(matches)); } parents = newParents; if (!parents.length) { break; } } ret = parents && parents.length > 1 ? Dom.unique(parents) : parents; } } if (!ret) { ret = []; for (i = 0; i < contextsLen; i++) { push.apply(ret, Dom._selectInternal(selector, contexts[i])); } // multiple contexts unique if (ret.length > 1 && contextsLen > 1) { Dom.unique(ret); } } } // 不写 context,就是包装一下 else { // 1.常见的单个元素 // Dom.query(document.getElementById('xx')) if (selector['nodeType'] || selector['setTimeout']) { ret = [selector]; } // 2.KISSY NodeList 特殊点直接返回,提高性能 else if (selector['getDOMNodes']) { ret = selector['getDOMNodes'](); } // 3.常见的数组 // var x=Dom.query('.l'); // Dom.css(x,'color','red'); else if (isArray(selector)) { ret = selector; } // 4.selector.item // Dom.query(document.getElementsByTagName('a')) // note: // document.createElement('select').item 已经在 1 处理了 // S.all().item 已经在 2 处理了 else if (isDomNodeList(selector)) { ret = makeArray(selector); } else { ret = [ selector ]; } if (!simpleContext) { var tmp = ret, ci, len = tmp.length; ret = []; for (i = 0; i < len; i++) { for (ci = 0; ci < contextsLen; ci++) { if (Dom._contains(contexts[ci], tmp[i])) { ret.push(tmp[i]); break; } } } } } // attach each method ret.each = query_each; return ret; } function hasSingleClass(el, cls) { // consider xml var className = el && (el.className || getAttr(el, 'class')); return className && (className = className.replace(/[\r\t\n]/g, SPACE)) && (SPACE + className + SPACE).indexOf(SPACE + cls + SPACE) > -1; } function getAttr(el, name) { var ret = el && el.getAttributeNode(name); if (ret && ret.specified) { return ret.nodeValue; } return undefined; } function isTag(el, value) { return value == '*' || el.nodeName.toLowerCase() === value.toLowerCase(); } S.mix(Dom, /** * @override KISSY.DOM * @class * @singleton */ { _compareNodeOrder: function (a, b) { if (!a.compareDocumentPosition || !b.compareDocumentPosition) { return a.compareDocumentPosition ? -1 : 1; } var bit = a.compareDocumentPosition(b) & 4; return bit ? -1 : 1; }, _getElementsByTagName: function (name, context) { // can not use getElementsByTagName for fragment return S.makeArray(context.querySelectorAll(name)); }, _getElementById: function (id, doc) { return doc.getElementById(id); }, _getSimpleAttr: getAttr, _isTag: isTag, _hasSingleClass: hasSingleClass, _matchesInternal: function (str, seeds) { var ret = [], i = 0, n, len = seeds.length; for (; i < len; i++) { n = seeds[i]; if (matches.call(n, str)) { ret.push(n); } } return ret; }, _selectInternal: function (str, context) { return makeArray(context.querySelectorAll(str)); }, /** * Accepts a string containing a CSS selector which is then used to match a set of elements. * @param {String|HTMLElement[]} selector * A string containing a selector expression. * or * array of HTMLElements. * @param {String|HTMLElement[]|HTMLDocument|HTMLElement} [context] context under which to find elements matching selector. * @return {HTMLElement[]} The array of found HTMLElements * @method */ query: query, /** * Accepts a string containing a CSS selector which is then used to match a set of elements. * @param {String|HTMLElement[]} selector * A string containing a selector expression. * or * array of HTMLElements. * @param {String|HTMLElement[]|HTMLDocument|HTMLElement|Window} [context] context under which to find elements matching selector. * @return {HTMLElement} The first of found HTMLElements */ get: function (selector, context) { return query(selector, context)[0] || null; }, /** * Sorts an array of Dom elements, in place, with the duplicates removed. * Note that this only works on arrays of Dom elements, not strings or numbers. * @param {HTMLElement[]} The Array of Dom elements. * @method * @return {HTMLElement[]} * @member KISSY.DOM */ unique: (function () { var hasDuplicate, baseHasDuplicate = true; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparison // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. [0, 0].sort(function () { baseHasDuplicate = false; return 0; }); function sortOrder(a, b) { if (a == b) { hasDuplicate = true; return 0; } return Dom._compareNodeOrder(a, b); } // 排序去重 return function (elements) { hasDuplicate = baseHasDuplicate; elements.sort(sortOrder); if (hasDuplicate) { var i = 1, len = elements.length; while (i < len) { if (elements[i] === elements[ i - 1 ]) { elements.splice(i, 1); --len; } else { i++; } } } return elements; }; })(), /** * Reduce the set of matched elements to those that match the selector or pass the function's test. * @param {String|HTMLElement[]} selector Matched elements * @param {String|Function} filter Selector string or filter function * @param {String|HTMLElement[]|HTMLDocument} [context] Context under which to find matched elements * @return {HTMLElement[]} * @member KISSY.DOM */ filter: function (selector, filter, context) { var elems = query(selector, context), id, tag, match, cls, ret = []; if (typeof filter == 'string' && (filter = trim(filter)) && (match = rSimpleSelector.exec(filter))) { id = match[1]; tag = match[2]; cls = match[3]; if (!id) { filter = function (elem) { var tagRe = true, clsRe = true; // 指定 tag 才进行判断 if (tag) { tagRe = isTag(elem, tag); } // 指定 cls 才进行判断 if (cls) { clsRe = hasSingleClass(elem, cls); } return clsRe && tagRe; } } else if (id && !tag && !cls) { filter = function (elem) { return getAttr(elem, 'id') == id; }; } } if (typeof filter === 'function') { ret = S.filter(elems, filter); } else { ret = Dom._matchesInternal(filter, elems); } return ret; }, /** * Returns true if the matched element(s) pass the filter test * @param {String|HTMLElement[]} selector Matched elements * @param {String|Function} filter Selector string or filter function * @param {String|HTMLElement[]|HTMLDocument} [context] Context under which to find matched elements * @member KISSY.DOM * @return {Boolean} */ test: function (selector, filter, context) { var elements = query(selector, context); return elements.length && (Dom.filter(elements, filter, context).length === elements.length); } }); return Dom; }, { requires: ['./api'] }); /** * @ignore * bachi selector optimize - 2013-07-17 * - http://jsperf.com/queryselctor-vs-getelementbyclassname2 * yiminghe@gmail.com - 2013-03-26 * - refactor to use own css3 selector engine */