|
Extended $ function called $$: msg#00128lang.ruby.rails.spinoffs
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. |
|
| <Prev in Thread] | Current Thread | [Next in Thread> |
|---|---|---|
| Previous by Date: | Scaling question: 00128, Michael McKinley |
|---|---|
| Next by Date: | Re: Extended $ function called $$: 00128, Michael Schuerig |
| Previous by Thread: | Scaling questioni: 00128, Michael McKinley |
| Next by Thread: | Re: Extended $ function called $$: 00128, Michael Schuerig |
| Indexes: | [Date] [Thread] [Top] [All Lists] |
| News | FAQ | advertise |