This code lets you use standard CSS selectors to get an array of elements.
For example, $$("#container div.myElements") would return all
subelements of
#container that are divs and are of the class myElements.
I submitted similar code a while back to the email address for the prototype
library but never got a reply. Thought I''d post this in the hopes that
some
others will find it useful.
I think this would make a good addition to Scriptaculous and could replace
some of the funky ways of referencing multiple elements at once with a more
standard way. For example, passing in two arguments, one of a an id and the
other of an element type. Instead, you could just pass a string or the
results of $$() much like you do with single elements and $().
There are three additions I had to make to the prototype library. These
could also find their way into util.js and are quite useful outside of the
$$() function. I ended up using part of the Element.Class functions from
util.js to avoid duplication. But if somebody wanted this in prototype.js, I
could rewrite to make it not depend on util.js.
Note that since some languages <COUGH>ColdFusion</COUGH> use the #
symbol
and it has to be escaped as ##, I also allow for % to be used in place of #.
You can also easily add your own replacements as it is defined in a separate
array named $$.hashes (it''s about half way down the code).
I also added a custom Element.setStyle() function that takes an array of
elements and styles them. This is not part of the code I''m submitting
(though you can use it). It just shows you an example of how useful the $$
function can be. I use $$() a lot in my code. It is as useful as $().
I''d
like to see it in scriptaculous so we can work on multiple elements just as
easily as single ones.
Note that you can send multiple arguments and also separate arguments with
commas and it would be valid:
$$("div.first,div.second");
same as
$$("div.first","div.second");
Can also mix
$$("#container
div.whatever","#firstname,#lastname#,.bolded");
Note that I actually broke all my own code. I used a format that was a
little tighter but a lot less standard CSS. This format as shown adheres
pretty closely to the standard used in most browsers.
Here is the code:
// equivalent to getElementsByTagName("*") but should work for all
browsers.
Element.getDescendants = function(el){
// browser searching ugliness necessary. msie supports
getElementsByTagName
// but fails when passing in "*" for some versions. Safari fails
too.
// Because of this, we need to walk DOM for failures.
// for now, I''ve set it to false but the bracketed part could be a
test
// for browser compatibility.
var supportsWildcards=(
false
)
var elements=[];
// this code doesn''t work with all browsers though my guess is that
it is
faster.
// Since I don''t know which browsers work, I''m going to let
the community
tell me
// and simply assume it doesn''t work anywhere for now.
if (supportsWildcards){
return el.getElementsByTagName("*");
}else{
var children=el.childNodes;
elements.append(children);
for(var i=0; i<children.length; i++){
var child = children[i];
elements.append(Element.getDescendants(child));
}
return elements;
}
}
// adds an arrays to an array but keeps the array flat
Array.prototype.append = function(array){
for(var i=0; i<array.length; i++){
var item=array[i];
if(item){this[this.length]=item;}
}
}
// Checks to see if the current string is in the given array
String.prototype.inArray = function(array){
for(var i=0; i<array.length; i++){
if(this == array[i]){return true;}
}
return false;
}
$$=function(){
if(typeof arguments[0] != ''string''){
return arguments[0];
}
// get a nice flat array of selectors
var selectorArray = [];
for(var i=0; i<arguments.length; i++){
selectorArray.append(arguments[i].split('',''));
}
// iterate through selectorArray and return elements
var elements=[];
for(var i=0; i<selectorArray.length; i++){
var selector = selectorArray[i];
var partArray = selector.split('' '');
var part = partArray[0];
// parse the first part of the selector.
// note: if there is only one part, go no further.
// if there is a second part, call $$ again on each subpart.
var firstChar = part.substring(0,1);
// "#id" find single element
if(firstChar.inArray($$.hashes)){
var id=part.substring(1,part.length);
var element = $(id);
if(element){
// return a single element
if(partArray.length <= 1){
elements.append(element);
// return the children of the single element with more selectors
}else{
var selectorRest = selector.substring(selector.indexOf(''
'')+1,selector.length);
elements.append($$.getSubelementsBySelector(element,selectorRest));
}
}else{
throw new Error("Element with id "+id+" not found");
}
// find multiple elements
}else{
elements.append($$.getSubelementsBySelector(document,selector));
}
}
return elements;
}
// list of valid symbols used to identify element IDs (e.g. ##myElement or
%myElement)
$$.hashes=[''#'',''%''];
// A private function used by $$().
// Separated because it needs to make recursive calls.
$$.getSubelementsBySelector = function(element,selector){
var elements = [];
var partArray = selector.split('' '');
var part = partArray[0];
var dotPos = part.indexOf(''.''); // the dot position (-1 if
not found)
if (dotPos == -1){
var nodeList = document.getElementsByTagName(part);
elements.append(nodeList);
// ".class" find any type of element of a given class.
} else if (dotPos == 0) {
var descendantArray = Element.getDescendants(element);
var className = part.substring(1,part.length);
for(var i=0; i<descendantArray.length; i++){
var descendant = descendantArray[i];
if(Element.Class.has_any(descendant,className)){
elements.push(descendant);
}
}
// "type.class" find any type of element of a given tag and class
}else{
var tagName = part.substring(0,dotPos);
var className = part.substring(dotPos+1,part.length);
var taggedElements = element.getElementsByTagName(tagName);
for(var i=0; i<taggedElements.length; i++){
var tempElement = taggedElements[i];
if(Element.Class.has_any(tempElement,className)){
elements.push(tempElement);
}
}
}
// if the selector only has one part, then return the elements now
if (partArray.length <= 1) {
return elements;
}
// if the selector has more than one part, we need to go down another
level
// to find more stuff.
var subelements = [];
var spacePos = selector.indexOf('' '');
var subselector = selector.substring(spacePos+1,selector.length);
for(var i=0; i<elements.length; i++){
subelements.append($$.getSubelementsBySelector(elements[i],subselector))
}
return subelements;
}
Element.setStyle = function(elements,style,value){
for(var i=0;i<elements.length;i++){
elements[i].style[style]=value;
}
}
Element.setStyle($$(''div.container
.findme'',''#myElement''),''color'',''green'');
Please let me know if people will be using this. Full permission to put into
scriptaculous or prototype and I''m willing to maintain it.