Reputation: 111910
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
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.sort
this array with a custom comparison function that sorts by innerText
.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
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
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
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
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
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
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
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
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
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