0

I have been working this problem for a while with no success. I am able to get the image blob from the ClipboardAPI and I am able to create an image file and attach it to the record via an Apex method. The problem seems to lie in the fact that the blob I get from the ClipboardAPI isn't in a form that Apex can do anything with and my image is always a blank square, or is otherwise useless. That is the crux of my problem that I need help with.

Since context is king, allow me to elaborate.

Requirements: A button that takes an image from the user's clipboard and attaches it as a file to the record. The idea being, when creating help requests, the user can take a screenshot of the problem in question and easily attach it to the record.

Solution: An LWC that can be placed on the page of any record that consists of a button. When a user clicks the button, the ClipboardAPI is used to grab the contents of the user's clipboard. If the contents are an image blob, the blob is sent to the Apex method which inserts a ContentVersion object, setting the VersionData field to the image blob.

While I am a competent salesforce dev, some of the concepts and techniques here are a little outside my comfort zone. Blobs for instance. I know a blob is a data type that represents binary data, and I understand that in the case of an image this blob is the data required to build the image itself. Beyond that I am lost and don't know why the javascript "version" of the blob I get from the ClipboardAPI is different from the Apex "version" of a blob that I am attempting to use to create the file.

I have attempted many different ways to make this work and one that seemed promising got me all the way to the finish line where I immediately fell flat on my face. Javascript has a method (URL.createObjectURL(blob)) to convert the blob into a url that looks like this: "blob:https://{sandboxname}sandbox.lightning.force.com/290bebe0-94cd-48ca-9246-632542d153b8" (where the {sandboxname} is the sandbox name). Unfortunately this URL is good enough to drop into the src of an image tag that can act as a UI/UX confirmation that the right image got pasted from the clipboard, but it is useless as far as creating an image to attach to the record because it returns a 404 error if I ever try to use it. It appears to be extremely short lived. I've noted that when you paste an image into a rich text field the content of the field is an image tag whose src is something like this: "https://{sandboxname}.sandbox.file.force.com/servlet/rtaImage?eid=a2l19000002sDCZ&feoid=00NC0000005Qt57&refid=0EM1900000031lV". If I pass this into the Apex method, I have a way of using it to get the blob and everything works. Unfortunately, I know of no way to get that particular style of url from the js blob.

Passing the js blob as a string doesn't work either. It seems like the blob I get from the clipboard is incompatible to the blob I need to create a ContentVersion record.

Any help with this blob situation would be great. However, I do ask that you don't respond with the first google search result you find, as chances are, I've already read it. ;)

Code below:

js controller:

import { LightningElement,api } from 'lwc';    
async function getClipboardContents() {
    try {
      const clipboardItems = await navigator.clipboard.read();
      for (const clipboardItem of clipboardItems) {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      }
    } catch (err) {
      console.error(err.name, err.message);
    }
}
export default class PasteFromClipboard extends LightningElement {
    @api recordId;
    showSpinner = false;
    imgSrc='';
    handlePasteClick(e){
        this.showSpinner = true;
        getClipboardContents()
        .then(blob=>{
            const apexParams = {
                parentId:this.recordId,
                fileName:'att',
                b:blob,
                contentType:'image/png'
            };
            let res = saveTheFile(apexParams);
            this.showSpinner = false;
        });
    }    
}

APEX Controller:

public with sharing class PasteFromClipboardController {
    public PasteFromClipboardController() {

    }
    @AuraEnabled
    public static string saveTheFile(Id parentId, String fileName, Blob b, String contentType) {
        try{            
            Datetime today = Datetime.now();
            fileName=fileName+' '+today.time()+'.png';
            ContentVersion contentVersionRec = new ContentVersion();  
            contentVersionRec.Title = fileName;  
            contentVersionRec.PathOnClient = '/' + fileName;  
            contentVersionRec.FirstPublishLocationId = parentId;  
            contentVersionRec.VersionData = b;  
            contentVersionRec.IsMajorVersion = true;  
            Insert contentVersionRec; 
            return contentVersionRec.Id;
        }
        catch(exception e){
            return e.getMessage();
        }
    }
}
1
  • You need to convert the JavaScript blob to a base64-encoded string, as Apex can handle base64 strings effectively. The URL created by URL.createObjectURL(blob) is not the actual binary data of the image, but rather a URL reference to the data stored in the browser, which is why it's not useful for your Apex method.
    – aryashah2k
    Commented Jan 17 at 15:44

1 Answer 1

3

To fix your issue with attaching images in Salesforce using LWC, you'll need to tweak how the image data is handled. Start by converting the image (which you got from the ClipboardAPI) into a base64 string right in your LWC's JavaScript. You can do this with a FileReader – it's a bit of JavaScript magic that turns the blob into a base64 string. Once you've got that string, send it over to your Apex code. In Apex, you need to change your method to take in this base64 string instead of the usual Blob. Then use Apex's tools (like EncodingUtil.base64Decode) to turn that string back into a Blob. This Blob can then be used to create your ContentVersion record, which means your image gets attached to the Salesforce record without a hitch. It's a bit like translating between two languages to make sure both sides understand each other

JS Function

async function getClipboardContents() {
    try {
        const clipboardItems = await navigator.clipboard.read();
        for (const clipboardItem of clipboardItems) {
            for (const type of clipboardItem.types) {
                const blob = await clipboardItem.getType(type);
                return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onloadend = () => resolve(reader.result); // This is base64 string
                    reader.onerror = reject;
                    reader.readAsDataURL(blob);
                });
            }
        }
    } catch (err) {
        console.error(err.name, err.message);
    }
}

Apex Controller:

public with sharing class PasteFromClipboardController {

    @AuraEnabled
    public static String saveTheFile(Id parentId, String fileName, String base64Data, String contentType) {
        try {
            // Decoding the base64 string to a Blob
            Blob decodedBlob = EncodingUtil.base64Decode(base64Data.substring(base64Data.indexOf(',') + 1));

            // Setting the filename with the current timestamp to make it unique
            Datetime now = Datetime.now();
            String formattedFileName = fileName + ' ' + now.format('yyyy-MM-dd_HH-mm-ss') + '.png';

            // Creating a new ContentVersion record
            ContentVersion contentVersionRec = new ContentVersion();
            contentVersionRec.Title = formattedFileName;
            contentVersionRec.PathOnClient = '/' + formattedFileName;
            contentVersionRec.FirstPublishLocationId = parentId;
            contentVersionRec.VersionData = decodedBlob;
            contentVersionRec.IsMajorVersion = true;

            // Inserting the ContentVersion record
            insert contentVersionRec;

            return contentVersionRec.Id;
        } catch (Exception e) {
            // Handling exceptions and returning the error message
            System.debug('Error in saveTheFile: ' + e.getMessage());
            return 'Error: ' + e.getMessage();
        }
    }
}

I have taken a first pass to modify the code as per the solution stated above in my answer.

3
  • I'll try that, thanks. Commented Jan 17 at 16:14
  • That worked! Thank you so much! Gotta learn more about blobs now... Commented Jan 17 at 16:36
  • Glad I could help with this!
    – aryashah2k
    Commented Jan 17 at 16:51

You must log in to answer this question.

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