A browser must support either sourceIndex
or compareDocumentPosition
to use this script to the fullest.
Safari 1.3.2 supports neither, so it doesn't order the elements.
HTML has several related elements with distinct tag names, like h1-h6 or input, select and textarea.
getElementsByTagName
works only on elements with the same tag name, so you cannot use it to get
a list of all headers or all form fields.
The getElementsByTagNames
script (note the plural "names") takes a list of tag
names and returns an array that contains all elements with these tag names in the order they appear in the
source code. This is extremely useful for, for instance, my ToC script, which needs all h3s and
h4s in the document in the correct order.
I'd have loved to add this method to the Node prototype, but that's impossible in Explorer and Safari. Therefore I use it as a normal function until all browsers expose their W3C DOM prototypes.
getElementsByTagNames
takes two arguments:
The function returns an array (and not a nodeList!) with the requested tags, ideally in the order they appear
in the source code. For this sorting the browser must support either sourceIndex
or
compareDocumentPosition
. If it supports neither (Safari) the array contains the elements in the
order of their tag names in the getElementsByTagNames
call.
var headerList = getElementsByTagNames('h1,h2,h3,h4');
Now headerList
becomes an array with all h1-h4 tags in the document in order of
appearance. Run script.
var element = document.getElementById('test'); var formFieldList = getElementsByTagNames('input,select,textarea',element);
Now formFieldList
becomes an array with all form fields that are descendants of
the element with ID="test" in order of appearance. Run script on
this test form (correct answer: input, select, input, textarea).
This is the script. Include it in your .js file and use it as described above.
function getElementsByTagNames(list,obj) { if (!obj) var obj = document; var tagNames = list.split(','); var resultArray = new Array(); for (var i=0;i<tagNames.length;i++) { var tags = obj.getElementsByTagName(tagNames[i]); for (var j=0;j<tags.length;j++) { resultArray.push(tags[j]); } } var testNode = resultArray[0]; if (!testNode) return []; if (testNode.sourceIndex) { resultArray.sort(function (a,b) { return a.sourceIndex - b.sourceIndex; }); } else if (testNode.compareDocumentPosition) { resultArray.sort(function (a,b) { return 3 - (a.compareDocumentPosition(b) & 6); }); } return resultArray; }
function getElementsByTagNames(list,obj) { if (!obj) var obj = document;
First of all determine the start element: obj
, or, if it's not given, the document.
var tagNames = list.split(','); var resultArray = new Array();
Split the list of tag names on commas. Create an array to hold the results.
for (var i=0;i<tagNames.length;i++) { var tags = obj.getElementsByTagName(tagNames[i]); for (var j=0;j<tags.length;j++) { resultArray.push(tags[j]); } }
Now we go through all tag names, use the normal getElementByTagName()
to get all the elements
with one tag name, and transfer them to resultArray
one by one. This is a bit of a kludge, but
since getElementsByTagName
returns a nodeList, I cannot use array.concat()
to create
the new array. Pushing the elements one by one is the best solution I've been able to find.
This gives us a resultArray
with pointers to all elements with the desired tag names, but still
in the order the tag names had in the getElementsByTagNames
call. We have to sort them in their
document order/
var testNode = resultArray[0];
Now we start the sorting. We need to know if the browser supports sourceIndex
or
compareDocumentPosition
, and to find out we take one node for a bit of support detection.
if (!testNode) return [];
If there is no first node (ie. the page doesn't contain any of the requested elements), we return an empty array.
The array.sort()
method takes an optional function as an argument. This function compares two elements
(usually called a
and b
). If the first one should go first the function should
return a negative number, if the second one should go first, the function should return a positive number.
If sourceIndex
is supported, we sort the elements by comparing their sourceIndices.
sourceIndex
is a useful Microsoft extension that gives the source order number of an element. The
first element in the page (<html>
) has sourceIndex
0, the second (<head>
)
has sourceIndex
1, etc. sourceIndex
is also the index number of the element in
getElementsByTagName('*')
.
if (testNode.sourceIndex) { resultArray.sort(function (a,b) { return a.sourceIndex - b.sourceIndex; }); }
If we subtract the sourceIndex
of the first element from that of the second, we get a negative
number if the first element comes first, and a positive number if the last element comes first. This is exactly
what sort()
needs. resultArray
now contains the elements sorted according to their
document position.
If compareDocumentPosition
is supported, we sort the elements by using this method.
compareDocumentPosition
is a Level 3 Core method that compares the document position of two nodes
and returns a bitmask:
If, for instance, a tag is contained by and follows another tag, compareDocumentPosition
returns
16 + 4 = 20.
else if (testNode.compareDocumentPosition) { resultArray.sort(function (a,b) { return 3 - (a.compareDocumentPosition(b) & 6); }); }
We're only interested in compareDocumentPosition
's values 2 or 4: precedes or follows. Therefore
we do a bitwise AND 6 (& 6
) so that the result is 2 or 4 (of course it can't be 6: a node cannot both
precede and follow another node).
If b
follows a
4 is returned, but sort()
needs a negative number. If b
precedes a
2 is
returned, but sort()
needs a positive number. To give sort()
what it needs I subtract
the 2 or 4 from 3. This gives 1 or -1, so that sort()
now properly sorts the elements and
resultArray
contains the elements sorted according to their document position.
return resultArray; }
Finally we return the resultArray
to whichever function asked for it. Remember that if a browser
doesn't support either sourceIndex
or compareDocumentPosition
the array is not sorted.