2

Suppose I have some HTML on a forum:

<div class="sign">username <span class="info">info</span></div>

I want to write a user script that changes it to something like this:

<div class="sign"><a itemprop="creator">username</a> <span class="info">info</span></div>

(The a element will have href as well. I omitted it intentionally to make the code shorter.)

I know how to create an a element, assign it a custom attribute, and add it to the DOM.

But I don't understand how to wrap username with it. That is, how to convert username from the 1st snippet to <a itemprop="creator">username</a> in the second snippet.

user3840170
  • 26,597
  • 4
  • 30
  • 62
user90726
  • 939
  • 1
  • 8
  • 28
  • Does this answer your question? [How to get the text node of an element?](https://stackoverflow.com/questions/6520192/how-to-get-the-text-node-of-an-element) – Mister Jojo Apr 04 '21 at 11:42
  • @user3840170 There are many useful remarks from you under answers, thanks. – user90726 Apr 04 '21 at 12:21

4 Answers4

1

You can try replacing the innerHTML:

var el = document.querySelector('.sign');
el.innerHTML = el.innerHTML.replace('username', '<a itemprop="creator">username</a>');
<div class="sign">username <span class="info">info</span></div>
Mamun
  • 66,969
  • 9
  • 47
  • 59
1

this way ?

let parentElm = document.querySelector('div.sign')
  , refNod    = parentElm.childNodes[0] 
  ;
  
if ( refNod.nodeValue === 'username') 
  {
  let newNod = document.createElement('a')
  // newNod.href = '/...'
  newNod.setAttribute('itemprop','creator')
  newNod.textContent = refNod.nodeValue
 
  parentElm.replaceChild( newNod, refNod )
  }

/*****/
console.log( parentElm.innerHTML )
<div class="sign">username<span class="info">info</span></div>
Mister Jojo
  • 20,093
  • 6
  • 21
  • 40
  • This will fail as soon as the spliced node contains other text which should be preserved. – user3840170 Apr 04 '21 at 12:12
  • @user3840170 I'm not sure the PO didn't explain something about a specific text in their question, but I changed my code to that effect. – Mister Jojo Apr 04 '21 at 12:50
1

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.

user3840170
  • 26,597
  • 4
  • 30
  • 62
-1

Find all elements with class .sign and replace it's inner HTML with a wrapped text before the span tag.

var elms = document.querySelectorAll('.sign');
for (var i = 0; i < elms.length; i++) {
  let item = elms[i];
  var html = item.innerHTML;
  item.innerHTML = html.replace(/(.*)?<span/, "<a itemprop=\"creator\" href='#'>$1</a><span");
}
<div class="sign">username <span class="info">info</span></div>
<div class="sign">other name <span class="info">info</span></div>
<div class="sign">other more and more <span class="info">info</span></div>

A little bit of regex saves the day sometimes.

If you want to preserve event attached to the span element-

var elms = document.querySelectorAll('.sign');
elms.forEach(item => {
  let span = item.querySelector('span');
  var html = item.innerHTML;
  let txt = html.match(/(.*)?<span/)[0].replace("<span", "");
  item.innerHTML = `<a itemprop="creator" href='#'>${txt}</a>`;
  item.appendChild(span);
});
Ariful Islam
  • 664
  • 8
  • 12