13

I have an LWC component that represents an SObject in a grid-based presentation. The grid is generated by a parent LWC component. The LWC component is to provide a "Copy" action that copies the salient details of the SObject the component represents onto the clipboard (two representations should be created; a plain text one that is just the object's name and an application/json one that is the full detail as a JSON string).

The individual grid cells are to provide a means to paste the copied detail either adding a new SObject instance for that cell or updating the existing instance.

To this end I've been trying to make use of the browser's clipboard functionality (note that I only need to support Chrome and Safari). To that end I have been reading up on the browser Clipboard API and have been trying to work out how to use it. It should be noted that the data to be copied isn't actually directly part of the DOM, so I've not been trying to use the document.execCommand approach described in the MDN web docs.

Here's a trivial example of the leaf node LWC JavaScript I've been trying to use in my proof of concept:

import { LightningElement, api } from 'lwc';

export default class Day extends LightningElement {
    @api day;

    copyDay = function () {
        console.log(`Entry ${this.day.id}!`);
        this.doCopy();
    }

    doCopy = function () {
        let content = [
            new Blob([`Day ${this.day.id}`], {type: "text/plain"}),
            new Blob([JSON.stringify(this.day)], {type: "application/json"})];

        navigator.clipboard.write(content).then(function () {
            console.log("successfully copied to clipboard");
        }, function () {
            console.log("failed to write to clipboard");
        });

        console.log(`copied ${this.day.id}`);
    }
}

The related template is:

<template>
  <lightning-card>
    <h3 class="title" slot="title">
      <a href="javascript:void(0);">Day {day.id}</a>
    </h3>
    <lightning-button-menu icon-size="xx-small" alternative-text="Actions" slot="actions">
      <lightning-menu-item value="Copy" label="Copy" onclick={copyDay}></lightning-menu-item>
    </lightning-button-menu>
  </lightning-card>
</template>

Note that the "day" API property is an object with "id" and some other properties that are a simple representation of the SObject for the "day".

The first issue I have is that this code causes an error when the copy action is performed:

TypeError: navigator.clipboard.write is not a function at Day.doCopy (compiledPreview.js:4490:31) at Day.copyDay (compiledPreview.js:4481:16)

This is seen when running in the latest Chrome browser and selecting the Copy menu option for a Day instance in the grid.

I have no clue why I see what I see here; attempting to log the navigator.clipboard shows essentially an empty object with no prototype. Is this because of the Locker Service?

I suspect the next issue I will face, if I can get this working, is that I won't get any events raised when writing this content to the clipboard (I actually want the parent component to react when the clipboard changes and to check whether the data in the clipboard is still valid for pasting as a "day" instance).

Has anyone actually done this sort of thing, using the browser clipboard? If I can't get it going I'll have to fall back to using an internal mechanism that doesn't use the clipboard at all (which would be a shame since this means copy-n-paste won't work across windows, for example, and I won't have different pastable representations used depending on the type support of the target application for the paste).

6 Answers 6

9

You are correct that clipboard on secure navigator is not available.

So, as a work around, get the details you need from variables in javascript and set it in an input, then copy that. Here is example:

HTML:

<div style="width: 20rem;">
    <lightning-card>
        <h3 class="title" slot="title">
            <a href="javascript:void(0);">Day {day.id}</a>
        </h3>
        <lightning-button-menu icon-size="xx-small" alternative-text="Actions" slot="actions">
            <lightning-menu-item value="Copy" label="Copy" onclick={copyDay}></lightning-menu-item>
        </lightning-button-menu>
    </lightning-card>
</div>
<input disabled class="my-class" style="position:fixed;top:-5rem;height:1px;width:10px;"></input>

JS:

@api day = { id: '123', name: 'my_day', label: 'My Day' };

copyDay = function() {
    console.log(`Entry ${this.day.id}!`);
    this.doCopy();
};

doCopy = function() {
    let content = this.day;
    let inp = this.template.querySelector('.my-class');
    inp.disabled = false;
    inp.value = JSON.stringify(content);
    inp.select();
    document.execCommand('copy');
    inp.disabled = true;

    console.log(`copied ${this.day.id}`);
};

Input will not work if not visible atleast as a tiny dot (cannot use slds-hide or width and height cannot be 0). Height 1 and width 10 is the best I was able to achieve. While copying remove disable and after copying disable it.

8
  • Thanks. I don't think I can use this approach since, as you said, I'd have to have some (small, but) visible artefact in the UI for this input. Nasty!
    – Phil W
    Commented Aug 5, 2019 at 16:15
  • Hmmm. Maybe I could move it "off screen" but leave it visible? Do you think that would work?
    – Phil W
    Commented Aug 5, 2019 at 16:16
  • 1
    I was testing exactly same to move it off-screen. Added position fixed. It works fine now. modified in answer Commented Aug 5, 2019 at 16:21
  • 1
    It's ugly (the need to add the input), but this gives the kernel of a workaround - I can then consider adding the "copy" handler that replaces the detail with my two different encoded values as per Interact with the Clipboard and modifying the clipboard content.
    – Phil W
    Commented Aug 5, 2019 at 16:30
  • How about this, when something is copied, store it browser cache, when removed remove from the cache? Commented Aug 5, 2019 at 16:34
11

Well, with LWS(Lightning Web Security) starting Spring 22, we can now use Clipboard API without any issue in Lightning Web Components. I just tested the writeText function & it works perfectly.

Here's a nice little utility that works.

import { api, LightningElement } from 'lwc';

export default class CopyToClipboardUtil extends LightningElement {
    @api
    async doCopy(textToCopy){
        const input = document.createElement("textarea");
        input.innerHTML = textToCopy;

        document.body.appendChild(input);
        input.select();

        if(navigator.clipboard){
            const selection = document.getSelection();
            await copy(selection.toString());
        } else {
            // deprecated but still a good fallback because it is supported in most of the browsers
            document.execCommand('copy');
        }
        document.body.removeChild(input);
     }

}

export const copy = async textToCopy => await navigator.clipboard.writeText(textToCopy).catch(err => console.error(JSON.stringify(err)), err => console.error(JSON.stringify(err)));

Playground here

Make sure you've LWS enabled.

10

Quite useful thread! I've put all your ideas in a Javascript module in this repository so that can import the it in any of your LWC's. Also, I found that the input element doesn't pick up line breaks, so you'll see that I'm creating a text area instead. I've also created a small component to demo how it works here.

Hopefully it is useful to you all.

In summary, the component would look like this:

const copyTextToClipboard = (content) => {
    // Create an input field with the minimum size and place in a not visible part of the screen
    let tempTextAreaField = document.createElement('textarea');
    tempTextAreaField.style = 'position:fixed;top:-5rem;height:1px;width:10px;';

    // Assign the content we want to copy to the clipboard to the temporary text area field
    tempTextAreaField.value = content;

    // Append it to the body of the page
    document.body.appendChild(tempTextAreaField);

    // Select the content of the temporary markup field
    tempTextAreaField.select();

    // Run the copy function to put the content to the clipboard
    document.execCommand('copy');

    // Remove the temporary element from the DOM as it is no longer needed
    tempTextAreaField.remove();
}

export { copyTextToClipboard };

And it would be used like this:

import { copyTextToClipboard } from 'c/copyTextToClipboard';

export default class CopyToClipboardBtn extends LightningElement {
    handleClick() {
        copyTextToClipboard('Copy this!');
    }
}
2
  • Thank you! Wonderful contribution! I found that copyTextToClipboard() never returns to the caller so my after copy ui animation is not running. Have you experienced this?
    – muchavie
    Commented Feb 27, 2021 at 14:50
  • I guess you mean that the focus is lost, isn't. That'd make sense as I'm explicitly doing tempTextAreaField.select(); If you mean some script afterwards is not run, I didn't face that, sorry. I'm showing a toast successfully, but not other animation is happening afterwards in my case. Do you have any example? PS: So sorry, didn't see your message till now. Commented Apr 28, 2021 at 20:16
3

I couldn't find a way to use the new Clipboard API in LWC, and because I don't have a selectable text in the DOM, I had to utilize a solution I found somewhere else. It works for me.

copyTo() {
    let extension_link = this.textToCopy;
        var dummy = document.createElement("textarea");
        // to avoid breaking orgain page when copying more words
        // cant copy when adding below this code
        // dummy.style.display = 'none'
        document.body.appendChild(dummy);
        //Be careful if you use texarea. setAttribute('value', value), which works 
       // with "input" does not work with "textarea". – Eduard
        dummy.value = extension_link;
        dummy.select();
        document.execCommand("copy");
        document.body.removeChild(dummy);
}
1
  • 1
    Hi. This is somewhat similar to the answer from salesforce-sas, but showing dynamic addition and removal of a DOM element just for the event firing, so upvoted as it is useful.
    – Phil W
    Commented Oct 9, 2020 at 14:05
2

According to Mozzila docs for clipboard API

write is not compatible with latest Chome, edge and safari.

writeText is, so I tried

navigator.clipboard.writeText('newClipText'); in locker console

when I enable locker, I get the same message as you Error: Cannot read property 'writeText' of undefined , so it looks like its not compatible.

Locker service viewer also says navigator.Clipboard would be returned as undefined.

So I guess, you have to stick to what sfdcfox has said.

2
  • 1
    I'm actually giving up on using the browser clipboard at this point and will re-visit when I have more time to see whether there is a way to use the old document.execCommand approach when you don't actually have any selectable text in the DOM (see my comment on sfdcfox's "answer").
    – Phil W
    Commented Aug 5, 2019 at 15:40
  • It works with LWS :) Commented Jan 29, 2022 at 16:54
0

I just tested this, and you can use the copy and paste events to detect, and control, the act of copying and pasting. Note that this still might not get you where you're trying to go, but hopefully this points you in the right direction.

connectedCallback() {
  window.addEventListener('copy', this.handleCopyEvent);
  window.addEventListener('paste', this.handlePasteEvent);
}
handleCopyEvent(event) {
  // ... //
}
handlePasteEvent(event) {
  // ... //
}
1
  • 2
    Thanks. The problem I see with this is that the copying relies on the document.execCommand("copy"), for example, and this requires some form of text selection in the DOM. I don't have any inputs in my DOM to select. I cannot see how I can use it for what I need - if I don't have a selection, how do I identify what was actually being copied (i.e. get to the "day" data from a specific instance of the Day LWC) in order to inject it into the event)?
    – Phil W
    Commented Aug 5, 2019 at 14:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .