130 lines
4.6 KiB
JavaScript
130 lines
4.6 KiB
JavaScript
|
var Selector = Class.create();
|
||
|
Selector.prototype = {
|
||
|
initialize: function(expression) {
|
||
|
this.params = {classNames: []};
|
||
|
this.expression = expression.toString().strip();
|
||
|
this.parseExpression();
|
||
|
this.compileMatcher();
|
||
|
},
|
||
|
|
||
|
parseExpression: function() {
|
||
|
function abort(message) { throw 'Parse error in selector: ' + message; }
|
||
|
|
||
|
if (this.expression == '') abort('empty expression');
|
||
|
|
||
|
var params = this.params, expr = this.expression, match, modifier, clause, rest;
|
||
|
while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
|
||
|
params.attributes = params.attributes || [];
|
||
|
params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
|
||
|
expr = match[1];
|
||
|
}
|
||
|
|
||
|
if (expr == '*') return this.params.wildcard = true;
|
||
|
|
||
|
while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
|
||
|
modifier = match[1], clause = match[2], rest = match[3];
|
||
|
switch (modifier) {
|
||
|
case '#': params.id = clause; break;
|
||
|
case '.': params.classNames.push(clause); break;
|
||
|
case '':
|
||
|
case undefined: params.tagName = clause.toUpperCase(); break;
|
||
|
default: abort(expr.inspect());
|
||
|
}
|
||
|
expr = rest;
|
||
|
}
|
||
|
|
||
|
if (expr.length > 0) abort(expr.inspect());
|
||
|
},
|
||
|
|
||
|
buildMatchExpression: function() {
|
||
|
var params = this.params, conditions = [], clause;
|
||
|
|
||
|
if (params.wildcard)
|
||
|
conditions.push('true');
|
||
|
if (clause = params.id)
|
||
|
conditions.push('element.readAttribute("id") == ' + clause.inspect());
|
||
|
if (clause = params.tagName)
|
||
|
conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
|
||
|
if ((clause = params.classNames).length > 0)
|
||
|
for (var i = 0, length = clause.length; i < length; i++)
|
||
|
conditions.push('element.hasClassName(' + clause[i].inspect() + ')');
|
||
|
if (clause = params.attributes) {
|
||
|
clause.each(function(attribute) {
|
||
|
var value = 'element.readAttribute(' + attribute.name.inspect() + ')';
|
||
|
var splitValueBy = function(delimiter) {
|
||
|
return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
|
||
|
}
|
||
|
|
||
|
switch (attribute.operator) {
|
||
|
case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
|
||
|
case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
|
||
|
case '|=': conditions.push(
|
||
|
splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
|
||
|
); break;
|
||
|
case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
|
||
|
case '':
|
||
|
case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break;
|
||
|
default: throw 'Unknown operator ' + attribute.operator + ' in selector';
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return conditions.join(' && ');
|
||
|
},
|
||
|
|
||
|
compileMatcher: function() {
|
||
|
this.match = new Function('element', 'if (!element.tagName) return false; \
|
||
|
element = $(element); \
|
||
|
return ' + this.buildMatchExpression());
|
||
|
},
|
||
|
|
||
|
findElements: function(scope) {
|
||
|
var element;
|
||
|
|
||
|
if (element = $(this.params.id))
|
||
|
if (this.match(element))
|
||
|
if (!scope || Element.childOf(element, scope))
|
||
|
return [element];
|
||
|
|
||
|
scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
|
||
|
|
||
|
var results = [];
|
||
|
for (var i = 0, length = scope.length; i < length; i++)
|
||
|
if (this.match(element = scope[i]))
|
||
|
results.push(Element.extend(element));
|
||
|
|
||
|
return results;
|
||
|
},
|
||
|
|
||
|
toString: function() {
|
||
|
return this.expression;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Object.extend(Selector, {
|
||
|
matchElements: function(elements, expression) {
|
||
|
var selector = new Selector(expression);
|
||
|
return elements.select(selector.match.bind(selector)).map(Element.extend);
|
||
|
},
|
||
|
|
||
|
findElement: function(elements, expression, index) {
|
||
|
if (typeof expression == 'number') index = expression, expression = false;
|
||
|
return Selector.matchElements(elements, expression || '*')[index || 0];
|
||
|
},
|
||
|
|
||
|
findChildElements: function(element, expressions) {
|
||
|
return expressions.map(function(expression) {
|
||
|
return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {
|
||
|
var selector = new Selector(expr);
|
||
|
return results.inject([], function(elements, result) {
|
||
|
return elements.concat(selector.findElements(result || element));
|
||
|
});
|
||
|
});
|
||
|
}).flatten();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function $$() {
|
||
|
return Selector.findChildElements(document, $A(arguments));
|
||
|
}
|