|
ArticleNodeCompareDocumentOrder
HOWTO determine if one node is before or after another node
The question is simple: is node A before or after node B in the document? The answer is... complex. The codeThis code relies on functions explained elsewhere: /**
* Compares the document order of two nodes, returning 0 if they are the same
* node, a negative number if node1 is before node2, and a positive number if
* node2 is before node1. Note that we compare the order the tags appear in the
* document so in the tree <b><i>text</i></b> the B node is considered to be
* before the I node.
*
* @param {Node} node1 The first node to compare
* @param {Node} node2 The second node to compare
* @return {Number} 0 if the nodes are the same node, a negative number if node1
* is before node2, and a positive number if node2 is before node1.
*/
goog.dom.compareNodeOrder = function(node1, node2) {
// Fall out quickly for equality.
if (node1 == node2) {
return 0;
}
// Use compareDocumentPosition where available
if (node1.compareDocumentPosition) {
// 4 is the bitmask for FOLLOWS.
return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
}
// Process in IE using sourceIndex - we check to see if the first node has
// a source index or if it's parent has one.
if ('sourceIndex' in node1 ||
(node1.parentNode && 'sourceIndex' in node1.parentNode)) {
var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
var index1 = isElement1 ? node1.sourceIndex : node1.parentNode.sourceIndex;
var index2 = isElement2 ? node2.sourceIndex : node2.parentNode.sourceIndex;
if (index1 != index2) {
return index1 - index2;
} else {
if (isElement1) {
// Since they are not equal, we can deduce that node2 is a child of
// node1 and therefore node1 comes first.
return -1;
}
if (isElement2) {
// Similarly, we know node1 is a child of node2 in this case.
return 1;
}
// If we get here, we know node1 and node2 are both child nodes of the
// same parent element.
var s = node2;
while ((s = s.previousSibling)) {
if (s == node1) {
// We just found node1 before node2.
return -1;
}
}
// Since we didn't find it, node1 must be after node2.
return 1;
}
}
// For Safari, we cheat and compare ranges.
var doc = goog.dom.getOwnerDocument(node1);
var range1, range2, compare;
range1 = doc.createRange();
range1.selectNode(node1);
range1.collapse(true);
range2 = doc.createRange();
range2.selectNode(node2);
range2.collapse(true);
return range1.compareBoundaryPoints(Range.START_TO_END, range2);
};The code walkthroughThe goog.dom.compareNodeOrder function always returns one of three values:
Note that we compare the order the tags appear in the document. In this example: <b><i>text</i></b> The <b> node is considered to be before the <i> node. If you want to test whether one node contains another node, use goog.node.contains instead. The first thing we should do is test whether the nodes are equal, because then we can just quit. There's no equality trickery here, just use == and be done with it. if (node1 == node2) {
return 0;
}W3C DOM Level 3 defines the node.compareDocumentPosition method. If the browser supports this method, we should use it, since it does pretty much exactly what we want. Unfortunately, few browsers support it. if (node1.compareDocumentPosition) {
// 4 is the bitmask for FOLLOWS.
return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
}Microsoft Internet Explorer supports a property called node.sourceIndex, which is the position of the node in the document.all collection. Since this collection is maintained in document order, we can use it to determine whether one node is before another. Unfortunately, the document.all collection only contains elements, not other node types. If one of the nodes we are comparing is not an element, we need to do some additional hacking to determine which one comes first. if ('sourceIndex' in node1 ||
(node1.parentNode && 'sourceIndex' in node1.parentNode)) {
var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
var index1 = isElement1 ? node1.sourceIndex : node1.parentNode.sourceIndex;
var index2 = isElement2 ? node2.sourceIndex : node2.parentNode.sourceIndex;
if (index1 != index2) {
return index1 - index2;
} else {
if (isElement1) {
// Since they are not equal, we can deduce that node2 is a child of
// node1 and therefore node1 comes first.
return -1;
}
if (isElement2) {
// Similarly, we know node1 is a child of node2 in this case.
return 1;
}
// If we get here, we know node1 and node2 are both child nodes of the
// same parent element.
var s = node2;
while ((s = s.previousSibling)) {
if (s == node1) {
// We just found node1 before node2.
return -1;
}
}
// Since we didn't find it, node1 must be after node2.
return 1;
}
}Safari supports neither node.compareDocumentPosition nor node.sourceIndex, so we need to get creative. One solution is to use a little-known technique called DOM ranges. Originally designed for emulating text selection (think selecting a paragraph of text in your word processor), we can abuse the range technique to compare the position of two arbitrary DOM nodes. var doc = goog.dom.getOwnerDocument(node1); var range1, range2, compare; range1 = doc.createRange(); range1.selectNode(node1); range1.collapse(true); range2 = doc.createRange(); range2.selectNode(node2); range2.collapse(true); return range1.compareBoundaryPoints(Range.START_TO_END, range2); Further reading
|
Sign in to add a comment
