Search code examples
javascriptreplacetextareaclipboard

JavaScript - Convert p tag to text area and copy changed text to clipboard


How to achieve this? In an HTML page, there is a p tag with some content (but only with indentations through &nbsp; and new line tags <br>). I'd like to offer two buttons: one to convert the p tag into a text area so that the existing content can be adapted by the user, one to copy the content (if the content was not adapted, the original content should be copied, if adapted, certainly the adapted code should be copied). I have prepared the code below so far. The final functionality should be (most of it not working/implemented in the snippet; the solution should be JS only):

  • Click Copy: copies the changed content or original content if not adapted
  • Click Adapt: turns the p tag into a text area, user can adapt the text
  • The text area should retain the original format: indentations and the line breaks but it should not display the HTML tags (e.g. <br>)
  • When the adapted content is copied after clicking Copy, the text area should disappear and the original content should be displayed again (the original p tag, not the text area), i.e. all the changes should be reverted
  • When clicking Adapt and then Adapt a second time, also all the changes should be reverted and the original p should be displayed again (otherwise, the text area would not vanish until page reload). Idea: When the Adapt button gets clicked, the name of the button should change to something like "Revert", and after clicking "Revert" the original p and the button "Adapt" could be displayed again.
  • How to position the Copy button in the first place and then the Adapt button right next to it? How to then reference to the p tag? It must work without a predefined ID.

Any further ideas? Thanks!

<button onclick="edit(this)">Adapt</button>
<p>some text 1<br>&nbsp;&nbsp;&nbsp;&nbsp;some text 2<br>&nbsp;some text 3&nbsp;&nbsp;some text 4 &amp;&amp; .... <br>&nbsp;&nbsp;..... <br>&nbsp;&nbsp;&nbsp;&nbsp;...... <br>&nbsp;&nbsp;...... <br>...... <br><br></p>
<button title="Copy to clipboard" onclick="copy_text(this)">Copy</button>

<script>
function edit(item) {
    var a = item.nextElementSibling;  
    a.outerHTML = a.outerHTML.replace(/<p>/g, '<textarea style="width: 100%; height: 100px ">').replace(/<\/p>/g, '</textarea>'); 
  }
</script>
<script>
    function copy_text(item) {
    const str = item.previousElementSibling.innerText;
    const el = document.createElement("textarea");
    el.value = str;
    el.setAttribute("readonly", "");
    el.style.position = "absolute";
    el.style.left = '-9999px';
    document.body.appendChild(el);
    el.select();
    document.execCommand("copy");
    document.body.removeChild(el);
  };
</script>


Solution

  • Test it here in StackBlitz

    I don't know how you intend finding the p element an id or a class or an attribute but for this answer I just used the tag.

    Check this answer for why I used the Clipboard API

    ReplaceWith MDN

    let original = '';
    const buttons = [...document.querySelectorAll('button')];
    
    function edit(button) {
      let paragraph = button.nextElementSibling.nextElementSibling;
      button.innerText = paragraph.tagName == 'P' ? 'Revert' : 'Adapt';
    
      if (paragraph.tagName == 'P') {
        original = paragraph.innerHTML;
        const textarea = Object.assign(document.createElement('textarea'), {
          innerHTML: paragraph.innerText, // I use the innerText so that the HTML tags will be stripped but the space and new lines will be kept
          style: 'width: 100%; height: 100px;' // Style for the textarea
        });
    
        paragraph.replaceWith(textarea); // Replace the p tag directly with the new textarea
      } else {
        const p = Object.assign(document.createElement('p'), {
          innerHTML: original
        });
        
        paragraph.replaceWith(p);
      }
    }
    
    function copy_text(button) {
      let paragraph = button.nextElementSibling;
      const str = paragraph.value || paragraph.innerText;
    
      navigator.clipboard.writeText(str).then(_ => {
        if (paragraph.tagName !== 'P') { // To convert the textarea back to the p
          const p = Object.assign(document.createElement('p'), {
            innerHTML: original // Convert the new lines back to br tag and the spaces to &nbsp
          });
          paragraph.replaceWith(p);
        }
    
        button.previousElementSibling.innerText = 'Adapt';
        alert('Copied');
      }, e => alert(`Failed: ${e}`));
    };
    
    // Event Listeners
    buttons.forEach(button => button.addEventListener('click', e => {
      const buttonType = button.innerText.toLowerCase();
      if (['adapt', 'revert'].includes(buttonType)) edit(button);
      else if (buttonType == 'copy') copy_text(button);
    }));
    <h1>Paragraph 1</h1>
    <button>Adapt</button>
    <button title="Copy to clipboard">Copy</button>
    
    <p>some text 1<br>&nbsp;&nbsp;&nbsp;&nbsp;some text 2<br>&nbsp;some text 3&nbsp;&nbsp;some text 4 &amp;&amp; .... <br>&nbsp;&nbsp;..... <br>&nbsp;&nbsp;&nbsp;&nbsp;...... <br>&nbsp;&nbsp;...... <br>...... <br><br></p>
    
    <h1>Paragraph 2</h1>
    
    <button>Adapt</button>
    <button title="Copy to clipboard">Copy</button>
    
    <p>some text 1<br>&nbsp;&nbsp;&nbsp;&nbsp;some text 2<br>&nbsp;some text 3&nbsp;&nbsp;some text 4 &amp;&amp; .... <br>&nbsp;&nbsp;..... <br>&nbsp;&nbsp;&nbsp;&nbsp;...... <br>&nbsp;&nbsp;...... <br>...... <br><br></p>