Yankee Web Code- Run.doRight()

by Kenneth Tibbetts

Content and Control Run.doRight() was named after a dog, actually, who was named for the Mountie who always gets his man. Use Run.doRight() to retrieve any element or collection of elements that fit a specific description. This function introduces a feature of the DOM that can be a little tricky, if you aren't prepared for it- the notion of "live arrays", or as the W3 calls them, nodeLists.

What's a Run.doRight?

You can use this function to find all of the hyperlinks or every heading in your document. You can retrieve a collection of elements that share nothing but a specified style property or className. Every element that contains the text 'elephant' can replace its 'elephant' with an image of a pachyderm. Or the text on a page or in selected elements can be hidden, shown, resized or translated.

Run.doRight() retrieves a collection of elements that can be dealt with as a group.

Arguments: 1 required. Two optional arguments

Returns: An array or a nodeSort of html elements

Arguments:
Run.doRight(tagname, reference element, specifics)

index required description type
[0] yes any html tagName or '*', plus (optional) modifier string
[1] no reference element, parent or container object or string
[2] no specific feature or attribute to look for string


Calling Run.doRight()

Some examples will demonstrate Run.doRight() in action:

var x= Run.doRight('*');

x is a nodeSort of every html element in the body of the document (does not include <body>)

Run.doRight('*','*');

returns all the elements in the document, including the <!doctype> , <html>, <head> and <body> elements.

Run.doRight('a');

returns all <a> elements(links and anchors) in the document.

Run.doRight('a','div2');

returns all <a> elements that are decendents of the element mr('div2').

Run.doRight('a','div2','href');

returns all <a> elements in mr('div2') that have a href attribute set (to anything).

Run.doRight('*','','ids=yankee');

returns all elements with the string 'yankee' included in their id string.

Run.doRight('*','','txt=yankee');

returns all elements that contain (in a textNode) the text 'yankee'.

Run.doRight('*','','color:#ff0000');

returns all elements that have an inline style color set to red .

Run.doRight('*','','className=hiderClass')

returns all elements of the class 'hiderClass'.

It's alive!- Managing nodeLists

Before we step through the code we ought to take a look at this nodeSort thing.When you look at an html or xhtml document, pretty much everything you see is a node. All of the nodes are the parents of the elements they contain, and the children of their containing elements. The big guy, the parent of all nodes, is the document itself. The document.documentElement is the html element, defined by the <html> tags, and containing everything between them. Some people call this element the root element of the document.

