vsync
vsync

Reputation: 130085

Replace a textNode with mixed-content HTML string

For this HTML node:

<div><i>foo</i> and <i>bar</i> go ### with <b>baz</b></div>

I would like to replace the string ### with another node (<u>well</u>), without replacing the entire innerHTML of the wrapper div.

Expected result:

<div><i>foo</i> and <i>bar</i> go <u>well</u> with <b>baz</b></div>

My approach was to iterate the childNodes, filter only the TEXT_NODE elements with the string i would like to replace and replace those textNodes with replaceChild using a DOM Fragment to hold the replaced content:

var root = document.querySelector('div'),
    tag = "<u>well</u>",
    tempFrag = document.createDocumentFragment(),
    children = root.childNodes,
    replacedString;

 for( var i = children.length; i--; ){
      if( children[i].nodeType === Node.TEXT_NODE && 
          children[i].nodeValue.length > 1        && 
          children[i].nodeValue.indexOf('###') != -1 ){
       
            replacedString = children[i].nodeValue.replace('###', tag);

            console.log( replacedString );
            tempFrag.innerHTML = replacedString;
            children[i].parentNode.replaceChild(tempFrag, children[i])
      }    
}
<div><i>foo</i> and <i>bar</i> go ### with <b>baz</b></div>


As you can see, replacing a textNode in this manner doesn't work as expected.

While I can manually extract each part of the replacedString and break it into:

`before textNode` / New element / `after textNode` 

and piece them all in, that would create a lot of code (this is actually the way i'm currently doing it, and am trying to think of a smarter way, but the fragment didn't help with the parsing & insertion as you can see)

Upvotes: 2

Views: 176

Answers (2)

vsync
vsync

Reputation: 130085

var root = document.querySelector('div'),
    tag = document.createElement('u'),
    children = root.childNodes,
    replacedNode,
    idx;

 tag.innerHTML = "well";
 
 for( var i = children.length; i--; ){
      if( children[i].nodeType === Node.TEXT_NODE && 
          children[i].nodeValue.length > 1 ){
            idx = children[i].nodeValue.indexOf('###');
            if( idx == -1 ) continue;
            
            replacedNode = children[i].splitText(idx);
            // remove '###' from the second split textNode ('### with')
            replacedNode.nodeValue = replacedNode.nodeValue.replace('###', '');
            // put the tag element before the second split textNode
            children[i].parentNode.insertBefore(tag, replacedNode);
      }    
}
<div><i>foo</i> and <i>bar</i> go ### with <b>baz</b></div>

Upvotes: 0

c-smile
c-smile

Reputation: 27460

Instead of this:

replacedString = inputChildren[i].nodeValue.replace('###', tag);

You can use

var offset = ...indexOf('###');
replacementNode = textnode.splitText(offset); 

And then by adding

textnode.parent.insertBefore(wrapper, replacementNode);

you can achieve what you want.

Upvotes: 1

Related Questions