getElementsByTagNames

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.

Use

getElementsByTagNames takes two arguments:

  1. A string with a comma-separated list of tag names.
  2. An optional start element. If it's present the script searches only for tags that are descendants of this element, if it's absent the script searches the entire document.

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.

Example 1

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.

Example 2

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).




The script

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;
}

Explanation

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.

Background: array.sort()

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.

sourceIndex

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.

compareDocumentPosition

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.