%PDF- %PDF-
Direktori : /home/opti/workflow/node_modules/slick/ |
Current File : /home/opti/workflow/node_modules/slick/finder.js |
/* Slick Finder */"use strict" // Notable changes from Slick.Finder 1.0.x // faster bottom -> up expression matching // prefers mental sanity over *obsessive compulsive* milliseconds savings // uses prototypes instead of objects // tries to use matchesSelector smartly, whenever available // can populate objects as well as arrays // lots of stuff is broken or not implemented var parse = require("./parser") // utilities var index = 0, counter = document.__counter = (parseInt(document.__counter || -1, 36) + 1).toString(36), key = "uid:" + counter var uniqueID = function(n, xml){ if (n === window) return "window" if (n === document) return "document" if (n === document.documentElement) return "html" if (xml) { var uid = n.getAttribute(key) if (!uid) { uid = (index++).toString(36) n.setAttribute(key, uid) } return uid } else { return n[key] || (n[key] = (index++).toString(36)) } } var uniqueIDXML = function(n) { return uniqueID(n, true) } var isArray = Array.isArray || function(object){ return Object.prototype.toString.call(object) === "[object Array]" } // tests var uniqueIndex = 0; var HAS = { GET_ELEMENT_BY_ID: function(test, id){ id = "slick_" + (uniqueIndex++); // checks if the document has getElementById, and it works test.innerHTML = '<a id="' + id + '"></a>' return !!this.getElementById(id) }, QUERY_SELECTOR: function(test){ // this supposedly fixes a webkit bug with matchesSelector / querySelector & nth-child test.innerHTML = '_<style>:nth-child(2){}</style>' // checks if the document has querySelectorAll, and it works test.innerHTML = '<a class="MiX"></a>' return test.querySelectorAll('.MiX').length === 1 }, EXPANDOS: function(test, id){ id = "slick_" + (uniqueIndex++); // checks if the document has elements that support expandos test._custom_property_ = id return test._custom_property_ === id }, // TODO: use this ? // CHECKED_QUERY_SELECTOR: function(test){ // // // checks if the document supports the checked query selector // test.innerHTML = '<select><option selected="selected">a</option></select>' // return test.querySelectorAll(':checked').length === 1 // }, // TODO: use this ? // EMPTY_ATTRIBUTE_QUERY_SELECTOR: function(test){ // // // checks if the document supports the empty attribute query selector // test.innerHTML = '<a class=""></a>' // return test.querySelectorAll('[class*=""]').length === 1 // }, MATCHES_SELECTOR: function(test){ test.className = "MiX" // checks if the document has matchesSelector, and we can use it. var matches = test.matchesSelector || test.mozMatchesSelector || test.webkitMatchesSelector // if matchesSelector trows errors on incorrect syntax we can use it if (matches) try { matches.call(test, ':slick') } catch(e){ // just as a safety precaution, also test if it works on mixedcase (like querySelectorAll) return matches.call(test, ".MiX") ? matches : false } return false }, GET_ELEMENTS_BY_CLASS_NAME: function(test){ test.innerHTML = '<a class="f"></a><a class="b"></a>' if (test.getElementsByClassName('b').length !== 1) return false test.firstChild.className = 'b' if (test.getElementsByClassName('b').length !== 2) return false // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one test.innerHTML = '<a class="a"></a><a class="f b a"></a>' if (test.getElementsByClassName('a').length !== 2) return false // tests passed return true }, // no need to know // GET_ELEMENT_BY_ID_NOT_NAME: function(test, id){ // test.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>' // return this.getElementById(id) !== test.firstChild // }, // this is always checked for and fixed // STAR_GET_ELEMENTS_BY_TAG_NAME: function(test){ // // // IE returns comment nodes for getElementsByTagName('*') for some documents // test.appendChild(this.createComment('')) // if (test.getElementsByTagName('*').length > 0) return false // // // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents // test.innerHTML = 'foo</foo>' // if (test.getElementsByTagName('*').length) return false // // // tests passed // return true // }, // this is always checked for and fixed // STAR_QUERY_SELECTOR: function(test){ // // // returns closed nodes (EG:"</foo>") for querySelector('*') for some documents // test.innerHTML = 'foo</foo>' // return !!(test.querySelectorAll('*').length) // }, GET_ATTRIBUTE: function(test){ // tests for working getAttribute implementation var shout = "fus ro dah" test.innerHTML = '<a class="' + shout + '"></a>' return test.firstChild.getAttribute('class') === shout } } // Finder var Finder = function Finder(document){ this.document = document var root = this.root = document.documentElement this.tested = {} // uniqueID this.uniqueID = this.has("EXPANDOS") ? uniqueID : uniqueIDXML // getAttribute this.getAttribute = (this.has("GET_ATTRIBUTE")) ? function(node, name){ return node.getAttribute(name) } : function(node, name){ node = node.getAttributeNode(name) return (node && node.specified) ? node.value : null } // hasAttribute this.hasAttribute = (root.hasAttribute) ? function(node, attribute){ return node.hasAttribute(attribute) } : function(node, attribute) { node = node.getAttributeNode(attribute) return !!(node && node.specified) } // contains this.contains = (document.contains && root.contains) ? function(context, node){ return context.contains(node) } : (root.compareDocumentPosition) ? function(context, node){ return context === node || !!(context.compareDocumentPosition(node) & 16) } : function(context, node){ do { if (node === context) return true } while ((node = node.parentNode)) return false } // sort // credits to Sizzle (http://sizzlejs.com/) this.sorter = (root.compareDocumentPosition) ? function(a, b){ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0 return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1 } : ('sourceIndex' in root) ? function(a, b){ if (!a.sourceIndex || !b.sourceIndex) return 0 return a.sourceIndex - b.sourceIndex } : (document.createRange) ? function(a, b){ if (!a.ownerDocument || !b.ownerDocument) return 0 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange() aRange.setStart(a, 0) aRange.setEnd(a, 0) bRange.setStart(b, 0) bRange.setEnd(b, 0) return aRange.compareBoundaryPoints(Range.START_TO_END, bRange) } : null this.failed = {} var nativeMatches = this.has("MATCHES_SELECTOR") if (nativeMatches) this.matchesSelector = function(node, expression){ if (this.failed[expression]) return null try { return nativeMatches.call(node, expression) } catch(e){ if (slick.debug) console.warn("matchesSelector failed on " + expression) this.failed[expression] = true return null } } if (this.has("QUERY_SELECTOR")){ this.querySelectorAll = function(node, expression){ if (this.failed[expression]) return true var result, _id, _expression, _combinator, _node // non-document rooted QSA // credits to Andrew Dupont if (node !== this.document){ _combinator = expression[0].combinator _id = node.getAttribute("id") _expression = expression if (!_id){ _node = node _id = "__slick__" _node.setAttribute("id", _id) } expression = "#" + _id + " " + _expression // these combinators need a parentNode due to how querySelectorAll works, which is: // finding all the elements that match the given selector // then filtering by the ones that have the specified element as an ancestor if (_combinator.indexOf("~") > -1 || _combinator.indexOf("+") > -1){ node = node.parentNode if (!node) result = true // if node has no parentNode, we return "true" as if it failed, without polluting the failed cache } } if (!result) try { result = node.querySelectorAll(expression.toString()) } catch(e){ if (slick.debug) console.warn("querySelectorAll failed on " + (_expression || expression)) result = this.failed[_expression || expression] = true } if (_node) _node.removeAttribute("id") return result } } } Finder.prototype.has = function(FEATURE){ var tested = this.tested, testedFEATURE = tested[FEATURE] if (testedFEATURE != null) return testedFEATURE var root = this.root, document = this.document, testNode = document.createElement("div") testNode.setAttribute("style", "display: none;") root.appendChild(testNode) var TEST = HAS[FEATURE], result = false if (TEST) try { result = TEST.call(document, testNode) } catch(e){} if (slick.debug && !result) console.warn("document has no " + FEATURE) root.removeChild(testNode) return tested[FEATURE] = result } var combinators = { " ": function(node, part, push){ var item, items var noId = !part.id, noTag = !part.tag, noClass = !part.classes if (part.id && node.getElementById && this.has("GET_ELEMENT_BY_ID")){ item = node.getElementById(part.id) // return only if id is found, else keep checking // might be a tad slower on non-existing ids, but less insane if (item && item.getAttribute('id') === part.id){ items = [item] noId = true // if tag is star, no need to check it in match() if (part.tag === "*") noTag = true } } if (!items){ if (part.classes && node.getElementsByClassName && this.has("GET_ELEMENTS_BY_CLASS_NAME")){ items = node.getElementsByClassName(part.classList) noClass = true // if tag is star, no need to check it in match() if (part.tag === "*") noTag = true } else { items = node.getElementsByTagName(part.tag) // if tag is star, need to check it in match because it could select junk, boho if (part.tag !== "*") noTag = true } if (!items || !items.length) return false } for (var i = 0; item = items[i++];) if ((noTag && noId && noClass && !part.attributes && !part.pseudos) || this.match(item, part, noTag, noId, noClass)) push(item) return true }, ">": function(node, part, push){ // direct children if ((node = node.firstChild)) do { if (node.nodeType == 1 && this.match(node, part)) push(node) } while ((node = node.nextSibling)) }, "+": function(node, part, push){ // next sibling while ((node = node.nextSibling)) if (node.nodeType == 1){ if (this.match(node, part)) push(node) break } }, "^": function(node, part, push){ // first child node = node.firstChild if (node){ if (node.nodeType === 1){ if (this.match(node, part)) push(node) } else { combinators['+'].call(this, node, part, push) } } }, "~": function(node, part, push){ // next siblings while ((node = node.nextSibling)){ if (node.nodeType === 1 && this.match(node, part)) push(node) } }, "++": function(node, part, push){ // next sibling and previous sibling combinators['+'].call(this, node, part, push) combinators['!+'].call(this, node, part, push) }, "~~": function(node, part, push){ // next siblings and previous siblings combinators['~'].call(this, node, part, push) combinators['!~'].call(this, node, part, push) }, "!": function(node, part, push){ // all parent nodes up to document while ((node = node.parentNode)) if (node !== this.document && this.match(node, part)) push(node) }, "!>": function(node, part, push){ // direct parent (one level) node = node.parentNode if (node !== this.document && this.match(node, part)) push(node) }, "!+": function(node, part, push){ // previous sibling while ((node = node.previousSibling)) if (node.nodeType == 1){ if (this.match(node, part)) push(node) break } }, "!^": function(node, part, push){ // last child node = node.lastChild if (node){ if (node.nodeType == 1){ if (this.match(node, part)) push(node) } else { combinators['!+'].call(this, node, part, push) } } }, "!~": function(node, part, push){ // previous siblings while ((node = node.previousSibling)){ if (node.nodeType === 1 && this.match(node, part)) push(node) } } } Finder.prototype.search = function(context, expression, found){ if (!context) context = this.document else if (!context.nodeType && context.document) context = context.document var expressions = parse(expression) // no expressions were parsed. todo: is this really necessary? if (!expressions || !expressions.length) throw new Error("invalid expression") if (!found) found = [] var uniques, push = isArray(found) ? function(node){ found[found.length] = node } : function(node){ found[found.length++] = node } // if there is more than one expression we need to check for duplicates when we push to found // this simply saves the old push and wraps it around an uid dupe check. if (expressions.length > 1){ uniques = {} var plush = push push = function(node){ var uid = uniqueID(node) if (!uniques[uid]){ uniques[uid] = true plush(node) } } } // walker var node, nodes, part main: for (var i = 0; expression = expressions[i++];){ // querySelector // TODO: more functional tests // if there is querySelectorAll (and the expression does not fail) use it. if (!slick.noQSA && this.querySelectorAll){ nodes = this.querySelectorAll(context, expression) if (nodes !== true){ if (nodes && nodes.length) for (var j = 0; node = nodes[j++];) if (node.nodeName > '@'){ push(node) } continue main } } // if there is only one part in the expression we don't need to check each part for duplicates. // todo: this might be too naive. while solid, there can be expression sequences that do not // produce duplicates. "body div" for instance, can never give you each div more than once. // "body div a" on the other hand might. if (expression.length === 1){ part = expression[0] combinators[part.combinator].call(this, context, part, push) } else { var cs = [context], c, f, u, p = function(node){ var uid = uniqueID(node) if (!u[uid]){ u[uid] = true f[f.length] = node } } // loop the expression parts for (var j = 0; part = expression[j++];){ f = []; u = {} // loop the contexts for (var k = 0; c = cs[k++];) combinators[part.combinator].call(this, c, part, p) // nothing was found, the expression failed, continue to the next expression. if (!f.length) continue main cs = f // set the contexts for future parts (if any) } if (i === 0) found = f // first expression. directly set found. else for (var l = 0; l < f.length; l++) push(f[l]) // any other expression needs to push to found. } } if (uniques && found && found.length > 1) this.sort(found) return found } Finder.prototype.sort = function(nodes){ return this.sorter ? Array.prototype.sort.call(nodes, this.sorter) : nodes } // TODO: most of these pseudo selectors include <html> and qsa doesnt. fixme. var pseudos = { // TODO: returns different results than qsa empty. 'empty': function(){ return !(this && this.nodeType === 1) && !(this.innerText || this.textContent || '').length }, 'not': function(expression){ return !slick.matches(this, expression) }, 'contains': function(text){ return (this.innerText || this.textContent || '').indexOf(text) > -1 }, 'first-child': function(){ var node = this while ((node = node.previousSibling)) if (node.nodeType == 1) return false return true }, 'last-child': function(){ var node = this while ((node = node.nextSibling)) if (node.nodeType == 1) return false return true }, 'only-child': function(){ var prev = this while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false var next = this while ((next = next.nextSibling)) if (next.nodeType == 1) return false return true }, 'first-of-type': function(){ var node = this, nodeName = node.nodeName while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false return true }, 'last-of-type': function(){ var node = this, nodeName = node.nodeName while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false return true }, 'only-of-type': function(){ var prev = this, nodeName = this.nodeName while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false var next = this while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false return true }, 'enabled': function(){ return !this.disabled }, 'disabled': function(){ return this.disabled }, 'checked': function(){ return this.checked || this.selected }, 'selected': function(){ return this.selected }, 'focus': function(){ var doc = this.ownerDocument return doc.activeElement === this && (this.href || this.type || slick.hasAttribute(this, 'tabindex')) }, 'root': function(){ return (this === this.ownerDocument.documentElement) } } Finder.prototype.match = function(node, bit, noTag, noId, noClass){ // TODO: more functional tests ? if (!slick.noQSA && this.matchesSelector){ var matches = this.matchesSelector(node, bit) if (matches !== null) return matches } // normal matching if (!noTag && bit.tag){ var nodeName = node.nodeName.toLowerCase() if (bit.tag === "*"){ if (nodeName < "@") return false } else if (nodeName != bit.tag){ return false } } if (!noId && bit.id && node.getAttribute('id') !== bit.id) return false var i, part if (!noClass && bit.classes){ var className = this.getAttribute(node, "class") if (!className) return false for (part in bit.classes) if (!RegExp('(^|\\s)' + bit.classes[part] + '(\\s|$)').test(className)) return false } var name, value if (bit.attributes) for (i = 0; part = bit.attributes[i++];){ var operator = part.operator, escaped = part.escapedValue name = part.name value = part.value if (!operator){ if (!this.hasAttribute(node, name)) return false } else { var actual = this.getAttribute(node, name) if (actual == null) return false switch (operator){ case '^=' : if (!RegExp( '^' + escaped ).test(actual)) return false; break case '$=' : if (!RegExp( escaped + '$' ).test(actual)) return false; break case '~=' : if (!RegExp('(^|\\s)' + escaped + '(\\s|$)').test(actual)) return false; break case '|=' : if (!RegExp( '^' + escaped + '(-|$)' ).test(actual)) return false; break case '=' : if (actual !== value) return false; break case '*=' : if (actual.indexOf(value) === -1) return false; break default : return false } } } if (bit.pseudos) for (i = 0; part = bit.pseudos[i++];){ name = part.name value = part.value if (pseudos[name]) return pseudos[name].call(node, value) if (value != null){ if (this.getAttribute(node, name) !== value) return false } else { if (!this.hasAttribute(node, name)) return false } } return true } Finder.prototype.matches = function(node, expression){ var expressions = parse(expression) if (expressions.length === 1 && expressions[0].length === 1){ // simplest match return this.match(node, expressions[0][0]) } // TODO: more functional tests ? if (!slick.noQSA && this.matchesSelector){ var matches = this.matchesSelector(node, expressions) if (matches !== null) return matches } var nodes = this.search(this.document, expression, {length: 0}) for (var i = 0, res; res = nodes[i++];) if (node === res) return true return false } var finders = {} var finder = function(context){ var doc = context || document if (doc.ownerDocument) doc = doc.ownerDocument else if (doc.document) doc = doc.document if (doc.nodeType !== 9) throw new TypeError("invalid document") var uid = uniqueID(doc) return finders[uid] || (finders[uid] = new Finder(doc)) } // ... API ... var slick = function(expression, context){ return slick.search(expression, context) } slick.search = function(expression, context, found){ return finder(context).search(context, expression, found) } slick.find = function(expression, context){ return finder(context).search(context, expression)[0] || null } slick.getAttribute = function(node, name){ return finder(node).getAttribute(node, name) } slick.hasAttribute = function(node, name){ return finder(node).hasAttribute(node, name) } slick.contains = function(context, node){ return finder(context).contains(context, node) } slick.matches = function(node, expression){ return finder(node).matches(node, expression) } slick.sort = function(nodes){ if (nodes && nodes.length > 1) finder(nodes[0]).sort(nodes) return nodes } slick.parse = parse; // slick.debug = true // slick.noQSA = true module.exports = slick