4

I want to copy a card number into the clipboard so that I can paste it in Notepad. The code which I got from the internet works very well if tried in the developer toolbar of the browser. However if I add that code into my Javascript file and run the project then it does not work. Following is the code:

$.ajax({
  type: "POST",
  url: '@Url.Action("CopyToClipboard", "MyAccountSurface")',
  data: {
    controlId: controlId
  },
  dataType: 'json',
  success: function(data) {
    var $temp = $("<input>");
    $("body").append($temp);
    $temp.val(data.CardNumber).select();
    document.execCommand("copy");
    $temp.remove();
    alert('copied successfully');
  }
});
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
user2746466
  • 361
  • 5
  • 23

7 Answers7

2

Updated:

User interaction is mandatory to execute document.execCommand. So in your case it is not possible to copy the text from AJAX Response. It is the security measure that browsers agreed upon.

Refer W3C API

Copy and cut commands triggered through a scripting API will only affect the contents of the real clipboard if the event is dispatched from an event that is trusted and triggered by the user, or if the implementation is configured to allow this.


A workaround with user interaction

Steps added:

  • Place a text box far away from webpage using relative position.
  • Add a button in a disabled state. Once data is available re-enable the button.
  • On button click you will be able to perform document.execCommand since you are directly interacting with browser (Hence no security issue as mentioned in API)

