benekastah
benekastah

Reputation: 5711

Sorting array of DOM nodes from top-level to bottom

I have an array (a javascript array, not a jQuery object) of DOM nodes that acts as a queue for a function I have that processes them. This array could potentially contain elements that have parent/child, grandparent/child, etc. relationships. I always want higher-level elements to be processed first. My first inclination was to whip up a quicksort function in javascript, but I knew it would be faster if I could use javascript's native Array.prototype.sort method.

I tried it this way:

domElements.sort(function (a, b) {
  return $(a).find(b).length ? 1 :
         $(b).find(a).length ? -1 :
         0;
});

But it didn't seem to sort it perfectly. I would still sometimes have child elements before their parents. Why doesn't this work? Is there a way to do this with javascript's native sort?

UPDATE: After surveying the methods in the answers, I wanted to know how they performed. Here are the results. Feel free to tweak and see how the performance is for you.

Upvotes: 2

Views: 650

Answers (4)

T.J. Crowder
T.J. Crowder

Reputation: 1074465

(Since you asked me to post it. :-) )

You've said you want the parent elements before the children in the array, but your code

domElements.sort(function (a, b) {
  return $(a).find(b).length ? 1 :
         $(b).find(a).length ? -1 :
         0;
});

...returns 1 if a is a parent of b, which will put a later in the array than b.

So I'm thinking:

domElements.sort(function (a, b) {
  return $(a).find(b).length ? -1 :
         $(b).find(a).length ? 1 :
         0;
});

But checking if an element is a descendant of another element is so straight-forward, I wonder if you really need the allocate-a-jQuery-object-and-then-call-find:

domElements.sort(function (a, b) {
  return isParentOf(a, b) ? -1 :
         isParentOf(b, a) ? 1 :
         0;
});

function isParentOf(parent, elm) {
    while (elm) {
        elm = elm.parentNode;
        if (elm === parent) {
            return true;
        }
    }
    return false;
}

Working example But note timmywil's answer — even though it does a bit of unnecessary work (putting the siblings in order, when all you care about is parent/child), there's a pre-baked jQuery function for that!

Upvotes: 1

kennebec
kennebec

Reputation: 104780

Sorting random elements in a document according to their source code order will keep childnodes and siblings in their proper positions.

if(!Array.prototype.indexOf){
    Array.prototype.indexOf= function(what, i){
        if(typeof i!= 'number') i= 0;
        var L= this.length;
        while(i< L){
            if(this[i]=== what) return i;
            ++i;
        }
        return -1;
    }
}
function nodeSort(nodesArray, pa){
    pa= pa || document;
    var original= [], src= pa.getElementsByTagName('*'), L= src.length;
    for(var i= 0; i<L; i++){
        if(nodesArray.indexOf(src[i])!=-1) original.push(src[i]);
    }
    return nodesArray.sort(function(a, b){
        return original.indexOf(a)- original.indexOf(b);
    });
}

Upvotes: 0

timmywil
timmywil

Reputation: 300

You can save some time by using jQuery's unique method: http://api.jquery.com/jQuery.unique/

jQuery.unique(domElements);

Upvotes: 2

benekastah
benekastah

Reputation: 5711

It looks like I mixed around the negative sign on the one:

domElements.sort(function (a, b) {
  return $(a).find(b).length ? -1 :
         $(b).find(a).length ? 1 :
         0;
});

Seems to work.

Upvotes: 0

Related Questions