There are a dozen defined varieties of nodes, each of which has an associated nodeType, which can be expressed as a constant string or an integer between 1 and 12. (IE hiccups on the constants, so practically it's best to remember the integers). I have the complete list in the 'Odds & Ends' section at the end of the article, but many are not useful in html.

The nodeTypes that are most useful in html:

nodeType value nodeType constant
1 ELEMENT_NODE
2 ATTRIBUTE_NODE
3 TEXT_NODE
9 DOCUMENT_NODE

The nodes that are named by html tags, <p> and <h1> and <img> and so on, have a nodeType of 1. This is the container- not the content! A <p> element will have childNodes of it's own, some may be other elements (links or anchors or images, for example) and some will be text nodes (nodeType 3). The text nodes are the end of the line, a text node has no child node.

A nodeSort is a collection of nodes.

Calling x= document.getElementsByTagName('p') returns the collection of all the paragraphs in the document. The value of x is like an array, it has a length, and you can use index notation to work with a member of the collection: x[0].style.color='#000000'. x[0].childNodes is the nodeSort of all the childNodes of the first paragraph.

But a nodeSort is not an array- at least it is not a static array. You might call it a "live" array. It reflects the nodeSort constantly. If you dynamically add a paragraph to the document, the new paragraph is inserted into the nodeSort, and the length increments. If you remove two paragraphs, the corresponding items are deleted from the nodeSort, and the length is reduced by two. Note that this is something like an array.splice(), not a push or pop. A nodeSort is always arranged according to the source order of the document.

This behavior can be useful, but you have to know it is happening. It can be the cause of a variety of mix ups and buggy loops. If you find yourself looking at a stack space or buffer overflow error, check your nodeSort loops!

Run.doRight() Source

The first argument is the only required argument. It is passed as a string, either an html tagName ('p') or the wild card ('*'). Either upper or lower case will do. This first parameter can have an exclamation point appended, if you want to return a static array ('*!' or 'p!') instead of a nodeSort. Here is the code source for Run.doRight():

function Run.doRight(wotTag,ref,tester){

var fixed= false;

var members;

var pa= (ref)? ref : document.body;

/*
If there is a second argument, use it as the pa variable.
pa becomes the parent element for the search. The default value for pa is document.body, which is used when you pass the empty string (or no argument after the first).

An '*' in the second argument raises the ante of the search to document, if you need to work with members of the head or the root element.
*/

if(wotTag.end('!')){

wotTag=wotTag.slice(0,-1);

fixed= true;

/*
There can be a modifier to the tagName in the first argument. Append an exclamation point to the tag: 'p!' or 'h3!' or '*!'. Use the modifier when you want to force a fixed- length array, rather than a 'live' nodeType return.

If the first argument is modified, A boolean (fixed) is set true, and the '!' is stripped from wotTag to leave a valid tagName.
*/

}


pa= (pa== '*' || pa== document)? document : mr(pa) ;

/*
99 times out of a hundred pa will be an element reference or string id.
Once in a while, however, we need to go beyond the body, into the head of the document. Passing '*' in the second argument signals this larger scope.

If pa is '*' (or a reference that evaluates to 'document') the parent Element for the search will be document. Otherwise, the function mr() resolves an object or id to its element, and the search is restricted to the descendents of mr(pa).

See mr(hoo) if you missed that discussion.
*/

if(wotTag== '*' && Yankee.iz('IE') && ( Yankee.iz('DF')== false) ) members= pa.all;

/*
IE5 cannot handle the '*' wildcard, so I have an extra step here.

IE5 can do a lot of the DOM, but it has allergic reactions to some of the recommended standards. Here, the statement evaluates true if the tagName argument is '*' and the browser is IE and a version before IE6. (You can find Yankee.iz() in the Run.mrs(hoo) article.)

If the condition is true, members is set to Microsoft's document.all or element.all array. Note that members in this case is an array, not a nodeSort, and is not 'live'. Again, this is only a problem when you use the '*' wild card, and only for IE before version 6.
*/

else members= pa.getElementsByTagName(wotTag);

/*
IE6 and Mozilla/N6 have no problem with '*'. And with tagNames other than the wildcard, IE5+ works properly. So most of the time, members is a nodeSort of the elements (all elements, or those of the given tag) found as decendents of the pa element.
*/


if(fixed && members) members= collect(members);

/*
If the '!' modifier was passed in the first argument, which set fixed to true, we can 'freeze' the nodeSort at this moment by passing it through the function Run.collect(). The return value is an array, not a nodeSort, and therefore is not altered when elements on the page are dynamically added or removed.

Run.collect() is commented in the article about Run.mrs(hoo)
*/

if(tester !=null && members) members= Run.filta(members,tester);

/*
If there is a third argument, and members is not empty, use Run.filta() to test the collection against the tester argument. Run.filta() is described next.
*/

return members;

/* return the collection to the calling function */

}

Run.filta()

Run.filta() is where things get interesting. There are two required arguments- Run.filta(members,tester). The first argument is either a nodeSort or an array of elements. The second argument is a string for the test condition that filters the elements. The return value is an array or a nodeSort, depeneding on the state of that first argument.

Usually Run.filta() is called directly from Run.doRight(); though it can be called on its own. It is only called from Run.doRight() if a third argument to Run.doRight(), for the test condition, is present.

The test condition can be an attribute and value, eg: 

Run.doRight('img','','width=500px').

It can be a style property and value : 

Run.doRight('h1','','color:black').

You can test for an attribute that has any value set, that is, is not null or the empty string: 

Run.doRight('a','','href') (returns all hypertext links, but not anchors that are not links).

You can test the ids of the members array for a pattern:

 Run.doRight('*','','ids=yan')  (finds all the members who have 'yan' as a part of their id).

You can find elements that contain a specified string of text: 

Run.doRight('*','','txt=Yankee Webshop').

These are the tests I use most often, though it is easy enough to add others. The source code for Run.filta() comes next.

Run.filta() Source 

function Run.filta(members,tester){

var delim,pass,testCase,filter,name,value,prop;

var testGroup=new Array;

delim=tester.match(/\s*[\:\=]\s*/);

/*
delim is ':' or '=' or null. The colon signals a style property, the = an attribute value or a special case. If neither delimiter is in the tester string, Run.filta will look for an attribute of the string
that is set to any value.The next line splits a delimited string into a name- value pair of items.
*/

if(delim){

filter= tester.split(delim);

name=filter[0];

value=filter[1];

}


for(var i= 0; i< members.length; i++){

pass=false;

testCase=members[i];

/*
this for loop processes each element in the members array as testCase.
pass is a boolean that will reject any testCase that does not 'pass' the test.
The test to be applied is determined by the delimiter character and the name variable.
*/

if(delim==null){

if(testCase.getAttribute(tester)) pass=true;

else if(testCase[tester]) pass=true;

/*
If the test is a string with no delimiter, look for an attribute existing (and set to some value, any value).
If the testCase being examined passes this test, set pass to true, to be added to the collection.
*/

}

else if(/\:/.test(delim)){

if(testCase.style && testCase.style[name]){

if(testCase.style[name]== value) pass=true;

}

/*
If ':' was the test delimiter:
Test if the style property (name) is set to the specified value.
*/

}


else if(/\=/.test(delim)){

if(name== 'txt'){

var tem1= Run.sayWhat(testCase).toLowerCase();

var tem2= value.toLowerCase();

if(tem1.indexOf(tem2) !=-1 ) pass= true;

/*
I force a case insensitive match for the string against the text content of the element. Run.sayWhat() is commented in another chapter, it is used here to burrow through the element's childNodes and return all the text.
*/

}

else if(name== 'ids'){

prop= testCase.getAttribute('id');

if(prop.indexOf(value) !=-1 ) pass= true;

/*
Checks for a matching pattern for the test string in the id of the testCase element.
*/

}

else if(testCase.getAttribute(name)){

if(testCase.getAttribute(name)== value) pass=true;

/*
If the testCase passes the test, pass=true
*/

}

}

if(pass== true) testGroup[testGroup.length]=testCase;

/*
If this element got a passing mark, add it to the testGroup collection.
End of the loop, repeat for the next member . When we have tested all the elements, return the collection to the calling function. Usually Run.doRight().
*/

}

return testGroup;

}


Odds and Ends

DOM Node Interfaces:

nodeType value nodeType constant
1 ELEMENT_NODE
2 ATTRIBUTE_NODE
3 TEXT_NODE
4 CDATA_SECTION_NODE
5 ENTITY_REFERENCE_NODE
6 ENTITY_NODE
7 PROCESSING_INSTRUCTION_NODE
8 COMMENT_NODE
9 DOCUMENT_NODE
10 DOCUMENT_TYPE_NODE
11 DOCUMENT_FRAGMENT_NODE
12 NOTATION_NODE

What Else?

    Code from the Yankee Webshop
  1. Practical Code
  2. A DOM Function Library
  3. mr(hoo)
  4. Run.dr(wot)
  5. Run.mrs(hoo)
  6. Run.doRight()
  7. Run.sayWhat()
  8. Run.zap()

Internet Resources:

e- mail: editor@yankeeweb.net