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>
68137 次浏览

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]);
}

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.

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

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

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; });

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

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); });

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;
}
});

Use ES6 syntax to re-sort children:

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


[...list.children]
.sort((a,b)=>a.innerText>b.innerText?1:-1)
.forEach(node=>list.appendChild(node));

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()));
}