266 lines
8.3 KiB
JavaScript
266 lines
8.3 KiB
JavaScript
|
//>>built
|
||
|
define("dojo/selector/lite", ["../has", "../_base/kernel"], function(has, dojo){
|
||
|
"use strict";
|
||
|
// summary:
|
||
|
// A small lightweight query selector engine that implements CSS2.1 selectors
|
||
|
// minus pseudo-classes and the sibling combinator, plus CSS3 attribute selectors
|
||
|
var testDiv = document.createElement("div");
|
||
|
var matchesSelector = testDiv.matchesSelector || testDiv.webkitMatchesSelector || testDiv.mozMatchesSelector || testDiv.msMatchesSelector || testDiv.oMatchesSelector; // IE9, WebKit, Firefox have this, but not Opera yet
|
||
|
var querySelectorAll = testDiv.querySelectorAll;
|
||
|
has.add("dom-matches-selector", !!matchesSelector);
|
||
|
has.add("dom-qsa", !!querySelectorAll);
|
||
|
|
||
|
// this is a simple query engine. It has handles basic selectors, and for simple
|
||
|
// common selectors is extremely fast
|
||
|
var liteEngine = function(selector, root){
|
||
|
if(combine && selector.indexOf(',') > -1){
|
||
|
return combine(selector, root);
|
||
|
}
|
||
|
var match = (querySelectorAll ?
|
||
|
/^([\w]*)#([\w\-]+$)|^(\.)([\w\-\*]+$)|^(\w+$)/ : // this one only matches on simple queries where we can beat qSA with specific methods
|
||
|
/^([\w]*)#([\w\-]+)(?:\s+(.*))?$|(?:^|(>|.+\s+))([\w\-\*]+)(\S*$)/) // this one matches parts of the query that we can use to speed up manual filtering
|
||
|
.exec(selector);
|
||
|
root = root || document;
|
||
|
if(match){
|
||
|
// fast path regardless of whether or not querySelectorAll exists
|
||
|
if(match[2]){
|
||
|
// an #id
|
||
|
// use dojo.byId if available as it fixes the id retrieval in IE
|
||
|
var found = dojo.byId ? dojo.byId(match[2]) : document.getElementById(match[2]);
|
||
|
if(!found || (match[1] && match[1] != found.tagName.toLowerCase())){
|
||
|
// if there is a tag qualifer and it doesn't match, no matches
|
||
|
return [];
|
||
|
}
|
||
|
if(root != document){
|
||
|
// there is a root element, make sure we are a child of it
|
||
|
var parent = found;
|
||
|
while(parent != root){
|
||
|
parent = parent.parentNode;
|
||
|
if(!parent){
|
||
|
return [];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return match[3] ?
|
||
|
liteEngine(match[3], found)
|
||
|
: [found];
|
||
|
}
|
||
|
if(match[3] && root.getElementsByClassName){
|
||
|
// a .class
|
||
|
return root.getElementsByClassName(match[4]);
|
||
|
}
|
||
|
var found;
|
||
|
if(match[5]){
|
||
|
// a tag
|
||
|
found = root.getElementsByTagName(match[5]);
|
||
|
if(match[4] || match[6]){
|
||
|
selector = (match[4] || "") + match[6];
|
||
|
}else{
|
||
|
// that was the entirety of the query, return results
|
||
|
return found;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if(querySelectorAll){
|
||
|
// qSA works strangely on Element-rooted queries
|
||
|
// We can work around this by specifying an extra ID on the root
|
||
|
// and working up from there (Thanks to Andrew Dupont for the technique)
|
||
|
// IE 8 doesn't work on object elements
|
||
|
if (root.nodeType === 1 && root.nodeName.toLowerCase() !== "object"){
|
||
|
return useRoot(root, selector, root.querySelectorAll);
|
||
|
}else{
|
||
|
// we can use the native qSA
|
||
|
return root.querySelectorAll(selector);
|
||
|
}
|
||
|
}else if(!found){
|
||
|
// search all children and then filter
|
||
|
found = root.getElementsByTagName("*");
|
||
|
}
|
||
|
// now we filter the nodes that were found using the matchesSelector
|
||
|
var results = [];
|
||
|
for(var i = 0, l = found.length; i < l; i++){
|
||
|
var node = found[i];
|
||
|
if(node.nodeType == 1 && jsMatchesSelector(node, selector, root)){
|
||
|
// keep the nodes that match the selector
|
||
|
results.push(node);
|
||
|
}
|
||
|
}
|
||
|
return results;
|
||
|
};
|
||
|
var useRoot = function(context, query, method){
|
||
|
// this function creates a temporary id so we can do rooted qSA queries, this is taken from sizzle
|
||
|
var oldContext = context,
|
||
|
old = context.getAttribute( "id" ),
|
||
|
nid = old || "__dojo__",
|
||
|
hasParent = context.parentNode,
|
||
|
relativeHierarchySelector = /^\s*[+~]/.test( query );
|
||
|
|
||
|
if(relativeHierarchySelector && !hasParent){
|
||
|
return [];
|
||
|
}
|
||
|
if(!old){
|
||
|
context.setAttribute("id", nid);
|
||
|
}else{
|
||
|
nid = nid.replace(/'/g, "\\$&");
|
||
|
}
|
||
|
if(relativeHierarchySelector && hasParent){
|
||
|
context = context.parentNode;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return method.call(context, "[id='" + nid + "'] " + query );
|
||
|
} finally {
|
||
|
if ( !old ) {
|
||
|
oldContext.removeAttribute( "id" );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if(!has("dom-matches-selector")){
|
||
|
var jsMatchesSelector = (function(){
|
||
|
// a JS implementation of CSS selector matching, first we start with the various handlers
|
||
|
var caseFix = testDiv.tagName == "div" ? "toLowerCase" : "toUpperCase";
|
||
|
function tag(tagName){
|
||
|
tagName = tagName[caseFix]();
|
||
|
return function(node){
|
||
|
return node.tagName == tagName;
|
||
|
}
|
||
|
}
|
||
|
function className(className){
|
||
|
var classNameSpaced = ' ' + className + ' ';
|
||
|
return function(node){
|
||
|
return node.className.indexOf(className) > -1 && (' ' + node.className + ' ').indexOf(classNameSpaced) > -1;
|
||
|
}
|
||
|
}
|
||
|
var attrComparators = {
|
||
|
"^=": function(attrValue, value){
|
||
|
return attrValue.indexOf(value) == 0;
|
||
|
},
|
||
|
"*=": function(attrValue, value){
|
||
|
return attrValue.indexOf(value) > -1;
|
||
|
},
|
||
|
"$=": function(attrValue, value){
|
||
|
return attrValue.substring(attrValue.length - value.length, attrValue.length) == value;
|
||
|
},
|
||
|
"~=": function(attrValue, value){
|
||
|
return (' ' + attrValue + ' ').indexOf(' ' + value + ' ') > -1;
|
||
|
},
|
||
|
"|=": function(attrValue, value){
|
||
|
return (attrValue + '-').indexOf(value + '-') == 0;
|
||
|
},
|
||
|
"=": function(attrValue, value){
|
||
|
return attrValue == value;
|
||
|
},
|
||
|
"": function(attrValue, value){
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
function attr(name, value, type){
|
||
|
if(value.match(/['"]/)){
|
||
|
// it is quoted, do an eval to parse the string (CSS and JS parsing are close enough)
|
||
|
value = eval(value);
|
||
|
}
|
||
|
var comparator = attrComparators[type || ""];
|
||
|
return function(node){
|
||
|
var attrValue = node.getAttribute(name);
|
||
|
return attrValue && comparator(attrValue, value);
|
||
|
}
|
||
|
}
|
||
|
function ancestor(matcher){
|
||
|
return function(node, root){
|
||
|
while((node = node.parentNode) != root){
|
||
|
if(matcher(node, root)){
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function parent(matcher){
|
||
|
return function(node, root){
|
||
|
node = node.parentNode;
|
||
|
return matcher ?
|
||
|
node != root && matcher(node, root)
|
||
|
: node == root;
|
||
|
};
|
||
|
}
|
||
|
var cache = {};
|
||
|
function and(matcher, next){
|
||
|
return matcher ?
|
||
|
function(node, root){
|
||
|
return next(node) && matcher(node, root);
|
||
|
}
|
||
|
: next;
|
||
|
}
|
||
|
return function(node, selector, root){
|
||
|
// this returns true or false based on if the node matches the selector (optionally within the given root)
|
||
|
var matcher = cache[selector]; // check to see if we have created a matcher function for the given selector
|
||
|
if(!matcher){
|
||
|
// create a matcher function for the given selector
|
||
|
// parse the selectors
|
||
|
if(selector.replace(/(?:\s*([> ])\s*)|(\.)?([\w-]+)|\[([\w-]+)\s*(.?=)?\s*([^\]]*)\]/g, function(t, combinator, type, value, attrName, attrType, attrValue){
|
||
|
if(value){
|
||
|
if(type == "."){
|
||
|
matcher = and(matcher, className(value));
|
||
|
}
|
||
|
else{
|
||
|
matcher = and(matcher, tag(value));
|
||
|
}
|
||
|
}
|
||
|
else if(combinator){
|
||
|
matcher = (combinator == " " ? ancestor : parent)(matcher);
|
||
|
}
|
||
|
else if(attrName){
|
||
|
matcher = and(matcher, attr(attrName, attrValue, attrType));
|
||
|
}
|
||
|
return "";
|
||
|
})){
|
||
|
throw new Error("Syntax error in query");
|
||
|
}
|
||
|
if(!matcher){
|
||
|
return true;
|
||
|
}
|
||
|
cache[selector] = matcher;
|
||
|
}
|
||
|
// now run the matcher function on the node
|
||
|
return matcher(node, root);
|
||
|
};
|
||
|
})();
|
||
|
}
|
||
|
if(!has("dom-qsa")){
|
||
|
var combine = function(selector, root){
|
||
|
// combined queries
|
||
|
selector = selector.split(/\s*,\s*/);
|
||
|
var indexed = [];
|
||
|
// add all results and keep unique ones, this only runs in IE, so we take advantage
|
||
|
// of known IE features, particularly sourceIndex which is unique and allows us to
|
||
|
// order the results
|
||
|
for(var i = 0; i < selector.length; i++){
|
||
|
var results = liteEngine(selector[i], root);
|
||
|
for(var j = 0, l = results.length; j < l; j++){
|
||
|
var node = results[j];
|
||
|
indexed[node.sourceIndex] = node;
|
||
|
}
|
||
|
}
|
||
|
// now convert from a sparse array to a dense array
|
||
|
var totalResults = [];
|
||
|
for(i in indexed){
|
||
|
totalResults.push(indexed[i]);
|
||
|
}
|
||
|
return totalResults;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
liteEngine.match = matchesSelector ? function(node, selector, root){
|
||
|
if(root){
|
||
|
// doesn't support three args, use rooted id trick
|
||
|
return useRoot(root, selector, function(query){
|
||
|
return matchesSelector.call(node, query);
|
||
|
});
|
||
|
}
|
||
|
// we have a native matchesSelector, use that
|
||
|
return matchesSelector.call(node, selector);
|
||
|
} : jsMatchesSelector; // otherwise use the JS matches impl
|
||
|
|
||
|
return liteEngine;
|
||
|
});
|