Integrate support for the W3C Selectors API into the Selector class. Will now use the API when possible (browser supports the API *and* recognizes the given selector). Means minor changes to the semantics of :enabled, :disabled, and :empty in order to comply with CSS spec.

This commit is contained in:
Andrew Dupont 2008-03-27 01:18:15 -05:00
parent 855e27311d
commit 03c1530014
3 changed files with 51 additions and 17 deletions

View File

@ -1,3 +1,5 @@
* Integrate support for the W3C Selectors API into the Selector class. Will now use the API when possible (browser supports the API *and* recognizes the given selector). Means minor changes to the semantics of :enabled, :disabled, and :empty in order to comply with CSS spec.
* Avoid re-extending element in Element#getDimensions. [kangax]
* Prevent Hash#toQueryString from serializing objets. [kangax, Tobie Langel]

View File

@ -5,7 +5,17 @@
var Selector = Class.create({
initialize: function(expression) {
this.expression = expression.strip();
this.compileMatcher();
if (this.shouldUseSelectorsAPI()) {
this.mode = 'selectorsAPI';
} else if (this.shouldUseXPath()) {
this.mode = 'xpath';
this.compileXPathMatcher();
} else {
this.mode = "normal";
this.compileMatcher();
}
},
shouldUseXPath: function() {
@ -20,16 +30,30 @@ var Selector = Class.create({
// XPath can't do namespaced attributes, nor can it read
// the "checked" property from DOM nodes
if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
if ((/(\[[\w-]*?:|:checked)/).test(e))
return false;
return true;
},
compileMatcher: function() {
if (this.shouldUseXPath())
return this.compileXPathMatcher();
shouldUseSelectorsAPI: function() {
if (!Prototype.BrowserFeatures.SelectorsAPI) 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;
@ -86,8 +110,16 @@ var Selector = Class.create({
findElements: function(root) {
root = root || document;
if (this.xpath) return document._getElementsByXPath(this.xpath, root);
return this.matcher(root);
var results;
switch (this.mode) {
case 'selectorsAPI':
return $A(root.querySelectorAll(this.expression));
case 'xpath':
return document._getElementsByXPath(this.xpath, root);
default:
return this.matcher(root);
}
},
match: function(element) {
@ -178,10 +210,10 @@ Object.extend(Selector, {
'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 or translate(text(), ' \t\r\n', '') = '')]",
'empty': "[count(*) = 0 and (count(text()) = 0)]",
'checked': "[@checked]",
'disabled': "[@disabled]",
'enabled': "[not(@disabled)]",
'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;
@ -575,7 +607,7 @@ Object.extend(Selector, {
'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 && !node.innerHTML.match(/^\s*$/))) continue;
if (node.tagName == '!' || node.firstChild) continue;
results.push(node);
}
return results;
@ -593,7 +625,8 @@ Object.extend(Selector, {
'enabled': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (!node.disabled) results.push(node);
if (!node.disabled && (!node.type || node.type !== 'hidden'))
results.push(node);
return results;
},

View File

@ -387,14 +387,14 @@
testSelectorWithEnabledDisabledChecked: function() {
this.assertEnumEqual([$('disabled_text_field')], $$('#troubleForm > *:disabled'));
this.assertEnumEqual($('troubleForm').getInputs().without($('disabled_text_field')), $$('#troubleForm > *:enabled'));
this.assertEnumEqual($('troubleForm').getInputs().without($('disabled_text_field'), $('hidden')), $$('#troubleForm > *:enabled'));
this.assertEnumEqual($('checked_box', 'checked_radio'), $$('#troubleForm *:checked'));
},
testSelectorWithEmpty: function() {
$('level3_1').innerHTML = "\t\n\n\r\n\t ";
this.assertEnumEqual($('level3_1', 'level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:empty'));
this.assertEnumEqual([$('level_only_child')], $$('#level_only_child:empty'));
$('level3_1').innerHTML = "";
this.assertEnumEqual($('level3_1', 'level3_2', 'level2_3'), $$('#level1 *:empty'));
this.assertEnumEqual([], $$('#level_only_child:empty'), 'newlines count as content!');
},
testIdenticalResultsFromEquivalentSelectors: function() {
@ -407,7 +407,6 @@
this.assertEnumEqual($$('ul > li:nth-child(odd)'), $$('ul > li:nth-child(2n+1)'));
this.assertEnumEqual($$('ul > li:first-child'), $$('ul > li:nth-child(1)'));
this.assertEnumEqual($$('ul > li:last-child'), $$('ul > li:nth-last-child(1)'));
this.assertEnumEqual($$('#troubleForm *:enabled'), $$('#troubleForm *:not(:disabled)'));
this.assertEnumEqual($$('ul > li:nth-child(n-999)'), $$('ul > li'));
this.assertEnumEqual($$('ul>li'), $$('ul > li'));
this.assertEnumEqual($$('#p a:not(a[rel$="nofollow"])>em'), $$('#p a:not(a[rel$="nofollow"]) > em'))