7

I have 5 div's created in HTML, and I want to add all of them into a div wrapper I created in JavaScript. I tried looping through the 5 div's via a for-in loop, then append the div as a child of the wrapper.

For some reason, the for loop changes the 5 div's order and doesn't append all of them in wrapper. How can I add all div's to wrapper, keeping the (HTML) order, using JavaScript?

(Please don't post JQuery answers because that isn't the question. I want JavaScript answers only.)

JSFiddle

var wrapper = document.createElement('div'),
  myClass = document.getElementsByClassName('myClass');

myClass[0].parentElement.appendChild(wrapper);
wrapper.id = 'wrapper';

for (var key in myClass) {
  if (!myClass.hasOwnProperty(key)) continue;

  wrapper.appendChild(myClass[key]);
}
#wrapper {
  border: 2px solid green;
  color: brown;
}
<div class="myClass">First</div>
<div class="myClass">Second</div>
<div class="myClass">Third</div>
<div class="myClass">Fourth</div>
<div class="myClass">Fifth</div>

4
  • 2
    If you want order, don't iterate using for...in!!!. There is also the problem that the collection is live. Commented Feb 7, 2016 at 23:59
  • 1
    Also, HTMLCollections have additional enumerable properties other than the indexes for their members (e.g. item, namedItem and length and the IDs of elements that have them). Commented Feb 8, 2016 at 0:23
  • @RobG Luckily these are inherited properties, so the hasOwnProperty check avoids this additional problem. Commented Feb 8, 2016 at 0:29
  • @Oriol— length and element IDs aren't inherited, though enumerability differs between browsers. Commented Feb 8, 2016 at 2:18

5 Answers 5

6

The document.getElementsByClassName method returns an HTMLCollection-object, which is similar to an array, as in it has numeric keys which should be used.

e.g. for (var i = 0; i < myClass.length; ++i)

Once you use an incremental numeric index, you'll notice it actually behaves the same as your key in myClass, which is rather logical, as the key is the numeric index.

What is happening is that an HTMLCollection represents elements in document order (a so called live list, which reflects the changes in the DOM) and you are moving them around by appending them to the wrapper element (hence the order within the HTMLCollection changes too).

There are several tricks to work around this, the one closest to your current setup would be to walk through the HTMLCollection from end to start and insertBefore instead of appendChild

for (var len = myClass.length - 1; len >=0; --len) {
    wrapper.insertBefore(myClass[len], wrapper.firstChild);
}

insertBefore fiddle

This works because the wrapper is (in your example) after the elements you're moving into it, therefor not changing the order of the elements.

There is another (easier) approach: document.querySelectorAll. The querySelectorAll method returns a (static) NodeList, so you can safely assume the order will not change while you move nodes around.

The syntax is (IMHO) more convenient than getElementsByClassname, as it uses CSS Selectors (much like the popular javascript framework we won't mention)

querySelectorAll fiddle

Sign up to request clarification or add additional context in comments.

7 Comments

An alternative is to add the elements to wrapper before adding it to the DOM so as they're moved, they are removed from the DOM and hence the collection also.
@Rogier Spieker Lol! You had me laughing by your last line! I didn't mean it like that, I just didn't want JQuery answers when there isn't a JQuery tag. But I like your answer! Thank you! I think I'm going to go with the document.querySelectorAll approach.
@RobG I don't quite understand what I gain by doing that.
What @RobG is saying is that by not having the wrapper element attached to the DOM, the collection returned by getElementsByClassName will be emptied, e.g. while (myClass.length > 0) {wrapper.appendChild(myClass[0]);} (this is what Maciej Kwas' answer does). The querySelectorAll-approach is more robust, as it will work more consistently over different conditions (the wrapper can be anywhere in-/outside the DOM)
@Jessica I'd attach the wrapper to the DOM as late as possible. I'd always try to keep the number of DOM-manipulations to a minimum, as these will trigger a reflow in the browser. The least amount of changes in the DOM would be to add the elements to the wrapper and then add the wrapper to the document, so that would be my choice for this scenario.
|
3

Look at your for loop step by step:

1. myClass[First, Second, Third, Fourth, Fifth]; wrapper[] ; key = 0
2. myClass[Second, Third, Fourth, Fifth] ; wrapper[First]; key = 1

Now instead of Second, you'll take Third, because key is 1, but you'll need to take the item at index 0. This also gives the fix: always take the item at position 0.

var wrapper = document.createElement('div'),
  myClass = document.getElementsByClassName('myClass');

myClass[0].parentElement.appendChild(wrapper);
wrapper.id = 'wrapper';

for (var i = 0; i < myClass.length; i++) {
  wrapper.appendChild(myClass[0]);
}
#wrapper {
  border: 2px solid green;
  color: brown;
}
<div class="myClass">First</div>
<div class="myClass">Second</div>
<div class="myClass">Third</div>
<div class="myClass">Fourth</div>
<div class="myClass">Fifth</div>

Comments

1

You are changing the collection on the fly (in the loop itself) by removing items from it, that's why it acts wired. Here's the code that should actually work:

var wrapper = document.createElement('div'),
myClass = document.getElementsByClassName('myClass'),
myClassParent = myClass[0].parentNode;

while (myClass.length) {
    wrapper.appendChild(myClass[0]);
}
myClassParent.appendChild(wrapper);
wrapper.setAttribute('id','wrapper');

https://jsfiddle.net/byd9fer3/1/

4 Comments

You can construct for loop as well for that, it's just easier condition to check.
I tried it with a for loop and it didn't work as expected. Here's an updated JSFiddle.
@Jessica read Gavriel's answer and then Oddadmix answer and comments, then you'll understand why in that matter for loop iterating from lowest index to highest doesn't work, but reversed would.
Everything is in other's answers jsfiddle.net/byd9fer3/3 , all you need now is a bit of logical sense.
0

I have a simple working version for you with code. Thanks

<html>
<body>

<button onclick="myFunction()">Try it</button>

<p><strong>Note:</strong> The getElementsByClassName() method is not supported in Internet Explorer 8 and earlier versions.</p>

<div class="myClass">First</div>
<div class="myClass">Second</div>
<div class="myClass">Third</div>
<div class="myClass">Fourth</div>
<div class="myClass">Fifth</div>

<script>
function myFunction() {

    var wrapper = document.createElement('div');
    var x = document.getElementsByClassName("myClass");
    

for(var i=0; i < x.length;++i){
    var newdiv = document.createElement('div');
    newdiv.appendChild(document.createTextNode(x[i].innerHTML));
    wrapper.appendChild(newdiv);
   }

 document.body.appendChild(wrapper);
}
</script>

</body>
</html>

1 Comment

I don't think setting the innerHTML as the textContent is a good idea. Consider using cloneNode.
0

The simplest is to use append method from Element and use it like:

Element.prototype.append.apply(domElement, arrayOfElements);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.