javascriptforeachcreateelementdomparserselectors-api

How to perform this dynamic Find and replace in Javascript over user generated data?


I have an app that allows user to generate text with HTML code in the following format:

<h2>User generated Dynamic Data 1</h2>
    <h3>User generated text 1.1</h3>
    <h3>User generated text 1.2</h3>


<h2>User generated Dynamic Data 2</h2>
    <h3>User generated text 2.1</h3>
    <h3>User generated text 2.2</h3>
    <h3>User generated text 2.3</h3>
    
<h2>User generated Dynamic Data 3</h2>
    <h3>User generated text 3.1</h3>
    <h3>User generated text 3.2</h3>

This is how it looks like in a browser:

This is how it looks like in a browser

Is there any way to replace what user generated with the one below, using javascript?

<h2>User generated Dynamic Data 1 <button class="something" onclick="bubble_fn_add_headers({output1: 'User generated Dynamic Data 1', output2: 'User generated text 1.1\nUser generated text 1.2'});">Save</button></h2>
    <h3>User generated text 1.1</h3>
    <h3>User generated text 1.2</h3>


<h2>User generated Dynamic Data 2 <button class="something" onclick="bubble_fn_add_headers({output1: 'User generated Dynamic Data 2', output2: 'User generated text 2.1\nUser generated text 2.2\nUser generated text 2.3'});">Save</button></h2>
    <h3>User generated text 2.1</h3>
    <h3>User generated text 2.2</h3>
    <h3>User generated text 2.3</h3>
    
    
<h2>User generated Dynamic Data 3 <button class="something" onclick="bubble_fn_add_headers({output1: 'User generated Dynamic Data 3', output2: 'User generated text 3.1\nUser generated text 2.2'});">Save</button></h2>
    <h3>User generated text 3.1</h3>
    <h3>User generated text 3.2</h3>    

This is how the above would look like in a browser:

This is how the above would look like in a browser

The situation is very trickey because:

Can you guys suggest any work around this using javascript?

Thanks

I would have tried

s.replace('<h2>', '<h2>User generated Dynamic Data 1 <button class="something" onclick="bubble_fn_add_headers({output1: 'User generated Dynamic Data 1', output2: 'User generated text 1.1\nUser generated text 1.2'});">Save</button></h2>')

But it just isn't possible because the texts are dynamically generated and are unique each time.


Solution

  • Don't use regex or replace to change HTML.

    Just use DOM access

    Here is the minimum safe way to create the object and embed it in a button

    const nextUntil = (element, selectors, filter = "*") => {
      const siblings = [element];
      let next = element.nextElementSibling;
    
      while (next && !next.matches(selectors)) {
        if (next.matches(filter))
          siblings.push(next);
        next = next.nextElementSibling;
      }
    
      return siblings;
    };
    
    document.querySelectorAll('h2').forEach(header => {
      const subs = nextUntil(header,'h2','h3')
      //console.log(subs)
      const object = {output1: subs[0].textContent,output2: subs.slice(1).map(ele => ele.textContent).join('\n')}
      header.innerHTML = `${header.textContent} 
        <button class="something" 
        onclick='bubble_fn_add_headers(${JSON.stringify(object)})'>Save</button>`;
    }) 
      
    const bubble_fn_add_headers = obj => console.log(obj);
    
    /* I recommend the following instead of inline onclick
    
    document.body.addEventListener('click', (e) => {
      const tgt = e.target.closest('button.something');
      if (!tgt) return; 
      bubble_fn_add_headers(tgt.dataset.content)
    })
    
    using this code for the data-attribute
    
        data-content='${
        JSON.stringify({ 
          "output1": subs[0].textContent, 
          "output2": subs.slice(1).map(ele => ele.textContent).join('\n')
        })}'
    */
    <h2>User generated Dynamic Data 1</h2>
    <h3>User generated text 1.1</h3>
    <h3>User generated text 1.2</h3>
    
    
    <h2>User generated Dynamic Data 2</h2>
    <h3>User generated text 2.1</h3>
    <h3>User generated text 2.2</h3>
    <h3>User generated text 2.3</h3>
    
    <h2>User generated Dynamic Data 3</h2>
    <h3>User generated text 3.1</h3>
    <h3>User generated text 3.2</h3>

    Here is what you asked for. It is VERY BRITTLE and will blow up the first time a user enters a quote or a double quote

    const nextUntil = (element, selectors, filter = "*") => {
      const siblings = [element];
      let next = element.nextElementSibling;
    
      while (next && !next.matches(selectors)) {
        if (next.matches(filter))
          siblings.push(next);
        next = next.nextElementSibling;
      }
    
      return siblings;
    };
    
    document.querySelectorAll('h2').forEach(header => {
      const subs = nextUntil(header,'h2','h3')
      //console.log(subs)
      const string = `{output1: '${subs[0].textContent}, output2: '${subs.slice(1).map(ele => ele.textContent).join('\\n')}`
      header.innerHTML = `${header.textContent} 
        <button class="something" 
        onclick="bubble_fn_add_headers('${string}')">Save</button>`;
    }) 
      
    const bubble_fn_add_headers = obj => console.log(obj);
    
    /* I recommend the following instead of inline onclick
    
    document.body.addEventListener('click', (e) => {
      const tgt = e.target.closest('button.something');
      if (!tgt) return; 
      bubble_fn_add_headers(tgt.dataset.content)
    })
    
    using this code for the data-attribute
    
        data-content='${
        JSON.stringify({ 
          "output1": subs[0].textContent, 
          "output2": subs.slice(1).map(ele => ele.textContent).join('\n')
        })}'
    */
    <h2>User generated Dynamic Data 1</h2>
    <h3>User generated text 1.1</h3>
    <h3>User generated text 1.2</h3>
    
    
    <h2>User generated Dynamic Data 2</h2>
    <h3>User generated text 2.1</h3>
    <h3>User generated text 2.2</h3>
    <h3>User generated text 2.3</h3>
    
    <h2>User generated Dynamic Data 3</h2>
    <h3>User generated text 3.1</h3>
    <h3>User generated text 3.2</h3>