Kevin Olomu
Kevin Olomu

Reputation: 782

JS sort parent element based on child element

I want to sort my JavaScript divs on their child values (span content).

I already found here a sorting algorthm for javascript on this thread: Easiest way to sort DOM nodes?

However it is only for simple list. I want to have a modified version which don't look on the paragraph elements.

HTML

  <div class="myclass">
    <p class="hide">1</p>
    <span>2</span>
  </div>

  <div class="myclass">
    <p class="hide">2</p>
    <span>1</span>
  </div>

  <div class="myclass">
    <p class="hide">3</p>
    <span>4</span>
  </div>

  <div class="myclass">
    <p class="hide">4</p>
    <span>1</span>
  </div>

JS

var list = document.getElementById('sortme');

var items = list.childNodes;
var itemsArr = [];
for (var i in items) {
    // HERE I need to get to the SPAN Element but how?
    if (items[i].nodeType == 1) { // get rid of the whitespace text nodes
        itemsArr.push(items[i]);
    }
}

itemsArr.sort(function(a, b) {
  return a.innerHTML == b.innerHTML
          ? 0
          : (a.innerHTML > b.innerHTML ? 1 : -1);
});

for (i = 0; i < itemsArr.length; ++i) {
  list.appendChild(itemsArr[i]);
}

Here's JSFiddle.

Upvotes: 0

Views: 2425

Answers (2)

the8472
the8472

Reputation: 43052

Instead of altering the document order you could use flexboxes and the CSS order: property to change their display order.

Upvotes: 0

raina77ow
raina77ow

Reputation: 106385

Here's one possible approach (demo):

var list = document.getElementById('sortme');

var nodesToSort = list.querySelectorAll('.myclass');

Array.prototype.map.call(nodesToSort, function(node) {
  return {
    node: node,
    relevantText: node.querySelector('span').textContent
  };
}).sort(function(a, b) {
  return a.relevantText.localeCompare(b.relevantText);
}).forEach(function(item) {
  list.appendChild(item.node);
});

As you noticed, there are several changes here.

First, as I said, you don't need to use root.childNodes, as all the items that should be sorted are easily collected with either querySelectorAll or getElementsByClassname method (I choose the former, mainly for consistency reasons). That also makes nodeType-based filtering redundant.

(actually, it would have been redundant in the link answer too, had the author used children property instead of childNodes)

Second, the sorting-helper array now contains not just nodes, but objects; the purpose of this technique - also known as memoization - is to minimize the sheer quantity of DOM queries, enhancing the performance.

(here I have replaced innerHTML query with textContent, as you seem to be interested only in text here, ignoring any inline tags within the <span>. If that's not the case, just replace it back)

Finally, another change from the original: replacing double comparison (first ==, then >) with simple string function - localeCompare, specifically designed for, well, comparing the strings.

And, of course, now the whole process is structured as a chain of operations:

  • organizing the array of items alongside their memoized sorting criteria
  • sorting this array
  • (re)appending the nodes from that array into the list

... without any need of intermediate variables and stuff.


It's not clear whether the structure is uniform (i.e., do you always want to sort based on <span> contents). If you just want to sort based on visible text, consider using node.innerText instead of node.querySelector('span').textContent. Be aware, however: innerText, although supported by any modern browser, by definition is a slow, calculated value.

Upvotes: 3

Related Questions