7

Objective: to copy text in a bootstrap modal to the clipboard.

JS:

$(document).ready(function(){
  $(document).on('click','#copy-btn', function(){

        // var value = $('#error-message').html();

        // using a static value, just to eliminate any question
        // about what should be copied.
        copytext('kilroy tested this');  

    })
});

function copytext(text) {
    var textField = document.createElement('textarea');
    textField.innerText = text;
    document.body.appendChild(textField);
    textField.select();
    document.execCommand('copy');
    textField.remove();
    console.log('should have copied ' + text); // but it refuses to do so when a modal is used!
}

This Works:

When I try this without a bootstrap modal popup, kilroy tested this is copied to my clipboard:

<button type="button" class="btn btn-success" id="copy-btn">Copy</button>

This Doesn't:

But... when I move the <button> into the modal, nothing gets copied to the clipboard, even though the console reports "should have copied kilroy tested this".

<!-- AJAX Error Popup -->
<div class="modal fade" id="ajax-error" tabindex="-1" role="dialog" aria-labelledby="errorModal" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header modal-header-danger">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
        <h4 class="modal-title" id="errorModal">Error Detected!</h4>
      </div>

      <div class="modal-body" id="error-message"><!-- AJAX message inserted here --></div>

      <div class="modal-footer">

        <button type="button" class="btn btn-success" id="copy-btn">Copy</button>

        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

I'm at a loss for any other way to troubleshoot this--

  • I've demonstrated that the copytext() function works,
  • I've eliminated any question about the source of what should be copied, and
  • I've demonstrated that copy-btn is being called when the button is clicked (copytext() is being called and logging to the console).

The only thing left is some wonkiness about the bootstrap modal.

Using jquery 2.1.1 and bootstrap 3.3.6

Open to any ideas or workarounds :)

Tim Morton
  • 2,255
  • 1
  • 13
  • 19

2 Answers2

18

document.execCommand('copy'); functionality depends on trusted events. If a event needs to be trusted then the target element should also have a proper focus.

Try setting the focus on the textElement and set it to the modal after you remove it from the text element. this should solve the problem

function copytext(text) {
    var textField = document.createElement('textarea');
    textField.innerText = text;
    document.body.appendChild(textField);
    textField.select();
    textField.focus(); //SET FOCUS on the TEXTFIELD
    document.execCommand('copy');
    textField.remove();
    console.log('should have copied ' + text); 
    ajax-error.focus(); //SET FOCUS BACK to MODAL
}
karthick
  • 11,101
  • 5
  • 48
  • 79
  • I could have sworn that I tried that out of desperation... but must have failed to refresh the browser or some stupid thing like that. Who knows... Thank you for the explanation, that helps take it out of the realm of sorcery! Works like it's supposed to now :) – Tim Morton Jan 06 '18 at 01:37
  • I'm using `vex` dialog modals, and this is the only way I succeeded to copy. Thanks a lot! – М.Б. Mar 09 '20 at 13:18
6

In short: as the modal has tabindex="-1" the .focus() will only work in Chrome. For a cross-browser solution, you have to insert the textarea into the DOM as a descendant of the modal.

The key is, that the textarea has to be the document.activeElement when the copy command is executed. In other words, it has to have focus. This could be achieved by calling .focus() on it, however in your specific case the click event happens within a modal with a tabindex="-1" already in focus. At the time of writing in this scenario the .focus() method will work in Chrome only. In other browsers tabindex="-1" will prevent the textarea from getting focused, unless it is a descendant node of the modal.

Therefore the solution below creates the textarea when it always can have focus, as a sibling of the clicked element:

$(document).ready(function(){
    $(document).on('click', '#copy-btn', function(){
        copytext('dferenc tested this', this);  
    })
});

function copytext(text, context) {
    var textField = document.createElement('textarea');
    textField.innerText = text;

    if (context) {
        context.parentNode.insertBefore(textField, context);
    } else {
        document.body.appendChild(textField);
    }

    textField.select();
    document.execCommand('copy');
    // Let `.remove()` also work with older IEs
    textField.parentNode.removeChild(textField);
    console.log('should have copied ' + text);
}
dferenc
  • 7,163
  • 12
  • 36
  • 42
  • Um, so you are putting a textarea inside of a button? – epascarello Jan 05 '18 at 22:56
  • Well, for a millisecond, yes. Not that I would do it in a markup. – dferenc Jan 05 '18 at 22:57
  • Well if it were going to be regular document, a validator would give "Error: The element textarea must not appear as a descendant of the button element." – epascarello Jan 05 '18 at 23:06
  • Agreed. For the sake of validity continuum, I have changed the script to insert the textarea _next_ to the button. The idea still stands, wherever the clicked button is, the textarea can have focus. – dferenc Jan 05 '18 at 23:35
  • Thanks for answering. I took the easier route, but the discussion was still profitable. – Tim Morton Jan 06 '18 at 01:40
  • @TimMorton You're welcome! FYI, to me `.focus()` does not seem to do the trick in Firefox and Safari, while the above does. – dferenc Jan 06 '18 at 08:48
  • @TimMorton also, updated the explanation why it works this way. – dferenc Jan 06 '18 at 14:06
  • I forgot to test on FF; I’ll have to check that when I get back to work. Thanks for the reminder. – Tim Morton Jan 06 '18 at 23:04