[SalesForce] Interacting with the (browser) Clipboard from LWC

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

Best Answer

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.