4 description: The new, superfast css selector engine.
14 toString = Object.prototype.toString;
16 // Feature / Bug detection
18 local.isNativeCode = function(fn){
19 return (/\{\s*\[native code\]\s*\}/).test('' + fn);
22 local.isXML = function(document){
23 return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
24 (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
27 local.setDocument = function(document){
29 // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
30 var nodeType = document.nodeType;
31 if (nodeType == 9); // document
32 else if (nodeType) document = document.ownerDocument; // node
33 else if (document.navigator) document = document.document; // window
36 // check if it's the old document
38 if (this.document === document) return;
39 this.document = document;
41 // check if we have done feature detection on this document before
43 var root = document.documentElement,
44 rootUid = this.getUIDXML(root),
45 features = featuresCache[rootUid],
49 for (feature in features){
50 this[feature] = features[feature];
55 features = featuresCache[rootUid] = {};
58 features.isXMLDocument = this.isXML(document);
60 features.brokenStarGEBTN
61 = features.starSelectsClosedQSA
63 = features.brokenMixedCaseQSA
64 = features.brokenGEBCN
65 = features.brokenCheckedQSA
66 = features.brokenEmptyAttributeQSA
67 = features.isHTMLDocument
68 = features.nativeMatchesSelector
71 var starSelectsClosed, starSelectsComments,
72 brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
73 brokenFormAttributeGetter;
75 var selected, id = 'slick_uniqueid';
76 var testNode = document.createElement('div');
78 var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
79 testRoot.appendChild(testNode);
81 // on non-HTML documents innerHTML and getElementsById doesnt work properly
83 testNode.innerHTML = '<a id="'+id+'"></a>';
84 features.isHTMLDocument = !!document.getElementById(id);
87 if (features.isHTMLDocument){
89 testNode.style.display = 'none';
91 // IE returns comment nodes for getElementsByTagName('*') for some documents
92 testNode.appendChild(document.createComment(''));
93 starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
95 // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
97 testNode.innerHTML = 'foo</foo>';
98 selected = testNode.getElementsByTagName('*');
99 starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
102 features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
104 // IE returns elements with the name instead of just id for getElementsById for some documents
106 testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
107 features.idGetsName = document.getElementById(id) === testNode.firstChild;
110 if (testNode.getElementsByClassName){
112 // Safari 3.2 getElementsByClassName caches results
114 testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
115 testNode.getElementsByClassName('b').length;
116 testNode.firstChild.className = 'b';
117 cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
120 // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
122 testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
123 brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
126 features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
129 if (testNode.querySelectorAll){
130 // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
132 testNode.innerHTML = 'foo</foo>';
133 selected = testNode.querySelectorAll('*');
134 features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
137 // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
139 testNode.innerHTML = '<a class="MiX"></a>';
140 features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
143 // Webkit and Opera dont return selected options on querySelectorAll
145 testNode.innerHTML = '<select><option selected="selected">a</option></select>';
146 features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
149 // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
151 testNode.innerHTML = '<a class=""></a>';
152 features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
157 // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
159 testNode.innerHTML = '<form action="s"><input id="action"/></form>';
160 brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
163 // native matchesSelector function
165 features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
166 if (features.nativeMatchesSelector) try {
167 // if matchesSelector trows errors on incorrect sintaxes we can use it
168 features.nativeMatchesSelector.call(root, ':slick');
169 features.nativeMatchesSelector = null;
175 root.slick_expando = 1;
176 delete root.slick_expando;
177 features.getUID = this.getUIDHTML;
179 features.getUID = this.getUIDXML;
182 testRoot.removeChild(testNode);
183 testNode = selected = testRoot = null;
187 features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
188 var method = this.attributeGetters[name];
189 if (method) return method.call(node);
190 var attributeNode = node.getAttributeNode(name);
191 return (attributeNode) ? attributeNode.nodeValue : null;
192 } : function(node, name){
193 var method = this.attributeGetters[name];
194 return (method) ? method.call(node) : node.getAttribute(name);
199 features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
200 return node.hasAttribute(attribute);
201 } : function(node, attribute){
202 node = node.getAttributeNode(attribute);
203 return !!(node && (node.specified || node.nodeValue));
207 // FIXME: Add specs: local.contains should be different for xml and html documents?
208 var nativeRootContains = root && this.isNativeCode(root.contains),
209 nativeDocumentContains = document && this.isNativeCode(document.contains);
211 features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
212 return context.contains(node);
213 } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
214 // IE8 does not have .contains on document.
215 return context === node || ((context === document) ? document.documentElement : context).contains(node);
216 } : (root && root.compareDocumentPosition) ? function(context, node){
217 return context === node || !!(context.compareDocumentPosition(node) & 16);
218 } : function(context, node){
220 if (node === context) return true;
221 } while ((node = node.parentNode));
225 // document order sorting
226 // credits to Sizzle (http://sizzlejs.com/)
228 features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
229 if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
230 return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
231 } : ('sourceIndex' in root) ? function(a, b){
232 if (!a.sourceIndex || !b.sourceIndex) return 0;
233 return a.sourceIndex - b.sourceIndex;
234 } : (document.createRange) ? function(a, b){
235 if (!a.ownerDocument || !b.ownerDocument) return 0;
236 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
237 aRange.setStart(a, 0);
239 bRange.setStart(b, 0);
241 return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
246 for (feature in features){
247 this[feature] = features[feature];
253 var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
254 reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
255 qsaFailExpCache = {};
257 local.search = function(context, expression, append, first){
259 var found = this.found = (first) ? null : (append || []);
261 if (!context) return found;
262 else if (context.navigator) context = context.document; // Convert the node from a window to a document
263 else if (!context.nodeType) return found;
268 uniques = this.uniques = {},
269 hasOthers = !!(append && append.length),
270 contextIsDocument = (context.nodeType == 9);
272 if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
274 // avoid duplicating items already in the append array
275 if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
279 if (typeof expression == 'string'){ // expression is a string
281 /*<simple-selectors-override>*/
282 var simpleSelector = expression.match(reSimpleSelector);
283 simpleSelectors: if (simpleSelector){
285 var symbol = simpleSelector[1],
286 name = simpleSelector[2],
291 if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
292 nodes = context.getElementsByTagName(name);
293 if (first) return nodes[0] || null;
294 for (i = 0; node = nodes[i++];){
295 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
298 } else if (symbol == '#'){
300 if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
301 node = context.getElementById(name);
302 if (!node) return found;
303 if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
304 if (first) return node || null;
305 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
307 } else if (symbol == '.'){
309 if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
310 if (context.getElementsByClassName && !this.brokenGEBCN){
311 nodes = context.getElementsByClassName(name);
312 if (first) return nodes[0] || null;
313 for (i = 0; node = nodes[i++];){
314 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
317 var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
318 nodes = context.getElementsByTagName('*');
319 for (i = 0; node = nodes[i++];){
320 className = node.className;
321 if (!(className && matchClass.test(className))) continue;
322 if (first) return node;
323 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
329 if (hasOthers) this.sort(found);
330 return (first) ? null : found;
333 /*</simple-selectors-override>*/
335 /*<query-selector-override>*/
336 querySelector: if (context.querySelectorAll){
338 if (!this.isHTMLDocument
339 || qsaFailExpCache[expression]
340 //TODO: only skip when expression is actually mixed case
341 || this.brokenMixedCaseQSA
342 || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
343 || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
344 || (!contextIsDocument //Abort when !contextIsDocument and...
345 // there are multiple expressions in the selector
346 // since we currently only fix non-document rooted QSA for single expression selectors
347 && expression.indexOf(',') > -1
350 ) break querySelector;
352 var _expression = expression, _context = context;
353 if (!contextIsDocument){
354 // non-document rooted QSA
355 // credits to Andrew Dupont
356 var currentId = _context.getAttribute('id'), slickid = 'slickid__';
357 _context.setAttribute('id', slickid);
358 _expression = '#' + slickid + ' ' + _expression;
359 context = _context.parentNode;
363 if (first) return context.querySelector(_expression) || null;
364 else nodes = context.querySelectorAll(_expression);
366 qsaFailExpCache[expression] = 1;
369 if (!contextIsDocument){
370 if (currentId) _context.setAttribute('id', currentId);
371 else _context.removeAttribute('id');
376 if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
377 if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
378 } else for (i = 0; node = nodes[i++];){
379 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
382 if (hasOthers) this.sort(found);
386 /*</query-selector-override>*/
388 parsed = this.Slick.parse(expression);
389 if (!parsed.length) return found;
390 } else if (expression == null){ // there is no expression
392 } else if (expression.Slick){ // expression is a parsed Slick object
394 } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
395 (found) ? found.push(expression) : found = expression;
397 } else { // other junk
401 /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
403 // cache elements for the nth selectors
406 this.posNTHLast = {};
407 this.posNTHType = {};
408 this.posNTHTypeLast = {};
410 /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
412 // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
413 this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
415 if (found == null) found = [];
420 var combinator, tag, id, classList, classes, attributes, pseudos;
421 var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
423 search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
425 combinator = 'combinator:' + currentBit.combinator;
426 if (!this[combinator]) continue search;
428 tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
430 classList = currentBit.classList;
431 classes = currentBit.classes;
432 attributes = currentBit.attributes;
433 pseudos = currentBit.pseudos;
434 lastBit = (j === (currentExpression.length - 1));
436 this.bitUniques = {};
439 this.uniques = uniques;
447 this[combinator](context, tag, id, classes, attributes, pseudos, classList);
448 if (first && lastBit && found.length) break search;
450 if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
451 this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
452 if (found.length) break search;
453 } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
456 currentItems = this.found;
459 // should sort if there are nodes in append and if you pass multiple expressions.
460 if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
462 return (first) ? (found[0] || null) : found;
468 local.uidk = 'slick-uniqueid';
470 local.getUIDXML = function(node){
471 var uid = node.getAttribute(this.uidk);
474 node.setAttribute(this.uidk, uid);
479 local.getUIDHTML = function(node){
480 return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
483 // sort based on the setDocument documentSorter method.
485 local.sort = function(results){
486 if (!this.documentSorter) return results;
487 results.sort(this.documentSorter);
491 /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
495 local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
497 local.parseNTHArgument = function(argument){
498 var parsed = argument.match(this.matchNTH);
499 if (!parsed) return false;
500 var special = parsed[2] || false;
501 var a = parsed[1] || 1;
502 if (a == '-') a = -1;
503 var b = +parsed[3] || 0;
505 (special == 'n') ? {a: a, b: b} :
506 (special == 'odd') ? {a: 2, b: 1} :
507 (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
509 return (this.cacheNTH[argument] = parsed);
512 local.createNTHPseudo = function(child, sibling, positions, ofType){
513 return function(node, argument){
514 var uid = this.getUID(node);
515 if (!this[positions][uid]){
516 var parent = node.parentNode;
517 if (!parent) return false;
518 var el = parent[child], count = 1;
520 var nodeName = node.nodeName;
522 if (el.nodeName != nodeName) continue;
523 this[positions][this.getUID(el)] = count++;
524 } while ((el = el[sibling]));
527 if (el.nodeType != 1) continue;
528 this[positions][this.getUID(el)] = count++;
529 } while ((el = el[sibling]));
532 argument = argument || 'n';
533 var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
534 if (!parsed) return false;
535 var a = parsed.a, b = parsed.b, pos = this[positions][uid];
536 if (a == 0) return b == pos;
538 if (pos < b) return false;
540 if (b < pos) return false;
542 return ((pos - b) % a) == 0;
546 /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
548 local.pushArray = function(node, tag, id, classes, attributes, pseudos){
549 if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
552 local.pushUID = function(node, tag, id, classes, attributes, pseudos){
553 var uid = this.getUID(node);
554 if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
555 this.uniques[uid] = true;
556 this.found.push(node);
560 local.matchNode = function(node, selector){
561 if (this.isHTMLDocument && this.nativeMatchesSelector){
563 return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
564 } catch(matchError){}
567 var parsed = this.Slick.parse(selector);
568 if (!parsed) return true;
570 // simple (single) selectors
571 var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
572 for (i = 0; (currentExpression = expressions[i]); i++){
573 if (currentExpression.length == 1){
574 var exp = currentExpression[0];
575 if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
580 if (simpleExpCounter == parsed.length) return false;
582 var nodes = this.search(this.document, parsed), item;
583 for (i = 0; item = nodes[i++];){
584 if (item === node) return true;
589 local.matchPseudo = function(node, name, argument){
590 var pseudoName = 'pseudo:' + name;
591 if (this[pseudoName]) return this[pseudoName](node, argument);
592 var attribute = this.getAttribute(node, name);
593 return (argument) ? argument == attribute : !!attribute;
596 local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
598 var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
600 if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
602 if (nodeName != tag) return false;
606 if (id && node.getAttribute('id') != id) return false;
609 if (classes) for (i = classes.length; i--;){
610 cls = this.getAttribute(node, 'class');
611 if (!(cls && classes[i].regexp.test(cls))) return false;
613 if (attributes) for (i = attributes.length; i--;){
614 part = attributes[i];
615 if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
617 if (pseudos) for (i = pseudos.length; i--;){
619 if (!this.matchPseudo(node, part.key, part.value)) return false;
626 ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
628 var i, item, children;
630 if (this.isHTMLDocument){
632 item = this.document.getElementById(id);
633 if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
634 // all[id] returns all the elements with that name or id inside node
635 // if theres just one it will return the element, else it will be a collection
636 children = node.all[id];
637 if (!children) return;
638 if (!children[0]) children = [children];
639 for (i = 0; item = children[i++];){
640 var idNode = item.getAttributeNode('id');
641 if (idNode && idNode.nodeValue == id){
642 this.push(item, tag, null, classes, attributes, pseudos);
649 // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
650 if (this.contains(this.root, node)) return;
652 } else if (this.document !== node && !this.contains(node, item)) return;
653 this.push(item, tag, null, classes, attributes, pseudos);
656 getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
657 children = node.getElementsByClassName(classList.join(' '));
658 if (!(children && children.length)) break getByClass;
659 for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
664 children = node.getElementsByTagName(tag);
665 if (!(children && children.length)) break getByTag;
666 if (!this.brokenStarGEBTN) tag = null;
667 for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
671 '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
672 if ((node = node.firstChild)) do {
673 if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
674 } while ((node = node.nextSibling));
677 '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
678 while ((node = node.nextSibling)) if (node.nodeType == 1){
679 this.push(node, tag, id, classes, attributes, pseudos);
684 '^': function(node, tag, id, classes, attributes, pseudos){ // first child
685 node = node.firstChild;
687 if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
688 else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
692 '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
693 while ((node = node.nextSibling)){
694 if (node.nodeType != 1) continue;
695 var uid = this.getUID(node);
696 if (this.bitUniques[uid]) break;
697 this.bitUniques[uid] = true;
698 this.push(node, tag, id, classes, attributes, pseudos);
702 '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
703 this['combinator:+'](node, tag, id, classes, attributes, pseudos);
704 this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
707 '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
708 this['combinator:~'](node, tag, id, classes, attributes, pseudos);
709 this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
712 '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
713 while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
716 '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
717 node = node.parentNode;
718 if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
721 '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
722 while ((node = node.previousSibling)) if (node.nodeType == 1){
723 this.push(node, tag, id, classes, attributes, pseudos);
728 '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
729 node = node.lastChild;
731 if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
732 else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
736 '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
737 while ((node = node.previousSibling)){
738 if (node.nodeType != 1) continue;
739 var uid = this.getUID(node);
740 if (this.bitUniques[uid]) break;
741 this.bitUniques[uid] = true;
742 this.push(node, tag, id, classes, attributes, pseudos);
748 for (var c in combinators) local['combinator:' + c] = combinators[c];
752 /*<pseudo-selectors>*/
754 'empty': function(node){
755 var child = node.firstChild;
756 return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
759 'not': function(node, expression){
760 return !this.matchNode(node, expression);
763 'contains': function(node, text){
764 return (node.innerText || node.textContent || '').indexOf(text) > -1;
767 'first-child': function(node){
768 while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
772 'last-child': function(node){
773 while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
777 'only-child': function(node){
779 while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
781 while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
785 /*<nth-pseudo-selectors>*/
787 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
789 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
791 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
793 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
795 'index': function(node, index){
796 return this['pseudo:nth-child'](node, '' + (index + 1));
799 'even': function(node){
800 return this['pseudo:nth-child'](node, '2n');
803 'odd': function(node){
804 return this['pseudo:nth-child'](node, '2n+1');
807 /*</nth-pseudo-selectors>*/
809 /*<of-type-pseudo-selectors>*/
811 'first-of-type': function(node){
812 var nodeName = node.nodeName;
813 while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
817 'last-of-type': function(node){
818 var nodeName = node.nodeName;
819 while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
823 'only-of-type': function(node){
824 var prev = node, nodeName = node.nodeName;
825 while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
827 while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
831 /*</of-type-pseudo-selectors>*/
835 'enabled': function(node){
836 return !node.disabled;
839 'disabled': function(node){
840 return node.disabled;
843 'checked': function(node){
844 return node.checked || node.selected;
847 'focus': function(node){
848 return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
851 'root': function(node){
852 return (node === this.root);
855 'selected': function(node){
856 return node.selected;
859 /*</pseudo-selectors>*/
862 for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
864 // attributes methods
866 var attributeGetters = local.attributeGetters = {
869 return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
873 return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
877 return (this.style) ? this.style.cssText : this.getAttribute('style');
880 'tabindex': function(){
881 var attributeNode = this.getAttributeNode('tabindex');
882 return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
886 return this.getAttribute('type');
889 'maxlength': function(){
890 var attributeNode = this.getAttributeNode('maxLength');
891 return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
896 attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
900 var Slick = local.Slick = (this.Slick || {});
902 Slick.version = '1.1.7';
906 Slick.search = function(context, expression, append){
907 return local.search(context, expression, append);
910 Slick.find = function(context, expression){
911 return local.search(context, expression, null, true);
914 // Slick containment checker
916 Slick.contains = function(container, node){
917 local.setDocument(container);
918 return local.contains(container, node);
921 // Slick attribute getter
923 Slick.getAttribute = function(node, name){
924 local.setDocument(node);
925 return local.getAttribute(node, name);
928 Slick.hasAttribute = function(node, name){
929 local.setDocument(node);
930 return local.hasAttribute(node, name);
935 Slick.match = function(node, selector){
936 if (!(node && selector)) return false;
937 if (!selector || selector === node) return true;
938 local.setDocument(node);
939 return local.matchNode(node, selector);
942 // Slick attribute accessor
944 Slick.defineAttributeGetter = function(name, fn){
945 local.attributeGetters[name] = fn;
949 Slick.lookupAttributeGetter = function(name){
950 return local.attributeGetters[name];
953 // Slick pseudo accessor
955 Slick.definePseudo = function(name, fn){
956 local['pseudo:' + name] = function(node, argument){
957 return fn.call(node, argument);
962 Slick.lookupPseudo = function(name){
963 var pseudo = local['pseudo:' + name];
964 if (pseudo) return function(argument){
965 return pseudo.call(this, argument);
970 // Slick overrides accessor
972 Slick.override = function(regexp, fn){
973 local.override(regexp, fn);
977 Slick.isXML = local.isXML;
979 Slick.uidOf = function(node){
980 return local.getUIDHTML(node);
983 if (!this.Slick) this.Slick = Slick;
985 }).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);