1

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

1

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.

  • 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 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 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 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 at 16:30
  • How about this, when something is copied, store it browser cache, when removed remove from the cache? – Pranay Jaiswal Aug 5 at 16:34
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
    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 at 14:51
0

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
    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 at 15:40

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.