2

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)

1
  • For anyone wondering, it's for my script Tagify Commented Nov 10, 2018 at 20:12

2 Answers 2

1

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.

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

2 Comments

Thanks. Split helped, I've managed to piece a working example (posted as an answer) but I still had to replace the ### string in replacementNode since it was still present
.parent should be either .parentNode or .parentElement (tried to edit the answer, but there are too many pending edits).
0

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>

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.