7

I have this HTML code:

<div id="main">
  <div id="dv_7">...</div>
  <div id="dv_1">...</div>
  <div id="dv_8">...</div>
  <div id="dv_4">...</div>
  <div id="dv_11">...</div>
  <div id="dv_2">...</div>
</div>

How to order the divs in the maindiv with javascript? I have no idea :(

thanks in advance,

Danny Fox
  • 38,659
  • 28
  • 68
  • 94

7 Answers7

8

Hope this helps. Updated the id to account for alphabetical ordering 1 and 11.

<div id="main">
  <div id="dv_07">7...</div>
  <div id="dv_01">1...</div>
  <div id="dv_08">8...</div>
  <div id="dv_04">4...</div>
  <div id="dv_11">11...</div>
  <div id="dv_02">2...</div>
</div>​

jQuery option:

var mylist = $('#main');
var listitems = mylist.children('div').get();
listitems.sort(function(a, b) {
    var compA = $(a).attr('id').toUpperCase();
    var compB = $(b).attr('id').toUpperCase();
    return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
})
$.each(listitems, function(idx, itm) {
    mylist.append(itm);
});​

Javascript option:

var mylist = document.getElementById('main');
var divs = mylist.getElementsByTagName('div');
var listitems = [];
for (i = 0; i < divs.length; i++) {
        listitems.push(divs.item(i));
}
listitems.sort(function(a, b) {
    var compA = a.getAttribute('id').toUpperCase();
    var compB = b.getAttribute('id').toUpperCase();
    return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
});
for (i = 0; i < listitems.length; i++) {
    mylist.appendChild(listitems[i]);
}​
Coding101
  • 527
  • 7
  • 18
7

How about:

var main = document.getElementById( 'main' );

[].map.call( main.children, Object ).sort( function ( a, b ) {
    return +a.id.match( /\d+/ ) - +b.id.match( /\d+/ );
}).forEach( function ( elem ) {
    main.appendChild( elem );
});

Live demo: http://jsfiddle.net/ZEKrH/6/

(You'll need to shim those array methods for IE8.)

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • Can you set a NodeList as the calling context for Array methods in IE8? ...or I suppose it wouldn't matter since they're shimmed! :P –  Mar 19 '12 at 22:58
  • @amnotiam Yes. Adding ES5-shim will make my demo work in IE8: http://jsfiddle.net/ZEKrH/1/ – Šime Vidas Mar 19 '12 at 23:02
  • Yeah, sorry about that. I wasn't thinking at first. I think there may be issues with native methods, but shimmed ones would obviously work. Already +1'd you. :) –  Mar 19 '12 at 23:06
  • `main.children` doesn't exist in IE8 and `main.querySelector()` doesn't exist in IE7. Do you have a shim for those? – jfriend00 Mar 19 '12 at 23:16
  • 1
    @jfriend00 `.children` does exist in IE6-8, but it also includes comment nodes (which shouldn't be an issue in this application). IE7 has only 3-5% left, and should die soon. If IE7 support is required, one should use a cross-browser library. It's not feasible to manually polyfill that browser (because there is just too much to fix). – Šime Vidas Mar 19 '12 at 23:35
  • @ŠimeVidas - the browser stats I've seen put IE7 at 4-6%. So, when you build a web-page, you don't support IE7 or recommend to others code that works in IE7? – jfriend00 Mar 19 '12 at 23:40
  • @jfriend00 As I said, if IE7 support is required, I recommend a cross-browser library. (I use jQuery for everything.) I don't think it's a good idea to try to polyfill IE7 manually or to deliberately restrict your code so that it also works in IE7. – Šime Vidas Mar 19 '12 at 23:55
  • You can do it just as efficiently without the shims using POJS and have it work in all browsers in use. – RobG Mar 20 '12 at 00:22
  • @RobG - yeah, that was the point I was trying to make (and why I provided a POJS solution in my answer). – jfriend00 Mar 20 '12 at 00:37
  • @RobG My stance is that the ES5 array methods should be embraced because they improve the quality of the code (the code communicates its purpose more clearly). Avoiding the array methods for the sole purpose of achieving compatibility with IE8/IE7 is senseless IMHO. – Šime Vidas Mar 20 '12 at 01:31
  • @SimeVidas—you also shouldn't use array methods where it isn't necessary. It's two lines of code to replace `map`, a while loop is just as easy as forEach, and the code is just as readable. It all depends on whether you want to be dependent on shims and libraries or keep your code clean and transparent (and highly compaible). – RobG Mar 20 '12 at 02:07
  • @RobG I'll say it again: array iteration methods are superior to loop statements. They improve the quality of the code. By avoiding them, you decrease the quality of your code. Doing that just to make it work in IE8/7 (even though there exists a convenient shim for those browsers) is not a good idea IMHO. – Šime Vidas Mar 20 '12 at 12:27
  • Repetition doesn't improve your argument, you need to justify extra code for `map`, `forEach` and have DOM elements inherit a `querySelector` method (I'd like to see how you do that in IE given how you've used it). Making long reference worms by chaining methods is not considered good practice either (difficult to debug, unusual syntax that is difficult for some to maintain) but you are happy to do that. And there are many more browsers than IE that don't support methods new in ES5 and HTML5. – RobG Mar 20 '12 at 12:36
  • @RobG Hm, let's first get one thing straight. Do you agree with me that - in an ES5-compatible environment - the ES5 array iteration methods are superior to loop statements? (So, if the environment *does* provide array iteration methods, they are preferred over loop statements.) – Šime Vidas Mar 20 '12 at 17:42
  • Yes, but that environment doesn't exist for browsers or web user agents in general. Given a choice, I tend to maximum compatibility, especially when it is simple to do. It means one less library or extra chunk of code to worry about. Also, extending built–in objects like Array is not a good idea as a general strategy as for..in iteration is done reasonably often (and there are valid reasons to do that) without suitable safeguards. Of course once ES5 is ubiquitous it won't matter, but I suspect it will some years before that is the case. – RobG Mar 20 '12 at 22:55
  • If `querySelector` is the only holdup, you'd just replace `main.querySelector( '#dv_' + id );` with `document.getElementById('dv_'+id);`, or [rework the code](http://jsfiddle.net/ZEKrH/2/) a little to eliminate DOM selection. –  Mar 21 '12 at 23:37
  • @RobG We have both explained our positions and we obviously disagree. – Šime Vidas Mar 22 '12 at 00:38
  • 1
    @amnotiam Ah, of course. DOM selection is not needed since we already have all references inside `main.children`. I've further simplified the code. – Šime Vidas Mar 22 '12 at 00:49
  • @ŠimeVidas: I thought about `.slice()` too, but decided to keep it with `.map()` since I think you'll run into trouble with IE when setting the calling context of a native Array method to a NodeList. I'd need to verify this, but I'm pretty sure. –  Mar 22 '12 at 01:03
  • ...though the `.map()` could possibly be reduced to `[].map.call(main.children, Object)` if IE doesn't mind a DOM element being passed to `Object`. –  Mar 22 '12 at 01:09
  • 1
    @amnotiam Yes, you are right. `[].slice.call( nodeList )` does not work in IE7/8. `[].map.call(main.children, Object)` does work in those browsers - it's a nice hack. – Šime Vidas Mar 22 '12 at 01:38
  • Glad to see it works in IE. I figured it had about a 50/50 chance. Thanks for verifying. –  Mar 22 '12 at 01:42
  • @amnotiam Btw I just checked and `[].map.call( x, Object )` is pretty much how underscore.js's `_.toArray( x )` works internally. (So, it seems that this is the way to go.) – Šime Vidas Mar 22 '12 at 01:49
  • @KayKoder In which browser does it not work? Do you get an error in the browser’s console? – Šime Vidas Nov 19 '19 at 14:18
2

the following might work:

var main = document.getElementById("main");
for(var i = 11; i > -1; i--) {
    var div = document.getElementById("dv_" + i);
    if(div != null)
        main.appendChild(div);
}
Tom
  • 4,096
  • 2
  • 24
  • 38
2

For a solution that works for any arbitrary number of child divs with numbers in any range, you can use a function like this (that actually parses the number of our the ID and does a true numeric sort on it):

function sortChildrenDivsById(parentId) {
    var parent = document.getElementById(parentId);
    // get child divs
    var children = parent.getElementsByTagName("div");
    var ids = [], obj, i, len;
    // build an array of objects that has both the element 
    // and a parsed div number in it so we can sort
    for (i = 0, len = children.length; i < len; i++) {
        obj = {};
        obj.element = children[i];
        obj.idNum = parseInt(children[i].id.replace(/[^\d]/g, ""), 10);
        ids.push(obj);
    }
    // sort the array
    ids.sort(function(a, b) {return(a.idNum - b.idNum);});
    // append in sorted order
    for (i = 0; i < ids.length; i++) {
         parent.appendChild(ids[i].element);
    }
}

Working example here: http://jsfiddle.net/jfriend00/v9mCM/

FYI, this is cross-browser, plain javascript and will even work in old browsers without shims.


Here's another way of doing it with slightly less code:

function sortChildrenDivsById(parentId) {
    var parent = document.getElementById(parentId);
    var children = parent.getElementsByTagName("div");
    var ids = [], i, len;
    for (i = 0, len = children.length; i < len; i++) {
        ids.push(parseInt(children[i].id.replace(/^.*_/g, ""), 10));
    }
    ids.sort(function(a, b) {return(a - b);});
     for (i = 0, len = ids.length; i < len; i++) {
         parent.appendChild(document.getElementById("dv_" + ids[i]));
     }
}

Working example: http://jsfiddle.net/jfriend00/TqJVb/

All the ids nums are parsed out and put into an array which is sorted and then each object is looked up by id and reinserted in sorted order. This wouldn't be quite as fast as the first one, but it's less code.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

The following is compatible with browsers back to IE 5 and similar, no shims or libraries. The sort function can be modified to sort numerically, at present it will sort the divs as 1, 11, 2, 4, etc. It's not too hard to change it so they are sorted 1, 2, 4, ... 11.

function sortDivs(div) {
  var divs = div.getElementsByTagName('div');
  var a = [];
  for (var i=0, iLen=divs.length; i<iLen; i++) { 
    a[i] = divs[i];
  }
  a.sort(function(a, b) {
    return a.id < b.id? -1 : a.id == b.id? 0 : 1;
  });
  while (iLen--) {
    div.insertBefore(a[iLen], div.firstChild);
  }
}

window.onload = function() {
  sortDivs(document.getElementById('main'));
}

Edit

Minimalist version for those who think it matters with comments and numeric sort.

function sortDivs(div) {
  var divs = div.getElementsByTagName('div');

  // Convert divs collection to an array
  for (var i=0, iLen=divs.length, a=[]; i<iLen; i++) a[i] = divs[i];

  // Sort the array numerically
  a.sort(function(a, b) {
    return a.id.split('_')[1] - b.id.split('_')[1]; 
  });

  // Move elements to sorted order
  while (iLen--) div.insertBefore(a[iLen], div.firstChild);
}
RobG
  • 142,382
  • 31
  • 172
  • 209
0

2021 answer

function sortChildren(parent, comparator) {
    parent.replaceChildren(...Array.from(parent.children).sort(comparator));
}

That's all you need. Example:

const main = document.getElementById('main');
sortChildren(main, (a, b) => +a.id.match( /\d+/ ) - +b.id.match( /\d+/ ));
serg06
  • 2,027
  • 1
  • 18
  • 26
0
<div id="main">
  <div id="dv_7" data-order=7>7</div>
  <div id="dv_1" data-order=1>1</div>
  <div id="dv_8" data-order=8>8</div>
  <div id="dv_4" data-order=4>4</div>
  <div id="dv_11" data-order=11>11</div>
  <div id="dv_2" data-order=2>2</div>
</div>

<button onclick="sort()">
Sort
</button>

function sort() {

  var main = document.getElementById( 'main' );

  [].map.call( main.children, Object ).sort( function ( a, b ) {
console.log(a.dataset.order);
      return +a.dataset.order - +b.dataset.order;
  }).forEach( function ( elem ) {
      main.appendChild( elem );
  });

}

Updated @Šime Vidas answer to support dynamic order with button and Data Attribute

http://jsfiddle.net/hbe4w90s/

Jonyx4
  • 157
  • 1
  • 6