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.

Community
  • 1
  • 1
Honinbo Shusaku
  • 1,411
  • 2
  • 27
  • 45
  • 2
    Keep in mind that text nodes are also child nodes. I guess you want to use [`firstElementChild`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/firstElementChild) instead, or `childNodes[0]`. – Felix Kling Jul 28 '15 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`. – Sushanth -- Jul 28 '15 at 18:28

4 Answers4

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

Community
  • 1
  • 1
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Didn't even know these element variants were added - seems I need to brush up on the DOM. – crush Jul 28 '15 at 18:38
  • @Barmar is `firstElementChild` essentially `parent.children[0]` under the hood? – Honinbo Shusaku Jul 28 '15 at 18:39
  • @Abdul It looks like it is. – Barmar Jul 28 '15 at 18:47
  • @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](https://www.w3schools.com/jsref/prop_element_lastelementchild.asp). – Tiramonium Oct 16 '19 at 19:34
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.

crush
  • 16,713
  • 9
  • 59
  • 100
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>
GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • is `firstElementChild` essentially `parent.children[0]` under the hood? – Honinbo Shusaku Jul 28 '15 at 18:40
  • 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. – GolezTrol Jul 28 '15 at 18:45
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.