16

Possible Duplicate:
Easiest way to sort DOM nodes?

I have a list of DIVs, like this :

<div id="list">
    <div id="categorie5.1-4">4</div>
    <div id="categorie5.1-3">3</div>
    <div id="categorie5.1-5">5</div>
    <div id="categorie5.1-1">1</div>
    <div id="categorie5.1-2">2</div>
</div>

and I want to sort them, using Javascript only (no Jquery) to have a result like this :

1
2
3
4
5

If needed, I can use the end of the DIV ids : "categorie5.1-4" (server-side I can define the DIV ids to embedded the wanted order)

Thank you very much for your help!


Here is the complete code :

<html>
<head>
<script type="text/javascript">
function sortdiv() {
var container = document.getElementById("list");
var elements = container.childNodes;
var sortMe = [];
for (var i=0; i<elements.length; i++) {
    if (!elements[i].id) {
        continue;
    }
    var sortPart = elements[i].id.split("-");
    if (sortPart.length > 1) {
        sortMe.push([ 1 * sortPart[1] , elements[i] ]);
    }
}
sortMe.sort(function(x, y) {
    return x[0] - y[0];
});
for (var i=0; i<sortMe.length; i++) {
    container.appendChild(sortMe[i][1]);
}
document.getElementById("button").innerHTML = "done.";
}
</script>
</head>
<body>
<div id="list">
    <div id="categorie5.1-4">4</div>
    <div id="categorie5.1-3">3</div>
    <div id="categorie5.1-5">5</div>
    <div id="categorie5.1-1">1</div>
    <div id="categorie5.1-2">2</div>
</div>
<div id="button"><a href="#" onclick="sortdiv();">sort!</a></div>
</body>
</html>
Community
  • 1
  • 1
jrm
  • 885
  • 5
  • 12
  • 20

3 Answers3

27

First you have to get all divs:

var toSort = document.getElementById('list').children;

toSort will be a NodeList. You have to transform it to an array:

toSort = Array.prototype.slice.call(toSort, 0);

and then you can pass a callback to the sort method:

toSort.sort(function(a, b) {
    var aord = +a.id.split('-')[1];
    var bord = +b.id.split('-')[1];
    return aord - bord;
});

Edit: As @Lekensteyn already noted, comparing IDs only works if you have only single digit numbers. Fixed it to support arbitrary numbers.

You have to loop over this array and append the elements again:

var parent = document.getElementById('list');
parent.innerHTML = "";

for(var i = 0, l = toSort.length; i < l; i++) {
    parent.appendChild(toSort[i]);
}

Edit: fixed typo

DEMO

Update: If you have so many elements, caching of the IDs could be done like so:

var cache = {
   add: function(id) {
       var n = +id.split('-')[1];
       this[id] = n;
       return n;
   }
};

