views:

118

answers:

3

If I have two nodes in an HTML document, how can I tell which one comes first in HTML document order in Javascript using DOM methods?

For example,

function funstuff(a, b) {
    //a and b can be any node in the DOM (text, element, etc)
    if(b comes before a in document order) {
        var t = b; b = a; a = t;
    }
    // process the nodes between a and b. I can handle this part 
    // when I know that a comes before b.
}
+1  A: 

Rather difficult, I personally would itterate up each tree till I found a common ansester, then check which parent node(or the actual node if that low) comes first starting with firstChild and working through siblings, something like:

  function OrderCheck(node1, node2){

     var ar1 = [null, node1];
     var ar2 = [null, node2];

     for(var i = 1; ar1[i] != null; i++)
       ar1[i+1]=ar1[i].parentNode;
     for(var i = 1; ar2[i] != null; i++)
       ar2[i+1]=ar2[i].parentNode;
     ar1.reverse(); ar2.reverse(); // easier to work with.
     i = 0;
     while( ar1[i] === ar2[i] ){
       if(ar1[i] === null)
         return 0;
       else
         i++
     }

     if(ar1[i] === null)
       return 2;
     if(ar2[i] === null)
       return 1;

     if(i != 0){
       var n = ar1[i-1].firstChild;
       do{
         if(n === ar1[i])
           return 1;
         if(n === ar2[i])
           return 2;
       }while(n = n.nextSibling);
     }
        return -1;// Shouldn't happen.
  }

  var order = OrderCheck(document.body, document.body.previousSibling);
  if( order == 1){
         // element 1 first
  }else if(order == 2){
         // element 2 first
  }else{
         // there was an error.
  }

I did just edit this code in an attempt to fix two possible problems, I haven't tested this new edit however, so if something breaks I shall have to try again. (Edited again to fix a "doesn't even run" style bug).

scragar
Nice answer, but I think this fails if one node is the ancestor of the other. You need to test for 'i' going beyond the length of either of the arrays.
Alohci
It is a very interesting idea, much more efficient than the one I had thought of. I'm going to include it as a fall back if a.compareDocumentPosition isn't defined (it's a DOM 3 method).Thanks!
Michael
+3  A: 

You can use the DOM function compareDocumentPosition which will return different numbers based on the two nodes' relationships:

DOCUMENT_POSITION_DISCONNECTED = 0x01;
DOCUMENT_POSITION_PRECEDING = 0x02;
DOCUMENT_POSITION_FOLLOWING = 0x04;
DOCUMENT_POSITION_CONTAINS = 0x08;
DOCUMENT_POSITION_CONTAINED_BY = 0x10;

Potentially the result could be the sum of more than one of these codes as the answer is a bitmask, but I can't imagine a situation where two of these conditions would be true at the same time. Also note that the "disconnected" result would be returned for instance with nodes that have been created but not added to the document tree yet

Gareth
Note that *compareDocumentPosition* isn't supported by any version of Internet Explorer, up to and including IE 8.
NickFitz
I think the full solution will be to fall back to scragar's code if it isn't defined. Thanks for the tip.
Michael
+2  A: 

Resig to the rescue:

// Compare Position - MIT Licensed, John Resig
function comparePosition(a, b){
  return a.compareDocumentPosition ?
    a.compareDocumentPosition(b) :
    a.contains ?
      (a != b && a.contains(b) && 16) +
        (a != b && b.contains(a) && 8) +
        (a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
          (a.sourceIndex < b.sourceIndex && 4) +
            (a.sourceIndex > b.sourceIndex && 2) :
          1) +
      0 :
      0;
}
Crescent Fresh
It should be noted that Resig's method will only work on element nodes, not on text nodes.
Michael
@Michael: really? Good to know.
Crescent Fresh
Yeah. Text nodes don't have .sourceIndex and they don't have .contains().
Michael