James
James

Reputation: 111910

Easiest way to sort DOM nodes?

If I have a list like this:

<ul id="mylist">
    <li id="list-item1">text 1</li>
    <li id="list-item2">text 2</li>
    <li id="list-item3">text 3</li>
    <li id="list-item4">text 4</li>
</ul>

What's the easiest way to re-arrange the DOM nodes to my preference? (This needs to happen automatically when the page loads, the list-order preference is gained from a cookie)

E.g.

<ul id="mylist">
    <li id="list-item3">text 3</li>
    <li id="list-item4">text 4</li>
    <li id="list-item2">text 2</li>
    <li id="list-item1">text 1</li>
</ul>

Upvotes: 100

Views: 81176

Answers (10)

ahuigo
ahuigo

Reputation: 3331

You can use this to re-sort an element's children:

const list = document.querySelector('#test-list');

[...list.children]
  .sort((a, b) => a.innerText > b.innerText ? 1 : -1)
  .forEach(node => list.appendChild(node));
  • list.children is an HTMLCollection, so we use the spread syntax introduced in ES6 to convert it to a standard array.
  • We sort this array with a custom comparison function that sorts by innerText.
  • Every child is re-appended to list in the order of the now sorted array. When an element is re-appended it is actually moved from its previous location (docs), so after every element has been re-appended in sorted order we end up with a sorted list.

Upvotes: 104

stallingOne
stallingOne

Reputation: 4006

In case you need numerical sorting, here is the short solution that worked for me:
(if you need alphabetical sorting, just remove the two + in the function, I think it's still a shorter solution than the currently most upvoted ones)

function sortSelectOpts(selectNode) {
    const optionNodes = Array.from(selectNode.children);
    optionNodes.sort((a,b) => +a.innerText > +b.innerText ?1:-1);
    optionNodes.forEach((option) => selectNode.appendChild(option));
}

Which is to be used like this:

sortSelectOpts(document.getElementById('CircuitNumber'));

Upvotes: 0

Leedehai
Leedehai

Reputation: 3950

The neatest way I can think of:

The param compare is just like the compare function used in Array.sort().

Sort child nodes.

/**
 * @param {!Node} parent
 * @param {function(!Node, !Node):number} compare
 */
function sortChildNodes(parent, compare) {
  const moveNode = (newParent, node) => {
    // If node is already under a parent, append() removes it from the
    // original parent before appending it to the new parent.
    newParent.append(node);
    return newParent;
  };
  parent.append(Array.from(parent.childNodes) // Shallow copies of nodes.
                    .sort(compare) // Sort the shallow copies.
                    .reduce(moveNode, document.createDocumentFragment()));
}

Sort child elements (a subset of child nodes).

/**
 * @param {!Element} parent
 * @param {function(!Element, !Element):number} compare
 */
function sortChildren(parent, compare) {
  const moveElement = (newParent, element) => {
    // If element is already under a parent, append() removes it from the
    // original parent before appending it to the new parent.
    newParent.append(element);
    return newParent;
  };
  parent.append(Array.from(parent.children) // Shallow copies of elements.
                    .sort(compare) // Sort the shallow copies.
                    .reduce(moveElement, document.createDocumentFragment()));
}

Upvotes: 0

Ebrahim Byagowi
Ebrahim Byagowi

Reputation: 11228

My version, hope will be useful for others:

var p = document.getElementById('mylist');
Array.prototype.slice.call(p.children)
  .map(function (x) { return p.removeChild(x); })
  .sort(function (x, y) { return /* your sort logic, compare x and y here */; })
  .forEach(function (x) { p.appendChild(x); });

Upvotes: 11

cgenco
cgenco

Reputation: 3557

Here's an ES6 function to sort DOM nodes in place:

const sortChildren = ({ container, childSelector, getScore }) => {
  const items = [...container.querySelectorAll(childSelector)];

  items
    .sort((a, b) => getScore(b) - getScore(a))
    .forEach(item => container.appendChild(item));
};

Here's how you would use it to sort Untapped user reviews by score:

sortChildren({
  container: document.querySelector("#main-stream"),
  childSelector: ".item",
  getScore: item => {
    const rating = item.querySelector(".rating");
    if (!rating) return 0;
    const scoreString = [...rating.classList].find(c => /r\d+/.test(c));
    const score = parseInt(scoreString.slice(1));
    return score;
  }
});

Upvotes: 7

Mars Robertson
Mars Robertson

Reputation: 13223

See it in action: http://jsfiddle.net/stefek99/y7JyT/

    jQuery.fn.sortDomElements = (function() {
        return function(comparator) {
            return Array.prototype.sort.call(this, comparator).each(function(i) {
                  this.parentNode.appendChild(this);
            });
        };
    })();

Terse

Upvotes: 13

peter
peter

Reputation: 439

without analyzing too much if this brings anything new to the table, i usually use this:

function forEach(ar, func){ if(ar){for(var i=ar.length; i--; ){ func(ar[i], i); }} }
function removeElement(node){ return node.parentNode.removeChild(node); }
function insertBefore(ref){ return function(node){ return ref.parentNode.insertBefore(node, ref); }; }

function sort(items, greater){ 
    var marker = insertBefore(items[0])(document.createElement("div")); //in case there is stuff before/after the sortees
    forEach(items, removeElement);
    items.sort(greater); 
    items.reverse(); //because the last will be first when reappending
    forEach(items, insertBefore(marker));
    removeElement(marker);
} 

where item is an array of children of the same parent. we remove starting with the last and append starting with the first to avoid flickering in the top part which is probably on screen. i usually get my items array like this:

forEachSnapshot(document.evaluate(..., 6, null), function(n, i){ items[i] = n; });

Upvotes: 1

Ash
Ash

Reputation: 318

If you're already using jQuery, I'd recommend tinysort : http://tinysort.sjeiti.com/

$("li").tsort({order:"asc"});
$("li").tsort({order:"desc"});

Upvotes: 8

Corey Trager
Corey Trager

Reputation: 23123

You might find that sorting the DOM nodes doesn't perform well. A different approach would be to have in your javascript an array that represents the data that would go into the DOM nodes, sort that data, and then regenerate the div that holds the DOM nodes.

Maybe you dont' have that many nodes to sort, so it wouldn't matter. My experience is based on trying to sort HTML tables by manipulating the DOM, including tables with hundreds of rows and a couple dozen columns.

Upvotes: 26

nickf
nickf

Reputation: 546065

Though there's probably an easier way to do this using a JS Library, here's a working solution using vanilla js.

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

var items = list.childNodes;
var itemsArr = [];
for (var i in items) {
    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]);
}

Upvotes: 96

Related Questions