简体   繁体   中英

Cannot set data to clipboard after getting data from web worker

When trigger copy event (cmd + c), our application needs to call and get data from web worker, and then set it to clipboard.

But after the data is ready, setting data to clipboard doesn't seem to work.

async function process_copy(event) {

        let data = await get_data_from_worker();

        console.log("DATA FROM WORKER: ", data);

        event.clipboardData.setData('text/plain', data);
        event.clipboardData.setData('text/html', data);

        event.preventDefault();
  }

      
 window.addEventListener('copy', process_copy.bind(this));

What I need is to set data to clipboard since the data from web worker is available for use.

The reason why I can't use this command

document.execCommand('copy')

Because time to get data from web worker may take more than 5 secs, and the command above doesn't work in these cases.

Here is an example:

worker.js

onmessage = function(e) {
  postMessage('WORKER DATA');
}

index.html

<!DOCTYPE html>
<html>
<body>
  <script>
    window.onload = function() {

      const my_worker = new Worker("worker.js");
      let call_back;

      my_worker.onmessage = function(e) {
        if(call_back){
          call_back(e.data);
        }
        call_back = undefined;
      }

      function get_data_from_worker() {
        return new Promise(
          function(resolve, reject) {
            call_back = resolve;
            my_worker.postMessage("GET DATA");
          }
        )
      }

      async function process_copy(event) {
        let data = await get_data_from_worker();

        console.log("DATA FROM WORKER: ", data);

        event.clipboardData.setData('text/plain', data);
        event.clipboardData.setData('text/html', data);

        event.preventDefault();
      }

      
      window.addEventListener('copy', process_copy.bind(this));

    };
  </script>
</body>
</html>

After users trigger the copy event, It calls to process_copy function, and waits for data.

In get_data_from_worker function, I have created a promise, which sends message to web worker, and then store resolve in call_back for later use.

When the web worker receive the message, it prepares data and send back, through postMessage method.

Then, the web worker message will be returned by call_back (inside my_worker.onmessage ).

After that, the data is ready in process_copy function. But We can't set that data to clipboard.

You correctly identified the problem: you need to handle the event synchronously to be able to overwrite its default behavior.

You can workaround that issue by redesigning the workflow.

If your data really needs 5s to be generated, then you will most probably need two clicks from your users:

  1. prepare the data
  2. copy the data to clipboard

You don't need to actually handle their copy event to be able to set the data in there, so clicks will do, however, clicks are needed because the browsers won't let us copy anything in the clipboard without an user gesture, and after 5s most browsers will consider the user-gesture dead already.

 btn.onclick = async (evt) => { // from first click we prepare the data btn.disabled = true; btn.textContent = "Please wait"; // simulate waiting for worker await wait(1000); const datatext = "data as text"; const datahtml = "<h1>data as html</h1>"; // now that the data is ready // we wait for the second click btn.disabled = false; btn.textContent = "Copy to clipboard"; btn.onclick = async (evt) => { // we prepare to handle the click event // so we can overwrite its content addEventListener("copy", evt => { evt.preventDefault(); evt.clipboardData.setData("text/plain", datatext); evt.clipboardData.setData("text/html", datahtml); }, { once: true }); // we force the copy event (we don't care of the content here) document.execCommand("copy"); btn.remove(); pastezone.classList.remove("hidden"); }; }; pastezone.addEventListener("paste", (evt) => { console.log("as text:", evt.clipboardData.getData("text/plain")); console.log("as html:", evt.clipboardData.getData("text/html")); }); function wait(ms) { return new Promise( (res) => setTimeout(res, ms) ); }
 .hidden { display: none; }
 <button id="btn">Prepare data to copy</button> <textarea id="pastezone">You can paste here to test </textarea>

And if you only need to write text and need tosupport IE, you could also use the Async Clipboard API instead of document.execCommand , but this won't work here in StackOverflow's sandboxed snippets.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM