5

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).

4

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 Aug 5 '19 at 16:15
  • Hmmm. Maybe I could move it "off screen" but leave it visible? Do you think that would work? – Phil W Aug 5 '19 at 16:16
  • 1
    I was testing exactly same to move it off-screen. Added position fixed. It works fine now. modified in answer – salesforce-sas Aug 5 '19 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 Aug 5 '19 at 16:30
  • How about this, when something is copied, store it browser cache, when removed remove from the cache? – Pranay Jaiswal Aug 5 '19 at 16:34
2

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!');
    }
}
1
  • 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 Feb 27 at 14:50
1

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.

1
  • 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 Aug 5 '19 at 15:40
1

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 Oct 9 '20 at 14:05
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 Aug 5 '19 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.