Javascript Copy function handle not working

Viewed 119

I am writing copy button for code-blocks, but the button can not handle "copy" event.

The buttons are created with createElement method, and event added to the buttons with addEventListener to the buttons, before appending to the blocks, through foreach loop.

JSFiddle Link of the blocks

Create Button code:

const textspan = document.createElement("SPAN");
const bgbutton = document.createElement("BUTTON");

bgbutton.classList.add("btnCopy", "badge");
bgbutton.innerText = "copy";
bgbutton.addEventListener('click', copytextHandle);

textspan.appendChild(bgbutton);

const codeSyn = document.querySelectorAll("pre");

codeSyn.forEach(function(el) {
    el.insertAdjacentElement('afterbegin', textspan);
})

In my text, I have three blocks, but only one button is visible(last), first two blocks has no button.

Also, the click event, and functionality can not implemented with click hook. The problem is straight about, click event, please mention which causing the error.

window.addEventListener('load', function() {
    var y = document.querySelectorAll("pre code");
    
    for(var i = 0; i < y.length; i++) {
        y[i].innerHTML = y[i].innerHTML.replace(/^\r?\n/, "");
    }
    
    const textspan = document.createElement("SPAN");
    const bgbutton = document.createElement("BUTTON");
    
    bgbutton.classList.add("btnCopy", "badge");
    bgbutton.innerText = "copy";
    bgbutton.addEventListener('click', copytextHandle);

    textspan.appendChild(bgbutton);
    
    const codeSyn = document.querySelectorAll("pre");
    
    codeSyn.forEach(function(el) {
        el.insertAdjacentElement('afterbegin', textspan);
    })  
})

const copytextHandle = (e) => {
    const codeBlock = document.querySelectorAll(".hljs-ln")[e.target.dataset.idx];
    const text = codeBlock
       .innerText
       .trim()
       .replace(/\s+/g, " ")
       .replace(/}/g, "}\n")
          .replace(/{ /g, "{\n  ")
          .replace(/;/g, ";\n  ")
        copySuccess.classList.remove('hide');
    navigator.clipboard.writeText(text).then(() => setTimeout(() => bgbutton.innerHTML("Copied"),200),
      (e) => {
        console.log('Error writing to the clipboard', e.message);
         copySuccess.classList.add('hide');
       }
    );  
};
span > button.btnCopy {
  position: absolute;
  right: 0;
  margin-top: 0.6rem;
}

pre {
  position: relative;
}

.btnCopy{border:none; background-color: #e8e8e8;}
/*background-color:a5a5a5, 9c9c9c*/
.btnCopy:hover{background-color: #585858}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Testing HighlightJS</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/styles/routeros.min.css">
</head>
<body>
    <h3>Testing HighlightJS Multiple blocks</h3>
    <h4>First Code block</h4>
    <pre><code class="language-css">
    .class{
    font-size: 0.85rem;
    font-weight: 600;
    font-family: monospace;
    }
    h1 .header{
    font-size: 1.5rem;;
    font-family: Arial;
    }
    </code></pre>
    
    <h4>Second Code block</h4>
    <pre><code class="language-css">
    .class{
    font-size: 0.85rem;
    font-weight: 600;
    font-family: monospace;
    }
    h1 .header{
    font-size: 1.5rem;;
    font-family: Arial;
    }
    </code></pre>
    
    <h4>Third Code block</h4>
    <pre><code class="language-css">
    .class{
    font-size: 0.85rem;
    font-weight: 600;
    font-family: monospace;
    }
    h1 .header{
    font-size: 1.5rem;;
    font-family: Arial;
    }
    </code></pre>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/highlight.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/languages/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.6.0/highlightjs-line-numbers.min.js"></script>
<script>hljs.highlightAll(); hljs.initLineNumbersOnLoad();</script>
    
</body>
</html>

2 Answers

After clicking the button copy you can get to a <table> element which is inside one of your <code> elements with this code:

const codeBlock = e.target.parentNode.nextSibling.children[0];

Once that is applied, the text within <code> gets copied to the variable text which we can see by running the code below and checking the console output.

The copy buttons now appear on all <code> blocks. I've added the code to create the button and span inside the codeSyn.forEach(function(el) { ... }, so that new elements are created for every <pre> that you are iterating through.

Another problem that arises is:

Uncaught ReferenceError: copySuccess is not defined

To resolve this error you should assign an HTML element that has class hide to the variable copySuccess so that this works:

copySuccess.classList.remove('hide');

Check the below code:

window.addEventListener('load', function() {
    var copySuccess = document.getElementById("copySuccess");
    var y = document.querySelectorAll("pre code");
    
    for(var i = 0; i < y.length; i++) {
        y[i].innerHTML = y[i].innerHTML.replace(/^\r?\n/, "");
    }
    
    const codeSyn = document.querySelectorAll("pre");
    
    codeSyn.forEach(function(el) {
        const textspan = document.createElement("SPAN");
        const bgbutton = document.createElement("BUTTON");
    
        bgbutton.classList.add("btnCopy", "badge");
        bgbutton.innerText = "copy";
        bgbutton.addEventListener('click', copytextHandle);

        textspan.appendChild(bgbutton);
        el.insertAdjacentElement('afterbegin', textspan);
    })  
})

const copytextHandle = (e) => {
    const codeBlock = e.target.parentNode.nextSibling.children[0];
    const text = codeBlock
       .innerText
       .trim()
       .replace(/\s+/g, " ")
       .replace(/}/g, "}\n")
          .replace(/{ /g, "{\n  ")
          .replace(/;/g, ";\n  ");
    navigator.clipboard.writeText(text).then(() => setTimeout(() => e.target.innerHTML = "copied",2000),
      (e) => {
        console.log('Error writing to the clipboard', e.message);
      }
    );
};
span > button.btnCopy {
  position: absolute;
  right: 0;
  margin-top: 0.6rem;
}

pre {
  position: relative;
}

.hide {
  display: none;
}

.btnCopy{border:none; background-color: #e8e8e8;}
/*background-color:a5a5a5, 9c9c9c*/
.btnCopy:hover{background-color: #585858}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Testing HighlightJS</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/styles/routeros.min.css">
</head>
<body>
    <h3>Testing HighlightJS Multiple blocks <span id="copySuccess" class="hide">Copied...</span></h3>
    <h4>First Code block</h4>
    <pre><code class="language-css">
    .class{
    font-size: 0.85rem;
    font-weight: 600;
    font-family: monospace;
    }
    h1 .header{
    font-size: 1.5rem;
    font-family: Arial;
    }
    </code></pre>
    
    <h4>Second Code block</h4>
    <pre><code class="language-css">
    .class{
    font-size: 0.85rem;
    font-weight: 600;
    font-family: monospace;
    }
    h1 .header{
    font-size: 1.5rem;
    font-family: Arial;
    }
    </code></pre>
    
    <h4>Third Code block</h4>
    <pre><code class="language-css">
    .class{
    font-size: 0.85rem;
    font-weight: 600;
    font-family: monospace;
    }
    h1 .header{
    font-size: 1.5rem;
    font-family: Arial;
    }
    </code></pre>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/highlight.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/languages/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.6.0/highlightjs-line-numbers.min.js"></script>
<script>hljs.highlightAll(); hljs.initLineNumbersOnLoad();</script>
    
</body>
</html>

What is happeninig is you're moving the same button in a loop all the way to last pre. Use eg. cloneNode to make a new button per pre element.

simple move scenario (https://jsfiddle.net/jtg6fczd/)

document.querySelector("div").append(button) // adds button to div
document.body.append(button) // moves button out of div 
Related