2 cssQuery, version 2.0.2 (2005-08-19)
3 Copyright: 2004-2005, Dean Edwards (http://dean.edwards.name/)
4 License: http://creativecommons.org/licenses/LGPL/2.1/
7 // the following functions allow querying of the DOM using CSS selectors
8 var cssQuery = function() {
11 // -----------------------------------------------------------------------
12 // main query function
13 // -----------------------------------------------------------------------
15 var $COMMA
= /\s*,\s*/;
16 var cssQuery = function($selector
, $$from) {
19 var $useCache
= arguments
.callee
.caching
&& !$$from;
20 var $base
= ($$from) ? ($$from.constructor == Array
) ? $$from : [$$from] : [document
];
21 // process comma separated selectors
22 var $$selectors
= parseSelector($selector
).split($COMMA
), i
;
23 for (i
= 0; i
< $$selectors
.length
; i
++) {
24 // convert the selector to a stream
25 $selector
= _toStream($$selectors
[i
]);
26 // faster chop if it starts with id (MSIE only)
27 if (isMSIE
&& $selector
.slice(0, 3).join("") == " *#") {
28 $selector
= $selector
.slice(2);
29 $$from = _msie_selectById([], $base
, $selector
[1]);
30 } else $$from = $base
;
32 var j
= 0, $token
, $filter
, $arguments
, $cacheSelector
= "";
33 while (j
< $selector
.length
) {
34 $token
= $selector
[j
++];
35 $filter
= $selector
[j
++];
36 $cacheSelector
+= $token
+ $filter
;
37 // some pseudo-classes allow arguments to be passed
38 // e.g. nth-child(even)
40 if ($selector
[j
] == "(") {
41 while ($selector
[j
++] != ")" && j
< $selector
.length
) {
42 $arguments
+= $selector
[j
];
44 $arguments
= $arguments
.slice(0, -1);
45 $cacheSelector
+= "(" + $arguments
+ ")";
47 // process a token/filter pair use cached results if possible
48 $$from = ($useCache
&& cache
[$cacheSelector
]) ?
49 cache
[$cacheSelector
] : select($$from, $token
, $filter
, $arguments
);
50 if ($useCache
) cache
[$cacheSelector
] = $$from;
52 $match
= $match
.concat($$from);
54 delete cssQuery
.error
;
57 cssQuery
.error
= $error
;
61 // -----------------------------------------------------------------------
63 // -----------------------------------------------------------------------
65 cssQuery
.toString = function() {
66 return "function cssQuery() {\n [version " + version
+ "]\n}";
71 cssQuery
.caching
= false;
72 cssQuery
.clearCache = function($selector
) {
74 $selector
= _toStream($selector
).join("");
75 delete cache
[$selector
];
82 cssQuery
.addModule = function($name
, $script
) {
83 if (loaded
) eval("$script=" + String($script
));
84 modules
[$name
] = new $script();;
88 cssQuery
.valueOf = function($code
) {
89 return $code
? eval($code
) : this;
92 // -----------------------------------------------------------------------
94 // -----------------------------------------------------------------------
97 var pseudoClasses
= {};
98 // a safari bug means that these have to be declared here
99 var AttributeSelector
= {match
: /\[([\w-]+(\|[\w-]+)?)\s*(\W?=)?\s*([^\]]*)\]/};
100 var attributeSelectors
= [];
102 // -----------------------------------------------------------------------
104 // -----------------------------------------------------------------------
106 // descendant selector
107 selectors
[" "] = function($results
, $from, $tagName
, $namespace) {
108 // loop through current selection
110 for (i
= 0; i
< $from.length
; i
++) {
112 var $subset
= getElementsByTagName($from[i
], $tagName
, $namespace);
113 // loop through descendants and add to results selection
114 for (j
= 0; ($element
= $subset
[j
]); j
++) {
115 if (thisElement($element
) && compareNamespace($element
, $namespace))
116 $results
.push($element
);
122 selectors
["#"] = function($results
, $from, $id
) {
123 // loop through current selection and check ID
125 for (j
= 0; ($element
= $from[j
]); j
++) if ($element
.id
== $id
) $results
.push($element
);
129 selectors
["."] = function($results
, $from, $className
) {
130 // create a RegExp version of the class
131 $className
= new RegExp("(^|\\s)" + $className
+ "(\\s|$)");
132 // loop through current selection and check class
134 for (i
= 0; ($element
= $from[i
]); i
++)
135 if ($className
.test($element
.className
)) $results
.push($element
);
138 // pseudo-class selector
139 selectors
[":"] = function($results
, $from, $pseudoClass
, $arguments
) {
140 // retrieve the cssQuery pseudo-class function
141 var $test
= pseudoClasses
[$pseudoClass
], $element
, i
;
142 // loop through current selection and apply pseudo-class filter
143 if ($test
) for (i
= 0; ($element
= $from[i
]); i
++)
144 // if the cssQuery pseudo-class function returns "true" add the element
145 if ($test($element
, $arguments
)) $results
.push($element
);
148 // -----------------------------------------------------------------------
150 // -----------------------------------------------------------------------
152 pseudoClasses
["link"] = function($element
) {
153 var $document
= getDocument($element
);
154 if ($document
.links
) for (var i
= 0; i
< $document
.links
.length
; i
++) {
155 if ($document
.links
[i
] == $element
) return true;
159 pseudoClasses
["visited"] = function($element
) {
160 // can't do this without jiggery-pokery
163 // -----------------------------------------------------------------------
165 // -----------------------------------------------------------------------
167 // IE5/6 includes comments (LOL) in it's elements collections.
168 // so we have to check for this. the test is tagName != "!". LOL (again).
169 var thisElement = function($element
) {
170 return ($element
&& $element
.nodeType
== 1 && $element
.tagName
!= "!") ? $element
: null;
173 // return the previous element to the supplied element
174 // previousSibling is not good enough as it might return a text or comment node
175 var previousElementSibling = function($element
) {
176 while ($element
&& ($element
= $element
.previousSibling
) && !thisElement($element
)) continue;
180 // return the next element to the supplied element
181 var nextElementSibling = function($element
) {
182 while ($element
&& ($element
= $element
.nextSibling
) && !thisElement($element
)) continue;
186 // return the first child ELEMENT of an element
187 // NOT the first child node (though they may be the same thing)
188 var firstElementChild = function($element
) {
189 return thisElement($element
.firstChild
) || nextElementSibling($element
.firstChild
);
192 var lastElementChild = function($element
) {
193 return thisElement($element
.lastChild
) || previousElementSibling($element
.lastChild
);
196 // return child elements of an element (not child nodes)
197 var childElements = function($element
) {
198 var $childElements
= [];
199 $element
= firstElementChild($element
);
201 $childElements
.push($element
);
202 $element
= nextElementSibling($element
);
204 return $childElements
;
207 // -----------------------------------------------------------------------
208 // browser compatibility
209 // -----------------------------------------------------------------------
211 // all of the functions in this section can be overwritten. the default
212 // configuration is for IE. The functions below reflect this. standard
213 // methods are included in a separate module. It would probably be better
214 // the other way round of course but this makes it easier to keep IE7 trim.
218 var isXML = function($element
) {
219 var $document
= getDocument($element
);
220 return (typeof $document
.mimeType
== "unknown") ?
221 /\.xml$/i.test($document
.URL
) :
222 Boolean($document
.mimeType
== "XML Document");
225 // return the element's containing document
226 var getDocument = function($element
) {
227 return $element
.ownerDocument
|| $element
.document
;
230 var getElementsByTagName = function($element
, $tagName
) {
231 return ($tagName
== "*" && $element
.all
) ? $element
.all
: $element
.getElementsByTagName($tagName
);
234 var compareTagName = function($element
, $tagName
, $namespace) {
235 if ($tagName
== "*") return thisElement($element
);
236 if (!compareNamespace($element
, $namespace)) return false;
237 if (!isXML($element
)) $tagName
= $tagName
.toUpperCase();
238 return $element
.tagName
== $tagName
;
241 var compareNamespace = function($element
, $namespace) {
242 return !$namespace || ($namespace == "*") || ($element
.scopeName
== $namespace);
245 var getTextContent = function($element
) {
246 return $element
.innerText
;
249 function _msie_selectById($results
, $from, id
) {
251 for (i
= 0; i
< $from.length
; i
++) {
252 if ($match
= $from[i
].all
.item(id
)) {
253 if ($match
.id
== id
) $results
.push($match
);
254 else if ($match
.length
!= null) {
255 for (j
= 0; j
< $match
.length
; j
++) {
256 if ($match
[j
].id
== id
) $results
.push($match
[j
]);
265 if (![].push
) Array
.prototype.push = function() {
266 for (var i
= 0; i
< arguments
.length
; i
++) {
267 this[this.length
] = arguments
[i
];
272 // -----------------------------------------------------------------------
274 // -----------------------------------------------------------------------
276 // select a set of matching elements.
277 // "from" is an array of elements.
278 // "token" is a character representing the type of filter
279 // e.g. ">" means child selector
280 // "filter" represents the tag name, id or class name that is being selected
281 // the function returns an array of matching elements
282 var $NAMESPACE
= /\|/;
283 function select($$from, $token
, $filter
, $arguments
) {
284 if ($NAMESPACE
.test($filter
)) {
285 $filter
= $filter
.split($NAMESPACE
);
286 $arguments
= $filter
[0];
287 $filter
= $filter
[1];
290 if (selectors
[$token
]) {
291 selectors
[$token
]($results
, $$from, $filter
, $arguments
);
296 // -----------------------------------------------------------------------
298 // -----------------------------------------------------------------------
300 // convert css selectors to a stream of tokens and filters
301 // it's not a real stream. it's just an array of strings.
302 var $STANDARD_SELECT
= /^[^\s>+~]/;
303 var $$STREAM
= /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;
304 function _toStream($selector
) {
305 if ($STANDARD_SELECT
.test($selector
)) $selector
= " " + $selector
;
306 return $selector
.match($$STREAM
) || [];
309 var $WHITESPACE
= /\s*([\s>+~(),]|^|$)\s*/g;
310 var $IMPLIED_ALL
= /([\s>+~,]|[^(]\+|^)([#.:@])/g;
311 var parseSelector = function($selector
) {
314 .replace($WHITESPACE
, "$1")
315 // e.g. ".class1" --> "*.class1"
316 .replace($IMPLIED_ALL
, "$1*$2");
320 toString: function() {return "'"},
321 match
: /^('[^']*')|("[^"]*")$/,
322 test: function($string
) {
323 return this.match
.test($string
);
325 add: function($string
) {
326 return this.test($string
) ? $string
: this + $string
+ this;
328 remove: function($string
) {
329 return this.test($string
) ? $string
.slice(1, -1) : $string
;
333 var getText = function($text
) {
334 return Quote
.remove($text
);
337 var $ESCAPE
= /([\/()[\]?{}|*+-])/g;
338 function regEscape($string
) {
339 return $string
.replace($ESCAPE
, "\\$1");
342 // -----------------------------------------------------------------------
344 // -----------------------------------------------------------------------
346 // -------- >> insert modules here for packaging << -------- \\
350 // -----------------------------------------------------------------------
351 // return the query function
352 // -----------------------------------------------------------------------