7a116201f7
The script that automatically adds the copyright notice was wrongly configured.
394 lines
15 KiB
JavaScript
394 lines
15 KiB
JavaScript
/***
|
|
|
|
MochiKit.Selector 1.5
|
|
|
|
See <http://mochikit.com/> for documentation, downloads, license, etc.
|
|
|
|
(c) 2005 Bob Ippolito and others. All rights Reserved.
|
|
|
|
***/
|
|
|
|
MochiKit.Base.module(MochiKit, 'Selector', '1.5', ['Base', 'DOM', 'Iter']);
|
|
|
|
MochiKit.Selector.Selector = function (expression) {
|
|
this.params = {classNames: [], pseudoClassNames: []};
|
|
this.expression = expression.toString().replace(/(^\s+|\s+$)/g, '');
|
|
this.parseExpression();
|
|
this.compileMatcher();
|
|
};
|
|
|
|
MochiKit.Selector.Selector.prototype = {
|
|
/***
|
|
|
|
Selector class: convenient object to make CSS selections.
|
|
|
|
***/
|
|
__class__: MochiKit.Selector.Selector,
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.parseExpression */
|
|
parseExpression: function () {
|
|
function abort(message) {
|
|
throw 'Parse error in selector: ' + message;
|
|
}
|
|
|
|
if (this.expression == '') {
|
|
abort('empty expression');
|
|
}
|
|
|
|
var repr = MochiKit.Base.repr;
|
|
var params = this.params;
|
|
var expr = this.expression;
|
|
var 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 ':':
|
|
params.pseudoClassNames.push(clause);
|
|
break;
|
|
case '':
|
|
case undefined:
|
|
params.tagName = clause.toUpperCase();
|
|
break;
|
|
default:
|
|
abort(repr(expr));
|
|
}
|
|
expr = rest;
|
|
}
|
|
|
|
if (expr.length > 0) {
|
|
abort(repr(expr));
|
|
}
|
|
},
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.buildMatchExpression */
|
|
buildMatchExpression: function () {
|
|
var repr = MochiKit.Base.repr;
|
|
var params = this.params;
|
|
var conditions = [];
|
|
var clause, i;
|
|
|
|
function childElements(element) {
|
|
return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, " + element + ".childNodes)";
|
|
}
|
|
|
|
if (params.wildcard) {
|
|
conditions.push('true');
|
|
}
|
|
if (clause = params.id) {
|
|
conditions.push('element.id == ' + repr(clause));
|
|
}
|
|
if (clause = params.tagName) {
|
|
conditions.push('element.tagName.toUpperCase() == ' + repr(clause));
|
|
}
|
|
if ((clause = params.classNames).length > 0) {
|
|
for (i = 0; i < clause.length; i++) {
|
|
conditions.push('MochiKit.DOM.hasElementClass(element, ' + repr(clause[i]) + ')');
|
|
}
|
|
}
|
|
if ((clause = params.pseudoClassNames).length > 0) {
|
|
for (i = 0; i < clause.length; i++) {
|
|
var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/);
|
|
var pseudoClass = match[1];
|
|
var pseudoClassArgument = match[2];
|
|
switch (pseudoClass) {
|
|
case 'root':
|
|
conditions.push('element.nodeType == 9 || element === element.ownerDocument.documentElement'); break;
|
|
case 'nth-child':
|
|
case 'nth-last-child':
|
|
case 'nth-of-type':
|
|
case 'nth-last-of-type':
|
|
match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/);
|
|
if (!match) {
|
|
throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument;
|
|
}
|
|
var a, b;
|
|
if (match[0] == 'odd') {
|
|
a = 2;
|
|
b = 1;
|
|
} else if (match[0] == 'even') {
|
|
a = 2;
|
|
b = 0;
|
|
} else {
|
|
a = match[2] && parseInt(match, 10) || null;
|
|
b = parseInt(match[3], 10);
|
|
}
|
|
conditions.push('this.nthChild(element,' + a + ',' + b
|
|
+ ',' + !!pseudoClass.match('^nth-last') // Reverse
|
|
+ ',' + !!pseudoClass.match('of-type$') // Restrict to same tagName
|
|
+ ')');
|
|
break;
|
|
case 'first-child':
|
|
conditions.push('this.nthChild(element, null, 1)');
|
|
break;
|
|
case 'last-child':
|
|
conditions.push('this.nthChild(element, null, 1, true)');
|
|
break;
|
|
case 'first-of-type':
|
|
conditions.push('this.nthChild(element, null, 1, false, true)');
|
|
break;
|
|
case 'last-of-type':
|
|
conditions.push('this.nthChild(element, null, 1, true, true)');
|
|
break;
|
|
case 'only-child':
|
|
conditions.push(childElements('element.parentNode') + '.length == 1');
|
|
break;
|
|
case 'only-of-type':
|
|
conditions.push('MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, ' + childElements('element.parentNode') + ').length == 1');
|
|
break;
|
|
case 'empty':
|
|
conditions.push('element.childNodes.length == 0');
|
|
break;
|
|
case 'enabled':
|
|
conditions.push('(this.isUIElement(element) && element.disabled === false)');
|
|
break;
|
|
case 'disabled':
|
|
conditions.push('(this.isUIElement(element) && element.disabled === true)');
|
|
break;
|
|
case 'checked':
|
|
conditions.push('(this.isUIElement(element) && element.checked === true)');
|
|
break;
|
|
case 'not':
|
|
var subselector = new MochiKit.Selector.Selector(pseudoClassArgument);
|
|
conditions.push('!( ' + subselector.buildMatchExpression() + ')');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (clause = params.attributes) {
|
|
MochiKit.Base.map(function (attribute) {
|
|
var value = 'MochiKit.DOM.getNodeAttribute(element, ' + repr(attribute.name) + ')';
|
|
var splitValueBy = function (delimiter) {
|
|
return value + '.split(' + repr(delimiter) + ')';
|
|
};
|
|
conditions.push(value + ' != null');
|
|
switch (attribute.operator) {
|
|
case '=':
|
|
conditions.push(value + ' == ' + repr(attribute.value));
|
|
break;
|
|
case '~=':
|
|
conditions.push('MochiKit.Base.findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1');
|
|
break;
|
|
case '^=':
|
|
conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value));
|
|
break;
|
|
case '$=':
|
|
conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value));
|
|
break;
|
|
case '*=':
|
|
conditions.push(value + '.match(' + repr(attribute.value) + ')');
|
|
break;
|
|
case '|=':
|
|
conditions.push(splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase()));
|
|
break;
|
|
case '!=':
|
|
conditions.push(value + ' != ' + repr(attribute.value));
|
|
break;
|
|
case '':
|
|
case undefined:
|
|
// Condition already added above
|
|
break;
|
|
default:
|
|
throw 'Unknown operator ' + attribute.operator + ' in selector';
|
|
}
|
|
}, clause);
|
|
}
|
|
|
|
return conditions.join(' && ');
|
|
},
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.compileMatcher */
|
|
compileMatcher: function () {
|
|
var code = 'return (!element.tagName) ? false : ' +
|
|
this.buildMatchExpression() + ';';
|
|
this.match = new Function('element', code);
|
|
},
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.nthChild */
|
|
nthChild: function (element, a, b, reverse, sametag){
|
|
var siblings = MochiKit.Base.filter(function (node) {
|
|
return node.nodeType == 1;
|
|
}, element.parentNode.childNodes);
|
|
if (sametag) {
|
|
siblings = MochiKit.Base.filter(function (node) {
|
|
return node.tagName == element.tagName;
|
|
}, siblings);
|
|
}
|
|
if (reverse) {
|
|
siblings = MochiKit.Iter.reversed(siblings);
|
|
}
|
|
if (a) {
|
|
var actualIndex = MochiKit.Base.findIdentical(siblings, element);
|
|
return ((actualIndex + 1 - b) / a) % 1 == 0;
|
|
} else {
|
|
return b == MochiKit.Base.findIdentical(siblings, element) + 1;
|
|
}
|
|
},
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.isUIElement */
|
|
isUIElement: function (element) {
|
|
return MochiKit.Base.findValue(['input', 'button', 'select', 'option', 'textarea', 'object'],
|
|
element.tagName.toLowerCase()) > -1;
|
|
},
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.findElements */
|
|
findElements: function (scope, axis) {
|
|
var element;
|
|
|
|
if (axis == undefined) {
|
|
axis = "";
|
|
}
|
|
|
|
function inScope(element, scope) {
|
|
if (axis == "") {
|
|
return MochiKit.DOM.isChildNode(element, scope);
|
|
} else if (axis == ">") {
|
|
return element.parentNode === scope;
|
|
} else if (axis == "+") {
|
|
return element === nextSiblingElement(scope);
|
|
} else if (axis == "~") {
|
|
var sibling = scope;
|
|
while (sibling = nextSiblingElement(sibling)) {
|
|
if (element === sibling) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
throw "Invalid axis: " + axis;
|
|
}
|
|
}
|
|
|
|
if (element = MochiKit.DOM.getElement(this.params.id)) {
|
|
if (this.match(element)) {
|
|
if (!scope || inScope(element, scope)) {
|
|
return [element];
|
|
}
|
|
}
|
|
}
|
|
|
|
function nextSiblingElement(node) {
|
|
node = node.nextSibling;
|
|
while (node && node.nodeType != 1) {
|
|
node = node.nextSibling;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
if (axis == "") {
|
|
scope = (scope || MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName || '*');
|
|
} else if (axis == ">") {
|
|
if (!scope) {
|
|
throw "> combinator not allowed without preceeding expression";
|
|
}
|
|
scope = MochiKit.Base.filter(function (node) {
|
|
return node.nodeType == 1;
|
|
}, scope.childNodes);
|
|
} else if (axis == "+") {
|
|
if (!scope) {
|
|
throw "+ combinator not allowed without preceeding expression";
|
|
}
|
|
scope = nextSiblingElement(scope) && [nextSiblingElement(scope)];
|
|
} else if (axis == "~") {
|
|
if (!scope) {
|
|
throw "~ combinator not allowed without preceeding expression";
|
|
}
|
|
var newscope = [];
|
|
while (nextSiblingElement(scope)) {
|
|
scope = nextSiblingElement(scope);
|
|
newscope.push(scope);
|
|
}
|
|
scope = newscope;
|
|
}
|
|
|
|
if (!scope) {
|
|
return [];
|
|
}
|
|
|
|
var results = MochiKit.Base.filter(MochiKit.Base.bind(function (scopeElt) {
|
|
return this.match(scopeElt);
|
|
}, this), scope);
|
|
|
|
return results;
|
|
},
|
|
|
|
/** @id MochiKit.Selector.Selector.prototype.repr */
|
|
repr: function () {
|
|
return 'Selector(' + this.expression + ')';
|
|
},
|
|
|
|
toString: MochiKit.Base.forwardCall("repr")
|
|
};
|
|
|
|
MochiKit.Base.update(MochiKit.Selector, {
|
|
|
|
/** @id MochiKit.Selector.findChildElements */
|
|
findChildElements: function (element, expressions) {
|
|
element = MochiKit.DOM.getElement(element);
|
|
var uniq = function(arr) {
|
|
var res = [];
|
|
for (var i = 0; i < arr.length; i++) {
|
|
if (MochiKit.Base.findIdentical(res, arr[i]) < 0) {
|
|
res.push(arr[i]);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
return MochiKit.Base.flattenArray(MochiKit.Base.map(function (expression) {
|
|
try {
|
|
var res = element.querySelectorAll(expression);
|
|
return Array.prototype.slice.call(res, 0);
|
|
} catch (ignore) {
|
|
// No querySelectorAll or extended expression syntax used
|
|
}
|
|
var nextScope = "";
|
|
var reducer = function (results, expr) {
|
|
var match = expr.match(/^[>+~]$/);
|
|
if (match) {
|
|
nextScope = match[0];
|
|
return results;
|
|
} else {
|
|
var selector = new MochiKit.Selector.Selector(expr);
|
|
var elements = MochiKit.Iter.reduce(function (elements, result) {
|
|
return MochiKit.Base.extend(elements, selector.findElements(result || element, nextScope));
|
|
}, results, []);
|
|
nextScope = "";
|
|
return elements;
|
|
}
|
|
};
|
|
var exprs = expression.replace(/(^\s+|\s+$)/g, '').split(/\s+/);
|
|
return uniq(MochiKit.Iter.reduce(reducer, exprs, [null]));
|
|
}, expressions));
|
|
},
|
|
|
|
findDocElements: function () {
|
|
return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(), arguments);
|
|
},
|
|
|
|
__new__: function () {
|
|
this.$$ = this.findDocElements;
|
|
MochiKit.Base.nameFunctions(this);
|
|
}
|
|
});
|
|
|
|
MochiKit.Selector.__new__();
|
|
|
|
MochiKit.Base._exportSymbols(this, MochiKit.Selector);
|