skip to Main Content

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.1nUser 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.1nUser generated text 2.2nUser 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.1nUser 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:

  • All the texts surrounded by <h2></h2> and <h3></h3> tags are user generated.
  • Users can generate any number of <h2> Texts followed by any or even zero number of <h3> texts.

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.1nUser generated text 1.2'});">Save</button></h2>')

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

4

Answers


  1. You can use regex for this. See the fiddle for more details.

    Code::

    let content = `
    <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>
    `;
    
    // -- find text between a <h2> tag
    const stext = content.match(/(?<=<h2>).*(</h2>)|(?<=<h3>).*(?=</h3>)/g);
    console.log('stext:: ', stext);
    
    let output1 = '', output2 = '', count = 0;
    stext.forEach(text => {
        if(text.indexOf('</h2>') > -1) {
            // -- every time a primary text is found update the user generated content. This will run 1 count behind the main counter
        if(count > 0) {
            content = content.replace(output1, `${output1}<button class="something" onclick="bubble_fn_add_headers({output1: '${output1}', output2: ${output2}});">Save</button>`)
        }
        // -- this is for primary text
        output1 = text.replace('</h2>', '');
        output2 = ''; // -- resetting this
        count++;
      }
      else {
        // -- this is for sub-text
        output2 += `${text}n`; 
      }
    });
    content = content.replace(output1, `${output1}<button class="something" onclick="bubble_fn_add_headers({output1: '${output1}', output2: ${output2}});">Save</button>`)
    
    document.querySelector('body').innerHTML = content;
    
    Login or Signup to reply.
  2. 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>
    Login or Signup to reply.
  3. Consider generating the HTML elements dynamically from the user input, which is easier and more scalable:

    1. Get user input value using querySelector()
    2. Store the input value in an array
    3. Generate a list of "li" or "div", using createElement() and appendChild()

    Working example is here.

    As for generating more complex/nested elements, better approach would be to store data as JSON object, and render by referencing the keys..

    Login or Signup to reply.
  4. Loop over the headings with querySelectorAll

    One solution would be to iterate over your content, extract the necessary text and generate the buttons dynamically, adding custom event handlers to each button.

    If I understood the problem, this should work.

    const myHeaders = document.querySelectorAll('h2');
    
    let el;
    for (const h2 of myHeaders) {
    
        const output1 = h2.textContent;
        let output2 = [];
        el = h2.nextElementSibling;
    
        while (el && el.matches('h3')) {
            console.log(el.textContent);
            output2.push(el.textContent);
            el = el.nextElementSibling;
        }
    
        const btn = document.createElement('button');
        btn.class = "something";
        btn.textContent = "Save";
        btn.addEventListener('click', ev => {
            bubble_fn_add_headers({ output1, output2: output2.join('n') });        
        });    
        h2.append(btn);
    }
    
    function bubble_fn_add_headers(data) {
        console.log(data);
    }
    

    However, I would question a number of things about the system that generated this output in the first place.
    One thing is that your HTML seems to have very little structure.
    Perhaps each <h2> element could be enclosed in a <section> for example. This would make the task a lot easier because you could loop over the sections and extract the <h2> and <h3> elements using simple queries.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search