Doing this with vanilla DOM APIs is a little involved, but not too hard. You will need to locate the DOM text node which contains the fragment you want to replace, split it into three parts, then replace the middle part with the node you want.
If you have a text node textNode
and want to replace the text spanning from index i
to index j
with a node computed by replacer
, you can use this function:
function spliceTextNode(textNode, i, j, replacer) {
const parent = textNode.parentNode;
const after = textNode.splitText(j);
const middle = i ? textNode.splitText(i) : textNode;
middle.remove();
parent.insertBefore(replacer(middle), after);
}
Adapting your example, you will have to use it something like this:
function spliceTextNode(textNode, i, j, replacer) {
const parent = textNode.parentNode;
const after = textNode.splitText(j);
const middle = i ? textNode.splitText(i) : textNode;
middle.remove();
parent.insertBefore(replacer(middle), after);
}
document.getElementById('inject').addEventListener('click', () => {
// XXX: locating the appropriate text node may vary
const textNode = document.querySelector('div.sign').firstChild;
const m = /\w+/.exec(textNode.data);
spliceTextNode(textNode, m.index, m.index + m[0].length, node => {
const a = document.createElement('a');
a.itemprop = 'creator';
a.href = 'https://example.com/';
a.title = "The hottest examples on the Web!";
a.appendChild(node);
return a;
})
}, false);
/* this is to demonstrate other nodes underneath the <div> are untouched */
document.querySelector('.info').addEventListener('click', (ev) => {
ev.preventDefault();
alert('hello');
}, false);
<div class="sign">@username: haha, <a href="http://example.org" class="info">click me too</a></div>
<p> <button id="inject">inject link</button>
Note how the ‘click me too’ handler is still attached to the link after the ‘username’ link is injected; modifying innerHTML
would fail to preserve this.