Replace Prototype's Selector implementation with Sizzle
This commit is contained in:
parent
9e4a7ce8e8
commit
4dd878f237
|
@ -11,3 +11,6 @@
|
|||
path = vendor/sprockets
|
||||
url = git://github.com/sstephenson/sprockets.git
|
||||
|
||||
[submodule "vendor/sizzle"]
|
||||
path = vendor/sizzle
|
||||
url = git://github.com/jeresig/sizzle.git
|
||||
|
|
3
Rakefile
3
Rakefile
|
@ -9,6 +9,7 @@ module PrototypeHelper
|
|||
DOC_DIR = File.join(ROOT_DIR, 'doc')
|
||||
TEMPLATES_DIR = File.join(ROOT_DIR, 'templates')
|
||||
PKG_DIR = File.join(ROOT_DIR, 'pkg')
|
||||
SIZZLE_DIR = File.join(ROOT_DIR, 'vendor', 'sizzle')
|
||||
TEST_DIR = File.join(ROOT_DIR, 'test')
|
||||
TEST_UNIT_DIR = File.join(TEST_DIR, 'unit')
|
||||
TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp')
|
||||
|
@ -18,7 +19,7 @@ module PrototypeHelper
|
|||
require_sprockets
|
||||
secretary = Sprockets::Secretary.new(
|
||||
:root => File.join(ROOT_DIR, path),
|
||||
:load_path => [SRC_DIR],
|
||||
:load_path => [SRC_DIR, SIZZLE_DIR],
|
||||
:source_files => [source],
|
||||
:strip_comments => strip_comments
|
||||
)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
|
||||
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
|
||||
* license. Please see http://www.yui-ext.com/ for more information. */
|
||||
//= require <sizzle>
|
||||
|
||||
/** section: DOM
|
||||
* class Selector
|
||||
|
@ -17,139 +15,8 @@ var Selector = Class.create({
|
|||
**/
|
||||
initialize: function(expression) {
|
||||
this.expression = expression.strip();
|
||||
|
||||
if (this.shouldUseSelectorsAPI()) {
|
||||
this.mode = 'selectorsAPI';
|
||||
} else if (this.shouldUseXPath()) {
|
||||
this.mode = 'xpath';
|
||||
this.compileXPathMatcher();
|
||||
} else {
|
||||
this.mode = "normal";
|
||||
this.compileMatcher();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
shouldUseXPath: (function() {
|
||||
|
||||
// Some versions of Opera 9.x produce incorrect results when using XPath
|
||||
// with descendant combinators.
|
||||
// see: http://opera.remcol.ath.cx/bugs/index.php?action=bug&id=652
|
||||
var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
|
||||
var isBuggy = false;
|
||||
if (document.evaluate && window.XPathResult) {
|
||||
var el = document.createElement('div');
|
||||
el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
|
||||
|
||||
var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
|
||||
"//*[local-name()='li' or local-name()='LI']";
|
||||
|
||||
var result = document.evaluate(xpath, el, null,
|
||||
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
||||
|
||||
isBuggy = (result.snapshotLength !== 2);
|
||||
el = null;
|
||||
}
|
||||
return isBuggy;
|
||||
})();
|
||||
|
||||
return function() {
|
||||
if (!Prototype.BrowserFeatures.XPath) return false;
|
||||
|
||||
var e = this.expression;
|
||||
|
||||
// Safari 3 chokes on :*-of-type and :empty
|
||||
if (Prototype.Browser.WebKit &&
|
||||
(e.include("-of-type") || e.include(":empty")))
|
||||
return false;
|
||||
|
||||
// XPath can't do namespaced attributes, nor can it read
|
||||
// the "checked" property from DOM nodes
|
||||
if ((/(\[[\w-]*?:|:checked)/).test(e))
|
||||
return false;
|
||||
|
||||
if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
})(),
|
||||
|
||||
shouldUseSelectorsAPI: function() {
|
||||
if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
|
||||
|
||||
if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
|
||||
|
||||
if (!Selector._div) Selector._div = new Element('div');
|
||||
|
||||
// Make sure the browser treats the selector as valid. Test on an
|
||||
// isolated element to minimize cost of this check.
|
||||
try {
|
||||
Selector._div.querySelector(this.expression);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
compileMatcher: function() {
|
||||
var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
|
||||
c = Selector.criteria, le, p, m, len = ps.length, name;
|
||||
|
||||
if (Selector._cache[e]) {
|
||||
this.matcher = Selector._cache[e];
|
||||
return;
|
||||
}
|
||||
|
||||
this.matcher = ["this.matcher = function(root) {",
|
||||
"var r = root, h = Selector.handlers, c = false, n;"];
|
||||
|
||||
while (e && le != e && (/\S/).test(e)) {
|
||||
le = e;
|
||||
for (var i = 0; i<len; i++) {
|
||||
p = ps[i].re;
|
||||
name = ps[i].name;
|
||||
if (m = e.match(p)) {
|
||||
this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
|
||||
new Template(c[name]).evaluate(m));
|
||||
e = e.replace(m[0], '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.matcher.push("return h.unique(n);\n}");
|
||||
eval(this.matcher.join('\n'));
|
||||
Selector._cache[this.expression] = this.matcher;
|
||||
},
|
||||
|
||||
compileXPathMatcher: function() {
|
||||
var e = this.expression, ps = Selector.patterns,
|
||||
x = Selector.xpath, le, m, len = ps.length, name;
|
||||
|
||||
if (Selector._cache[e]) {
|
||||
this.xpath = Selector._cache[e]; return;
|
||||
}
|
||||
|
||||
this.matcher = ['.//*'];
|
||||
while (e && le != e && (/\S/).test(e)) {
|
||||
le = e;
|
||||
for (var i = 0; i<len; i++) {
|
||||
name = ps[i].name;
|
||||
if (m = e.match(ps[i].re)) {
|
||||
this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
|
||||
new Template(x[name]).evaluate(m));
|
||||
e = e.replace(m[0], '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.xpath = this.matcher.join('');
|
||||
Selector._cache[this.expression] = this.xpath;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Selector#findElements(root) -> [Element...]
|
||||
* - root (Element || document): A "scope" to search within. All results will
|
||||
|
@ -158,644 +25,29 @@ var Selector = Class.create({
|
|||
* Searches the document for elements that match the instance's CSS
|
||||
* selector.
|
||||
**/
|
||||
findElements: function(root) {
|
||||
root = root || document;
|
||||
var e = this.expression, results;
|
||||
|
||||
switch (this.mode) {
|
||||
case 'selectorsAPI':
|
||||
// querySelectorAll queries document-wide, then filters to descendants
|
||||
// of the context element. That's not what we want.
|
||||
// Add an explicit context to the selector if necessary.
|
||||
if (root !== document) {
|
||||
var oldId = root.id, id = $(root).identify();
|
||||
// Escape special characters in the ID.
|
||||
id = id.replace(/([\.:])/g, "\\$1");
|
||||
e = "#" + id + " " + e;
|
||||
}
|
||||
|
||||
results = $A(root.querySelectorAll(e)).map(Element.extend);
|
||||
root.id = oldId;
|
||||
|
||||
return results;
|
||||
case 'xpath':
|
||||
return document._getElementsByXPath(this.xpath, root);
|
||||
default:
|
||||
return this.matcher(root);
|
||||
}
|
||||
findElements: function(rootElement) {
|
||||
return Sizzle(this.expression, rootElement || document);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Selector#match(element) -> Boolean
|
||||
*
|
||||
* Tests whether a `element` matches the instance's CSS selector.
|
||||
**/
|
||||
match: function(element) {
|
||||
this.tokens = [];
|
||||
|
||||
var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
|
||||
var le, p, m, len = ps.length, name;
|
||||
|
||||
while (e && le !== e && (/\S/).test(e)) {
|
||||
le = e;
|
||||
for (var i = 0; i<len; i++) {
|
||||
p = ps[i].re;
|
||||
name = ps[i].name;
|
||||
if (m = e.match(p)) {
|
||||
// use the Selector.assertions methods unless the selector
|
||||
// is too complex.
|
||||
if (as[name]) {
|
||||
this.tokens.push([name, Object.clone(m)]);
|
||||
e = e.replace(m[0], '');
|
||||
} else {
|
||||
// reluctantly do a document-wide search
|
||||
// and look for a match in the array
|
||||
return this.findElements(document).include(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var match = true, name, matches;
|
||||
for (var i = 0, token; token = this.tokens[i]; i++) {
|
||||
name = token[0], matches = token[1];
|
||||
if (!Selector.assertions[name](element, matches)) {
|
||||
match = false; break;
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
return Sizzle.matches(this.expression, [element]).length == 1;
|
||||
},
|
||||
|
||||
|
||||
toString: function() {
|
||||
return this.expression;
|
||||
},
|
||||
|
||||
|
||||
inspect: function() {
|
||||
return "#<Selector:" + this.expression.inspect() + ">";
|
||||
return "#<Selector: " + this.expression + ">";
|
||||
}
|
||||
});
|
||||
|
||||
if (Prototype.BrowserFeatures.SelectorsAPI &&
|
||||
document.compatMode === 'BackCompat') {
|
||||
// Versions of Safari 3 before 3.1.2 treat class names case-insensitively in
|
||||
// quirks mode. If we detect this behavior, we should use a different
|
||||
// approach.
|
||||
Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
|
||||
var div = document.createElement('div'),
|
||||
span = document.createElement('span');
|
||||
|
||||
div.id = "prototype_test_id";
|
||||
span.className = 'Test';
|
||||
div.appendChild(span);
|
||||
var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
|
||||
div = span = null;
|
||||
return isIgnored;
|
||||
})();
|
||||
}
|
||||
|
||||
Object.extend(Selector, {
|
||||
_cache: { },
|
||||
|
||||
xpath: {
|
||||
descendant: "//*",
|
||||
child: "/*",
|
||||
adjacent: "/following-sibling::*[1]",
|
||||
laterSibling: '/following-sibling::*',
|
||||
tagName: function(m) {
|
||||
if (m[1] == '*') return '';
|
||||
return "[local-name()='" + m[1].toLowerCase() +
|
||||
"' or local-name()='" + m[1].toUpperCase() + "']";
|
||||
},
|
||||
className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
|
||||
id: "[@id='#{1}']",
|
||||
attrPresence: function(m) {
|
||||
m[1] = m[1].toLowerCase();
|
||||
return new Template("[@#{1}]").evaluate(m);
|
||||
},
|
||||
attr: function(m) {
|
||||
m[1] = m[1].toLowerCase();
|
||||
m[3] = m[5] || m[6];
|
||||
return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
|
||||
},
|
||||
pseudo: function(m) {
|
||||
var h = Selector.xpath.pseudos[m[1]];
|
||||
if (!h) return '';
|
||||
if (Object.isFunction(h)) return h(m);
|
||||
return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
|
||||
},
|
||||
operators: {
|
||||
'=': "[@#{1}='#{3}']",
|
||||
'!=': "[@#{1}!='#{3}']",
|
||||
'^=': "[starts-with(@#{1}, '#{3}')]",
|
||||
'$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
|
||||
'*=': "[contains(@#{1}, '#{3}')]",
|
||||
'~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
|
||||
'|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
|
||||
},
|
||||
pseudos: {
|
||||
'first-child': '[not(preceding-sibling::*)]',
|
||||
'last-child': '[not(following-sibling::*)]',
|
||||
'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
|
||||
'empty': "[count(*) = 0 and (count(text()) = 0)]",
|
||||
'checked': "[@checked]",
|
||||
'disabled': "[(@disabled) and (@type!='hidden')]",
|
||||
'enabled': "[not(@disabled) and (@type!='hidden')]",
|
||||
'not': function(m) {
|
||||
var e = m[6], p = Selector.patterns,
|
||||
x = Selector.xpath, le, v, len = p.length, name;
|
||||
|
||||
var exclusion = [];
|
||||
while (e && le != e && (/\S/).test(e)) {
|
||||
le = e;
|
||||
for (var i = 0; i<len; i++) {
|
||||
name = p[i].name
|
||||
if (m = e.match(p[i].re)) {
|
||||
v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
|
||||
exclusion.push("(" + v.substring(1, v.length - 1) + ")");
|
||||
e = e.replace(m[0], '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "[not(" + exclusion.join(" and ") + ")]";
|
||||
},
|
||||
'nth-child': function(m) {
|
||||
return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
|
||||
},
|
||||
'nth-last-child': function(m) {
|
||||
return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
|
||||
},
|
||||
'nth-of-type': function(m) {
|
||||
return Selector.xpath.pseudos.nth("position() ", m);
|
||||
},
|
||||
'nth-last-of-type': function(m) {
|
||||
return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
|
||||
},
|
||||
'first-of-type': function(m) {
|
||||
m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
|
||||
},
|
||||
'last-of-type': function(m) {
|
||||
m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
|
||||
},
|
||||
'only-of-type': function(m) {
|
||||
var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
|
||||
},
|
||||
nth: function(fragment, m) {
|
||||
var mm, formula = m[6], predicate;
|
||||
if (formula == 'even') formula = '2n+0';
|
||||
if (formula == 'odd') formula = '2n+1';
|
||||
if (mm = formula.match(/^(\d+)$/)) // digit only
|
||||
return '[' + fragment + "= " + mm[1] + ']';
|
||||
if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
|
||||
if (mm[1] == "-") mm[1] = -1;
|
||||
var a = mm[1] ? Number(mm[1]) : 1;
|
||||
var b = mm[2] ? Number(mm[2]) : 0;
|
||||
predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
|
||||
"((#{fragment} - #{b}) div #{a} >= 0)]";
|
||||
return new Template(predicate).evaluate({
|
||||
fragment: fragment, a: a, b: b });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
criteria: {
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
|
||||
attr: function(m) {
|
||||
m[3] = (m[5] || m[6]);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
|
||||
},
|
||||
pseudo: function(m) {
|
||||
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
|
||||
return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
|
||||
},
|
||||
descendant: 'c = "descendant";',
|
||||
child: 'c = "child";',
|
||||
adjacent: 'c = "adjacent";',
|
||||
laterSibling: 'c = "laterSibling";'
|
||||
},
|
||||
|
||||
patterns: [
|
||||
// combinators must be listed first
|
||||
// (and descendant needs to be last combinator)
|
||||
{ name: 'laterSibling', re: /^\s*~\s*/ },
|
||||
{ name: 'child', re: /^\s*>\s*/ },
|
||||
{ name: 'adjacent', re: /^\s*\+\s*/ },
|
||||
{ name: 'descendant', re: /^\s/ },
|
||||
|
||||
// selectors follow
|
||||
{ name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
|
||||
{ name: 'id', re: /^#([\w\-\*]+)(\b|$)/ },
|
||||
{ name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ },
|
||||
{ name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
|
||||
{ name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
|
||||
{ name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
|
||||
],
|
||||
|
||||
// for Selector.match and Element#match
|
||||
assertions: {
|
||||
tagName: function(element, matches) {
|
||||
return matches[1].toUpperCase() == element.tagName.toUpperCase();
|
||||
},
|
||||
|
||||
className: function(element, matches) {
|
||||
return Element.hasClassName(element, matches[1]);
|
||||
},
|
||||
|
||||
id: function(element, matches) {
|
||||
return element.id === matches[1];
|
||||
},
|
||||
|
||||
attrPresence: function(element, matches) {
|
||||
return Element.hasAttribute(element, matches[1]);
|
||||
},
|
||||
|
||||
attr: function(element, matches) {
|
||||
var nodeValue = Element.readAttribute(element, matches[1]);
|
||||
return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
|
||||
}
|
||||
},
|
||||
|
||||
handlers: {
|
||||
// UTILITY FUNCTIONS
|
||||
// joins two collections
|
||||
concat: function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
a.push(node);
|
||||
return a;
|
||||
},
|
||||
|
||||
// marks an array of nodes for counting
|
||||
mark: function(nodes) {
|
||||
var _true = Prototype.emptyFunction;
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._countedByPrototype = _true;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
unmark: (function(){
|
||||
|
||||
// IE improperly serializes _countedByPrototype in (inner|outer)HTML
|
||||
// due to node properties being mapped directly to attributes
|
||||
var PROPERTIES_ATTRIBUTES_MAP = (function(){
|
||||
var el = document.createElement('div'),
|
||||
isBuggy = false,
|
||||
propName = '_countedByPrototype',
|
||||
value = 'x'
|
||||
el[propName] = value;
|
||||
isBuggy = (el.getAttribute(propName) === value);
|
||||
el = null;
|
||||
return isBuggy;
|
||||
})();
|
||||
|
||||
return PROPERTIES_ATTRIBUTES_MAP ?
|
||||
function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node.removeAttribute('_countedByPrototype');
|
||||
return nodes;
|
||||
} :
|
||||
function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._countedByPrototype = void 0;
|
||||
return nodes;
|
||||
}
|
||||
})(),
|
||||
|
||||
// mark each child node with its position (for nth calls)
|
||||
// "ofType" flag indicates whether we're indexing for nth-of-type
|
||||
// rather than nth-child
|
||||
index: function(parentNode, reverse, ofType) {
|
||||
parentNode._countedByPrototype = Prototype.emptyFunction;
|
||||
if (reverse) {
|
||||
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
|
||||
var node = nodes[i];
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
},
|
||||
|
||||
// filters out duplicates and extends all nodes
|
||||
unique: function(nodes) {
|
||||
if (nodes.length == 0) return nodes;
|
||||
var results = [], n;
|
||||
for (var i = 0, l = nodes.length; i < l; i++)
|
||||
// use `typeof` operator to prevent errors
|
||||
if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
|
||||
n._countedByPrototype = Prototype.emptyFunction;
|
||||
results.push(Element.extend(n));
|
||||
}
|
||||
return Selector.handlers.unmark(results);
|
||||
},
|
||||
|
||||
// COMBINATOR FUNCTIONS
|
||||
descendant: function(nodes) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
h.concat(results, node.getElementsByTagName('*'));
|
||||
return results;
|
||||
},
|
||||
|
||||
child: function(nodes) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
for (var j = 0, child; child = node.childNodes[j]; j++)
|
||||
if (child.nodeType == 1 && child.tagName != '!') results.push(child);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
adjacent: function(nodes) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
var next = this.nextElementSibling(node);
|
||||
if (next) results.push(next);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
laterSibling: function(nodes) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
h.concat(results, Element.nextSiblings(node));
|
||||
return results;
|
||||
},
|
||||
|
||||
nextElementSibling: function(node) {
|
||||
while (node = node.nextSibling)
|
||||
if (node.nodeType == 1) return node;
|
||||
return null;
|
||||
},
|
||||
|
||||
previousElementSibling: function(node) {
|
||||
while (node = node.previousSibling)
|
||||
if (node.nodeType == 1) return node;
|
||||
return null;
|
||||
},
|
||||
|
||||
// TOKEN FUNCTIONS
|
||||
tagName: function(nodes, root, tagName, combinator) {
|
||||
var uTagName = tagName.toUpperCase();
|
||||
var results = [], h = Selector.handlers;
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
// fastlane for ordinary descendant combinators
|
||||
if (combinator == "descendant") {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
h.concat(results, node.getElementsByTagName(tagName));
|
||||
return results;
|
||||
} else nodes = this[combinator](nodes);
|
||||
if (tagName == "*") return nodes;
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.tagName.toUpperCase() === uTagName) results.push(node);
|
||||
return results;
|
||||
} else return root.getElementsByTagName(tagName);
|
||||
},
|
||||
|
||||
id: function(nodes, root, id, combinator) {
|
||||
var targetNode = $(id), h = Selector.handlers;
|
||||
|
||||
if (root == document) {
|
||||
// We don't have to deal with orphan nodes. Easy.
|
||||
if (!targetNode) return [];
|
||||
if (!nodes) return [targetNode];
|
||||
} else {
|
||||
// In IE, we can check sourceIndex to see if root is attached
|
||||
// to the document. If not (or if sourceIndex is not present),
|
||||
// we do it the hard way.
|
||||
if (!root.sourceIndex || root.sourceIndex < 1) {
|
||||
var nodes = root.getElementsByTagName('*');
|
||||
for (var j = 0, node; node = nodes[j]; j++) {
|
||||
if (node.id === id) return [node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
if (combinator == 'child') {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (targetNode.parentNode == node) return [targetNode];
|
||||
} else if (combinator == 'descendant') {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.descendantOf(targetNode, node)) return [targetNode];
|
||||
} else if (combinator == 'adjacent') {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Selector.handlers.previousElementSibling(targetNode) == node)
|
||||
return [targetNode];
|
||||
} else nodes = h[combinator](nodes);
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node == targetNode) return [targetNode];
|
||||
return [];
|
||||
}
|
||||
return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
|
||||
},
|
||||
|
||||
className: function(nodes, root, className, combinator) {
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
return Selector.handlers.byClassName(nodes, root, className);
|
||||
},
|
||||
|
||||
byClassName: function(nodes, root, className) {
|
||||
if (!nodes) nodes = Selector.handlers.descendant([root]);
|
||||
var needle = ' ' + className + ' ';
|
||||
for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
|
||||
nodeClassName = node.className;
|
||||
if (nodeClassName.length == 0) continue;
|
||||
if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
attrPresence: function(nodes, root, attr, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.hasAttribute(node, attr)) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
attr: function(nodes, root, attr, value, operator, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var handler = Selector.operators[operator], results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
var nodeValue = Element.readAttribute(node, attr);
|
||||
if (nodeValue === null) continue;
|
||||
if (handler(nodeValue, value)) results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
pseudo: function(nodes, name, value, root, combinator) {
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
return Selector.pseudos[name](nodes, value, root);
|
||||
}
|
||||
},
|
||||
|
||||
pseudos: {
|
||||
'first-child': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
if (Selector.handlers.previousElementSibling(node)) continue;
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
'last-child': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
if (Selector.handlers.nextElementSibling(node)) continue;
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
'only-child': function(nodes, value, root) {
|
||||
var h = Selector.handlers;
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
|
||||
results.push(node);
|
||||
return results;
|
||||
},
|
||||
'nth-child': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root);
|
||||
},
|
||||
'nth-last-child': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root, true);
|
||||
},
|
||||
'nth-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root, false, true);
|
||||
},
|
||||
'nth-last-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, formula, root, true, true);
|
||||
},
|
||||
'first-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, "1", root, false, true);
|
||||
},
|
||||
'last-of-type': function(nodes, formula, root) {
|
||||
return Selector.pseudos.nth(nodes, "1", root, true, true);
|
||||
},
|
||||
'only-of-type': function(nodes, formula, root) {
|
||||
var p = Selector.pseudos;
|
||||
return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
|
||||
},
|
||||
|
||||
// handles the an+b logic
|
||||
getIndices: function(a, b, total) {
|
||||
if (a == 0) return b > 0 ? [b] : [];
|
||||
return $R(1, total).inject([], function(memo, i) {
|
||||
if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
|
||||
return memo;
|
||||
});
|
||||
},
|
||||
|
||||
// handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
|
||||
nth: function(nodes, formula, root, reverse, ofType) {
|
||||
if (nodes.length == 0) return [];
|
||||
if (formula == 'even') formula = '2n+0';
|
||||
if (formula == 'odd') formula = '2n+1';
|
||||
var h = Selector.handlers, results = [], indexed = [], m;
|
||||
h.mark(nodes);
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
if (!node.parentNode._countedByPrototype) {
|
||||
h.index(node.parentNode, reverse, ofType);
|
||||
indexed.push(node.parentNode);
|
||||
}
|
||||
}
|
||||
if (formula.match(/^\d+$/)) { // just a number
|
||||
formula = Number(formula);
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.nodeIndex == formula) results.push(node);
|
||||
} else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
|
||||
if (m[1] == "-") m[1] = -1;
|
||||
var a = m[1] ? Number(m[1]) : 1;
|
||||
var b = m[2] ? Number(m[2]) : 0;
|
||||
var indices = Selector.pseudos.getIndices(a, b, nodes.length);
|
||||
for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
|
||||
for (var j = 0; j < l; j++)
|
||||
if (node.nodeIndex == indices[j]) results.push(node);
|
||||
}
|
||||
}
|
||||
h.unmark(nodes);
|
||||
h.unmark(indexed);
|
||||
return results;
|
||||
},
|
||||
|
||||
'empty': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++) {
|
||||
// IE treats comments as element nodes
|
||||
if (node.tagName == '!' || node.firstChild) continue;
|
||||
results.push(node);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
'not': function(nodes, selector, root) {
|
||||
var h = Selector.handlers, selectorType, m;
|
||||
var exclusions = new Selector(selector).findElements(root);
|
||||
h.mark(exclusions);
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node._countedByPrototype) results.push(node);
|
||||
h.unmark(exclusions);
|
||||
return results;
|
||||
},
|
||||
|
||||
'enabled': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node.disabled && (!node.type || node.type !== 'hidden'))
|
||||
results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
'disabled': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (node.disabled) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
'checked': function(nodes, value, root) {
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (node.checked) results.push(node);
|
||||
return results;
|
||||
}
|
||||
},
|
||||
|
||||
operators: {
|
||||
'=': function(nv, v) { return nv == v; },
|
||||
'!=': function(nv, v) { return nv != v; },
|
||||
'^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
|
||||
'$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
|
||||
'*=': function(nv, v) { return nv == v || nv && nv.include(v); },
|
||||
'~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
|
||||
'|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
|
||||
'-').include('-' + (v || "").toUpperCase() + '-'); }
|
||||
},
|
||||
|
||||
/**
|
||||
* Selector.split(expression) -> [String...]
|
||||
*
|
||||
* Takes a string of CSS selectors separated by commas; returns an array
|
||||
* of individual selectors.
|
||||
*
|
||||
* Safer than doing a naive `Array#split`, since selectors can have commas
|
||||
* in other places.
|
||||
**/
|
||||
split: function(expression) {
|
||||
var expressions = [];
|
||||
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
return expressions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Selector.matchElements(elements, expression) -> [Element...]
|
||||
*
|
||||
|
@ -804,14 +56,9 @@ Object.extend(Selector, {
|
|||
* The only nodes returned will be those that match the given CSS selector.
|
||||
**/
|
||||
matchElements: function(elements, expression) {
|
||||
var matches = $$(expression), h = Selector.handlers;
|
||||
h.mark(matches);
|
||||
for (var i = 0, results = [], element; element = elements[i]; i++)
|
||||
if (element._countedByPrototype) results.push(element);
|
||||
h.unmark(matches);
|
||||
return results;
|
||||
return Sizzle.matches(expression, elements);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Selector.findElement(elements, expression[, index = 0]) -> Element
|
||||
* Selector.findElement(elements[, index = 0]) -> Element
|
||||
|
@ -822,12 +69,10 @@ Object.extend(Selector, {
|
|||
* Returns the `index`th element overall if `expression` is not given.
|
||||
**/
|
||||
findElement: function(elements, expression, index) {
|
||||
if (Object.isNumber(expression)) {
|
||||
index = expression; expression = false;
|
||||
}
|
||||
return Selector.matchElements(elements, expression || '*')[index || 0];
|
||||
if (Object.isUndefined(index)) index = 0;
|
||||
return Sizzle.matches(expression, elements)[index];
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Selector.findChildElements(element, expressions) -> [Element...]
|
||||
*
|
||||
|
@ -835,33 +80,17 @@ Object.extend(Selector, {
|
|||
* (or selectors) specified in `expressions`.
|
||||
**/
|
||||
findChildElements: function(element, expressions) {
|
||||
expressions = Selector.split(expressions.join(','));
|
||||
var results = [], h = Selector.handlers;
|
||||
for (var i = 0, l = expressions.length, selector; i < l; i++) {
|
||||
selector = new Selector(expressions[i].strip());
|
||||
h.concat(results, selector.findElements(element));
|
||||
}
|
||||
return (l > 1) ? h.unique(results) : results;
|
||||
var results = [], exprs = expressions.toArray();
|
||||
while (exprs.length) Sizzle(exprs.shift(), element || document, results);
|
||||
return results;
|
||||
}
|
||||
});
|
||||
|
||||
if (Prototype.Browser.IE) {
|
||||
Object.extend(Selector.handlers, {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
concat: function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** related to: Selector
|
||||
* $$(expression...) -> [Element...]
|
||||
*
|
||||
* Returns all elements in the document that match the provided CSS selectors.
|
||||
**/
|
||||
function $$() {
|
||||
return Selector.findChildElements(document, $A(arguments));
|
||||
return Selector.findChildElements(document, $A(arguments)).map(Element.extend);
|
||||
}
|
||||
|
|
|
@ -356,14 +356,6 @@ new Test.Unit.Runner({
|
|||
this.assert(typeof results[2].show == 'function');
|
||||
},
|
||||
|
||||
testCountedIsNotAnAttribute: function() {
|
||||
var el = $('list');
|
||||
Selector.handlers.mark([el]);
|
||||
this.assert(!el.innerHTML.include("_counted"));
|
||||
Selector.handlers.unmark([el]);
|
||||
this.assert(!el.innerHTML.include("_counted"));
|
||||
},
|
||||
|
||||
testCopiedNodesGetIncluded: function() {
|
||||
this.assertElementsMatch(
|
||||
Selector.matchElements($('counted_container').descendants(), 'div'),
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e0f5cbc75d12aa78f3ef30930414b2f88da7b2b8
|
Loading…
Reference in New Issue