2

In an attempt to make my answer more flexible on this question:

function invertDivs(parentDiv) {
  var first = document.getElementById(parentDiv).firstChild;
  console.log(first);
  var second = document.getElementById(parentDiv).lastChild;
  console.log(second);
  document.getElementById(parentDiv).insertBefore(second, first);
}
<div id="parent">
  <div id="first">Div 1</div>
  <div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>

However, the divs are only inverted sometimes, not always.

An initial click on the button yields this on the console:

enter image description here

A second click:

enter image description here

After a bunch of clicks:

enter image description here

I'm confused as to what's wrong with the code. I select the first child of parent div, and do the same for the last child. Then I just insert the current second div before the first. That's the end of the function. They are also the direct children of the parent div, as required by the insertBefore function.

2
  • 2
    Keep in mind that text nodes are also child nodes. I guess you want to use firstElementChild instead, or childNodes[0]. Commented Jul 28, 2015 at 18:28
  • 1
    Each of the white spaces in your HTML will be considered as a text node. You might not run into the issue when you get rid of all the spaces in your HTML. But that is a pain. Instead you need to check for the nodeValue . 1 is an element node and 3 being the text node. So you need to add additional checks in place to avoid processing the 1's. Commented Jul 28, 2015 at 18:28

4 Answers 4

7

As mentioned in comments, firstChild and lastChild can return text nodes for the whitespace between elements. You can use firstElementChild and lastElementChild to ignore these.

function invertDivs(parentDiv) {
  var first = document.getElementById(parentDiv).firstElementChild;
  console.log(first);
  var second = document.getElementById(parentDiv).lastElementChild;
  console.log(second);
  document.getElementById(parentDiv).insertBefore(second, first);
}
<div id="parent">
  <div id="first">Div 1</div>
  <div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>

For some other workarounds, which you might need for older browsers, see element.firstChild is returning '<TextNode ...' instead of an Object in FF

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

4 Comments

Didn't even know these element variants were added - seems I need to brush up on the DOM.
@Barmar is firstElementChild essentially parent.children[0] under the hood?
@Abdul It looks like it is.
@Abdul no, both parent.children[0] as parent.childNodes[0] behave like parent.firstChild. Seems like the only methods that accurately find only child elements are indeed firstElementChild and lastElementChild. All the others find all and every kinds of elements, including spaces and line breaks. Take a look at W3C's reference.
3

You're not taking into account text nodes. In your HTML example above, there are 5 nodes.

[0] => TextNode
[1] => #first
[2] => TextNode
[3] => #second
[4] => TextNode

It seems pretty evident that you don't care about the text nodes here. You have quite a few options.

One option would be to filter out all the text nodes. (Can't use Array.prototype.filter method because childNodes is not an array, but a NodeList)

This will give you an array of only DOM elements.

function invertDivs(parentNodeId) {
  var childElements = [],
      parentNode = document.getElementById(parentNodeId);
  
  //Filter out the child nodes that aren't elements.
  //parentNode.childNodes is a NodeList, and not an array (even though it looks like one)
  for (var i = 0; i < parentNode.childNodes.length; ++i) {
    if (parentNode.childNodes[i].nodeType === 1)
      childElements.push(parentNode.childNodes[i]);
  }
  
  parentNode.insertBefore(childElements[childElements.length - 1], childElements[0]);
}
<div id="parent">
  <div id="first">Div 1</div>
  <div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>

Another option would be to use the more modern DOM API properties: See Barmar or GolezTrol's answers. They would be much more performant if you audience has support for IE9+ browsers.

Comments

1

It's not random. If I click 2 times, to add Div 2 to the end of the list, then click 3 times to get Div 1 at the end of the list. This pattern repeats.

The reason is because there are also next nodes inbetween. This is the whitespace inbetween the elements.

To work around this, use the children attribute. This selects the child elements (instead of nodes).

function invertDivs(parentDiv) {
  var parent = document.getElementById(parentDiv);
  var first = parent.children[0];
  console.log(first);
  var second = parent.children[parent.children.length-1];
  console.log(second);
  document.getElementById(parentDiv).insertBefore(second, first);
}
<div id="parent">
  <div id="first">Div 1</div>
  <div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>

2 Comments

is firstElementChild essentially parent.children[0] under the hood?
Yes it is, although it doesn't fail but return null when there is no child. So actually firstElementChild is easier to use. Note browser compatibility though. firstElementChild is available in IE9+. children was already available in earlier versions, although it had a small bug in IE8 and before: it also included comments... If you don't care about IE8- (and you shouldn't), then firstElementChild is the easiest choice.
1

The answer to your question is given in the MDN docs(https://developer.mozilla.org/en-US/docs/Web/API/Node/firstChild) for Node.firstChild. If you refer to docs you will understand why you are getting the #text as the first Node.

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.