3

I'm trying to remove all child elements from a node but leave the actual text content of the node. I.e. go from this:

<h3>
   MY TEXT
   <a href='...'>Link</a>
   <a href='...'>Link</a>
   <select>
      <option>Value</option>
      <option>Value</option>
   </select>
</h3>

to this:

<h3>
   MY TEXT
</h3>

I know that there are a million easy ways to do this in jQuery, but it's not an option for this project... I've got to use plain old javascript.

This:

var obj = document.getElementById("myID");
if ( obj.hasChildNodes() ){
    while ( obj.childNodes){
        obj.removeChild( obj.firstChild );       
    }
}

obviously results in just <h3></h3>, and when I tried:

var h3 = content_block.getElementsByTagName('h3')[0];
var h3_children = h3.getElementsByTagName('*');
for(var i=0;i<h3_children.length;i++){
   h3_children[i].parentNode.removeChild(h3_children[i]);
}

It gets hung up part way through. I figured it was having trouble removing the options, but altering the for loop to skip removal unless h3_children[i].parentNode==h3 (i.e. only remove first-level child-elements) stops after removing the first <a> element.

I'm sure I'm missing something super obvious here, but perhaps it's time to turn to the crowd. How can I remove all child elements but leave the first-level textNodes alone? And why doesn't the above approach work?

EDITS

There are a couple of working solutions posted, which is great, but I'm still a little mystified as to why looping through and removing h3.getElementsByTagName('*') doesn't work. A similar approach(adapted from Blender) likewise does not complete the process of removing child nodes. Any thoughts as to why this would be?

Community
  • 1
  • 1
Ben D
  • 14,321
  • 3
  • 45
  • 59

5 Answers5

3
var h3=content_block.getElementsByTagName("h3")[0];
for(var i=0;i<h3.childNodes.length;i++)
{
  if(h3.childNodes[i].nodeType==3)//TEXT_NODE
  {
    continue;
  }
  else
  {
    h3.removeChild(h3.childNodes[i]);
    i--;
  }
}

JSFiddle demo

Edit:

Combined the i-- to make it look shorter:

var h3=content_block.getElementsByTagName("h3")[0];
for(var i=0;i<h3.childNodes.length;i++)
{
  if(h3.childNodes[i].nodeType==3)//TEXT_NODE
    continue;
  else
    h3.removeChild(h3.childNodes[i--]);
}

Edit #2:

Pointed out by @SomeGuy, make it even shorter:

var h3=content_block.getElementsByTagName("h3")[0];
for(var i=0;i<h3.childNodes.length;i++)
{
  if(h3.childNodes[i].nodeType!=3)//not TEXT_NODE
    h3.removeChild(h3.childNodes[i--]);
}

The brackets can be removed too, but that would be "less readable" and "confusing", so I keep it there.

Passerby
  • 9,715
  • 2
  • 33
  • 50
  • Good man! I figured I probably needed to run a check for `[x].nodeType==3` but I didn't think to use `h3.childNodes.length`... I kept trying to figure out how to use `while ( obj.childNodes)` without creating an infinite loop. **Any idea why the `getElementsByTagName() loop` didn't work?** – Ben D Oct 19 '12 at 05:59
  • @BenD Notice the `i--` in my answer? Whenever you removed a child, the parent node's `childNodes.length` is shorten by 1, so you have to "step back" your iterator too. Otherwise you would end up with calling `h3_children[3]` while it only has 2 children left. – Passerby Oct 19 '12 at 07:01
  • It could be just `if (lala.nodeType != 3) remove`, even shorter without meaningless continue branch. – Leonid Oct 19 '12 at 08:11
  • @SomeGuy Thanks! I love your solution, by the way. – Passerby Oct 19 '12 at 08:20
1

You can check properties .nodeType or .nodeName for each node.

Text nodes have these properties set to:

.nodeType == 3
.nodeName == '#text'`
Rango
  • 3,877
  • 2
  • 22
  • 32
1

For instance:

var e = obj.firstChild
while (e) {
   if (e.nodeType == 3) {
      e = e.nextSibling
   } else {
      var n = e.nextSibling
      obj.removeChild(e)
      e = n
   }
}
Leonid
  • 3,121
  • 24
  • 31
1

try this. I am assuming you will keep ant text here.

var h3 = document.getElementsByTagName('h3')[0];

if (h3.hasChildNodes()) {
    for (var i = h3.childNodes.length - 1; i >= 0; i--) {
        if (h3.childNodes[i].nodeName != "#text")
            h3.removeChild(h3.childNodes[i]);
    }
}

Hope it will work.

  • very similar to Passerby's answer, but it does work, and it's a slightly shorter syntax – Ben D Oct 19 '12 at 06:08
0

Well, The thing I used (inspired from the answers here) is somewhat like:

   var h3 = document.getElementsByTagName("h3")[0];
    Array.protoype.filter.call(h3.childNodes, function(child){
        if (child.nodeType != 3) {
            h3.removeChild(child);
        }
    });
cipher
  • 2,414
  • 4
  • 30
  • 54