$(document).ready(function() {
  $.ajax({
    url: 'https://jsonplaceholder.typicode.com' + '/posts/1',
    method: 'GET' 
  }).then(function(data) {
    console.log(data);
    $('#toBeCopied').val(data.title);
    $("#copyIt").attr('disabled', null);
  });

  
});
function copyToClipboard(){
    var $temp = $("<input />");
    $("body").append($temp);
    $temp.val($("#toBeCopied").val()).select();
    var result = false;
    try {
        result = document.execCommand("copy");
    } catch (err) {
        console.log("Copy error: " + err);
    }
    $temp.remove();
  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input type="text" value="dummy" id="toBeCopied" style="display:none; position: relative; left: -1000px;">
<b>Below button will be enabled once the data is available from AJAX</b>
<button id="copyIt" onclick="copyToClipboard()" disabled>Copy To Clipboard</button>
Gangadhar Jannu
  • 4,136
  • 6
  • 29
  • 49
2

If you want to copy into clipboard on click with Ajax

Element, which you are going to click has few events: mousedown and click. And they are triggered in this order. It means that you can send ajax request in first one and work with result in last one and in this case you will not have security problems.

Let me share working example:

  $link.on("mousedown", function() {
    var url = $(this).data("url");
    var $temp = $("<input id='copy_container' style='position:fixed;left:-200px;'>");

    $.ajax({
      url: url,
      dataType: "json",
      success: function (data) {
        $("body").append($temp);
        $temp.val(data.text);
      }
    })
  })

  $link.on("click", function() {
    setTimeout(function() {
      var $input = $("input#copy_container");
      if ($input.length && $input.val().length > 0) {
        $input.select();
        document.execCommand("copy");
        $input.remove();
      }
    }, 100)
  })

You need this timeout with 100ms to wait for ajax response. It can be improved like you want.

Fixed and negative position - I think you know why we need it.

Anton
  • 21
  • 2
1

Well, what are you copying? document.execCommand("copy"); requires something to be selected(highlighted) in the first place.

I think in your example, select follows .val(). But in order for this to work you need to be selecting an element, not it's value.

$temp.val(data.CardNumber);
$temp.select();

copied = document.execCommand("copy");
$temp.remove();

if(copied){
    alert('copied successfully');
}else{
    alert('something went wrong');
}
Serg Chernata
  • 12,280
  • 6
  • 32
  • 50
  • But, why this code runs successfully outside the ajax call. If i add the hardcode value in cardnumber. – user2746466 Dec 28 '16 at 11:39
  • Hi Serg, value of copied is coming 'false'. – user2746466 Dec 28 '16 at 11:43
  • How do I make it work for a sent text rather than element, as i want to copy it on the fly, i do not want to store it in any element. – user2746466 Dec 28 '16 at 11:48
  • @user2746466 I'm still looking into it but I believe this is a security thing. Copying something for the user into clipboard can already lead to malicious behavior. Doing so completely invisibly is even worse, however I can't say for a fact if it can be done yet. – Serg Chernata Dec 28 '16 at 11:53
  • @user2746466 I think if you make your new element `display:none` in css, you could at least make it seem to the user like you're not creating and removing an element just for this. – Serg Chernata Dec 28 '16 at 12:03
  • @user2746466 also, move `$temp.remove();` into the `if` statement. Especially if you end up visually hiding the field with css. – Serg Chernata Dec 28 '16 at 12:06
  • Hi Serg, I want to copy string not the element. Element i am already creating dynamically. – user2746466 Dec 28 '16 at 13:06
  • @user2746466 I know, that's how you do it, by calling select method on the element, not `.val()`. Try that in a fiddle, that part definitely works. – Serg Chernata Dec 28 '16 at 14:03
  • @user2746466 It's in my example above. – Serg Chernata Dec 28 '16 at 14:14
  • @user2746466 alrighty man, here's a full example of a working method: https://stackoverflow.com/questions/22581345/click-button-copy-to-clipboard-using-jquery – Serg Chernata Dec 28 '16 at 14:59
1
var response;

// recursively using setTimeout to wait for response 
var recCopy = function (){
    if(response){
        copy(response); 
        return;
    }     
    else {
        setTimeout(recCopy,2000); // for e.g. 2ms
    }
}

function copy(value) {
    var tempInput = document.createElement("input");
    tempInput.style = "position: absolute; left: -1000px; top: -1000px";
    tempInput.value = value;
    document.body.appendChild(tempInput);
    tempInput.select();
    try {
            var successful = document.execCommand('copy',false,null);
            var msg = successful ? 'successful' : 'unsuccessful';
            console.log('Copying text command was ' + msg);
        } catch (err) {
            alert('Oops, unable to copy to clipboard');
        }
    document.body.removeChild(tempInput);
}

$('.copyToClipboard').on('mousedown',function (e){
        $.ajax({
          url: "https://www.example.com/xyz",
          data: {
             formData :formData
          },
          type : 'post',
          success: function (data) {     
            response = data;  // on success, update the response
          } 
        }) 
});

$('.copyToClipboard').on('click',function (e){
    recCopy();
});

ajax call and copying response need to be handled separately (for e.g click and mousedown events have been handled here).
setTimeout can be recursively used to check for response. Once response is received, copy to clipboard can be executed.

Wassey
  • 11
  • 2
0

As this is not a user interaction, it will not work.

The workarounds which we can implement is getting the data from the ajax call as soon as the mouseenter in the area where user wants to copy the data & place the data in some textarea or input box.

And, on the click event we can copy the data in the clipboard.

//mouseenter event of a button

$("#jq-copy-txt").on('mouseenter', function() {
    $.ajax({
      url: 'url',
      method: 'GET'
    }).then(function(data) {
       let copyFrom = document.getElementById("jq-cpy-txt-area"); 
        document.body.appendChild(copyFrom);
        copyFrom .textContent = data.title;
    });
});

// click event fired by user

$("#jq-copy-txt").on('click', function() {

   var fn = function() {
      let copyFrom = document.getElementsByTagName("textarea")[0];
      copyFrom.select();
      document.execCommand("copy");
    };
    setTimeout(fn, 1000);});
0

What @Anton said was good but it is all bad practice because You are depending on the server to give You a response at a given time which is bad, You can see in all the big websites that have complicated backend that they just put it in an HTML object so the user could copy it via ctrl+c or via button click, I would've done it a lil bit differently than Antons way

$('#btn').on('click', function(e){

    var url = 'Your-link.com';
    var $temp = $("<input id='copy_container' 
        style='position:fixed;left:-200px;'>");
    $.ajax({
      type: "POST",
      url: url,
      success: function(res) {
        $("body").append($temp);
        $temp.val(res);
      },
      error: function() {
         //handle error and do something
      }
    });
    setTimeout(function() {
        var $input = $("input#copy_container");
        if ($input.length && $input.val().length > 0) {
            $input.select();
            document.execCommand("copy");
            $input.remove();
        }
    }, 500)
});   

this way You dont need to reuse event listeners, yet like I said before its far from perfect. Better put it in a HTML element shown to the user.

z3r0
  • 39
  • 1
  • 7
0

Here is how I do it. You can set value from ajax call return successful to clipboard (first button) or copy text from textarea to clipboard whenever you want it to (second button).

<div>
        <div>
            <label>Content</label>
        </div>
        <div>
            <button type="button" onclick="fnGenerate()">Retrieve and Copy To Clipboard</button>
        </div>
        <div>
            <button type="button" onclick="fnCopy()">Copy To Clipboard</button>
        </div>
        <div>
            <div>
                <textarea cols="50" rows="8" id="Content"></textarea>
            </div>
        </div>
    </div>

for the js part

function fnGenerate() {
        $.ajax({                
            type: 'POST',
            dataType: 'json',
            ............
            ............
            url: 'someUrl',                  
            success: function (data, textStatus, xhr) {
                $('#Content').val(data);
                $('#Content').select();
                document.execCommand('copy');
            },
            error: function (xhr) {
                //Do Something to handle error
                var error = xhr.responseText;
            }
        });
    }

    function fnCopy() {
        $("#Content").select();
        document.execCommand('copy');
    }
user14181068
  • 269
  • 3
  • 7