toSort.sort(function(a, b) {
    var aord = cache[a.id] || cache.add(a.id);
    var bord = cache[b.id] || cache.add(b.id);
    return aord - bord;
});
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thank you very much Felix - I understand the concept, but I am a js-beginner, and when I tried to implement your solution I do not see the result of the sort... Do I need to trigger something to see the DIVs rearranged? – jrm Feb 21 '11 at 15:29
  • 1
    @jrm: Hey, no, I had just a typo in the code. See my demo http://jsfiddle.net/fkling/nXkDp/ – Felix Kling Feb 21 '11 at 16:23
  • Thank you very much for your help Felix, your code does the job perfectly - Leken's code seems a bit more sophisticated and can nicely handle some exceptions (invalid IDs, comments, etc.) – jrm Feb 21 '11 at 16:33
  • @jrm: Whatever works best for you :) Regarding invalid IDs: I assumed you only deal with elements with correct IDs. What should happen to elements that don't have a proper ID? Should they be removed? Put at the end? Regarding comments: `children` only returns element nodes (so no comments, text nodes etc) – Felix Kling Feb 21 '11 at 16:38
  • @Felix, thank you for your help, I am playing with your code, which seems very optimized - I still have a question, is it possible to use a "blind character" in Javascript? Like document.getElementById('qwerty' + i + '*').innerHTML = "text", with * being 0 or 1 or many characters? (of course if only have ONE div with an ID begining with 'qwerty + i' – jrm Feb 21 '11 at 17:15
  • You can achieve that with XPath, but that might be overkill for this task. You could loop through all elements, and use `element.id.indexOf("qwerty" + i) == 0` to match all IDs beginning with `"qwerty" + i`. – Lekensteyn Feb 21 '11 at 17:19
  • Hum OK seems to be a bad idea - I explain : I want to integrate in my code different sorts, so my idea was to have an ID like this : `id="cat5.1-1.3.2"` so this div will be either in 1st position, 3rd, or 2nd depending of the button clicked – jrm Feb 21 '11 at 17:40
  • @jrm: Well, you could do something @Lekensteyn did and pre-process all the IDs and create mappings from that. Another idea would be to generate the mappings on the server side (i.e. generate JavaScript). You could use classes for the placeholder thing. I.e. give all elements starting with ID `qwerty5` the same class. In newer browser you could use `querySelectorAll` and use the attribute selector. Or you could use jQuery to select the elements. That would save you a lot of trouble. – Felix Kling Feb 21 '11 at 18:06
  • @Felix, please, what would be the code if I had jQuery? – jrm Feb 21 '11 at 18:54
  • @Felix, would it be easier if I use a dedicated classname for my sorts? like `class="class1 class2 SORT1.3.2"` ? – jrm Feb 21 '11 at 18:56
  • @jrm: To be honest, I don't know what would be the best solution here. I think *I* would create maps at the server side and set them as JavaScript object. Then one does not have to do pre-processing at the client side and not think about an encoding of the search order. – Felix Kling Feb 21 '11 at 20:16
5

You should put the elements in an array, run a sort function over the elements, and re-append the sorted elements to the container.

// container is <div id="list">
var container = document.getElementById("list");
// all elements below <div id="list">
var elements = container.childNodes;
// temporary storage for elements which will be sorted
var sortMe = [];
// iterate through all elements in <div id="list">
for (var i=0; i<elements.length; i++) {
    // skip nodes without an ID, comment blocks for example
    if (!elements[i].id) {
        continue;
    }
    var sortPart = elements[i].id.split("-");
    // only add the element for sorting if it has a dash in it
    if (sortPart.length > 1) {
        /*
         * prepare the ID for faster comparison
         * array will contain:
         *   [0] => number which will be used for sorting 
         *   [1] => element
         * 1 * something is the fastest way I know to convert a string to a
         * number. It should be a number to make it sort in a natural way,
         * so that it will be sorted as 1, 2, 10, 20, and not 1, 10, 2, 20
         */
        sortMe.push([ 1 * sortPart[1] , elements[i] ]);
    }
}
// sort the array sortMe, elements with the lowest ID will be first
sortMe.sort(function(x, y) {
    // remember that the first array element is the number, used for comparison
    return x[0] - y[0];
});
// finally append the sorted elements again, the old element will be moved to
// the new position
for (var i=0; i<sortMe.length; i++) {
    // remember that the second array element contains the element itself
    container.appendChild(sortMe[i][1]);
}

You can test this code at http://jsfiddle.net/4gkDt/1/

Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
  • 1
    Note that the sort function can be improved, if your IDs are like `categorie5.1-X`, this sort function will suffice. Otherwise, if you've IDs like `categorie5.1-X` and `categorie-5.1-XY`, you'll have to extract the X or XY part from the ID, and convert it to a number, to prevent a sorted list like 1, 11, 2, 21, 3, 4, ... – Lekensteyn Feb 21 '11 at 14:22
  • Thank you very much for your help - Do I need to "update" (to "echo"/"write") something to see the result? – jrm Feb 21 '11 at 15:26
  • (and yes, I will have some -XY IDs...) – jrm Feb 21 '11 at 15:30
  • I've just optimized the script. The sort function can operate more times on an element, so rather than retrieving the number from the ID each time, it's better to store the retrieved number for comparison. – Lekensteyn Feb 21 '11 at 15:56
  • Thank you, really! I tried to implement in a simple html file, but the "for (var i=0; i – jrm Feb 21 '11 at 16:08
  • In the last code, there was an error, `element.id` should be `elements[i].id`. You can test the code at http://jsfiddle.net/4gkDt/1/ – Lekensteyn Feb 21 '11 at 16:13
  • (I edited my first post, so you can see the complete code) – jrm Feb 21 '11 at 16:13
  • Note that if you use `childNodes` instead of `children`, you have to filter for text nodes. – Felix Kling Feb 21 '11 at 16:33
  • @Felix : can you explain this a little bit more please? – jrm Feb 21 '11 at 16:34
  • @jrm: Ah, I have overlooked the `if` clause in the `for` loop. The code will work this way. The difference is that `childNodes` will return *every* node, like comments, text and what ever. Whereas `children` will only return *element* nodes (everything we consider as HTML tags). – Felix Kling Feb 21 '11 at 16:37
0

Javascript arrays has a sort function.

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort

you can make sort the childnodes list node on innerHTML (in your example) or some other key.

This shows how to get the list node http://codingrecipes.com/documentgetelementbyid-on-all-browsers-cross-browser-getelementbyid

so something like

document.getElementById("list").children.sort(yourSortFunction)
Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
madmik3
  • 6,975
  • 3
  • 38
  • 60