2

Long-time answerer, first-time questioner...

I am trying to fill out a pdf in LWC using a third-party library, PDF-Lib. There is a great example of how to create a pdf with this library that gets me most of the way there, Create Custom PDF in LWC, but when I flip to filling out a pdf form, I have an issue I can't figure out!

Issue: How do I load a pdf static resource to use in PDF-Lib. Here is my code:

    import { LightningElement, api } from "lwc";
    import PDFLib from "@salesforce/resourceUrl/pdflib";
    import bol from "@salesforce/resourceUrl/fflBOL";
    import { loadScript } from "lightning/platformResourceLoader";
    
    export default class BtnGenerateBOL extends LightningElement {
        isExecuting = false;
    
        @api async invoke() {
            if (this.isExecuting) {
                return;
            }
    
            this.isExecuting = true;
            await this.fillBOL();
            this.isExecuting = false;
        }
    
        renderedCallback() {
            loadScript(this, PDFLib).then(() => {});
        }
    
        async fillBOL() {
            //
            // ISSUE HERE:  How do I appropriately get the pdf so PDFLib will load it!!
            //
            const pdfResource = bol;  //this doesn't work (error below)
            const pdfDoc = await PDFLib.PDFDocument.load(pdfResource);
            const form = pdfDoc.getForm();
    
            const shipFromName = form.get("Ship_From_Name");
    
            shipFromName.setText("This works");
    
            form.flatten();
    
            const pdfBytes = await pdfDoc.save();
    
            this.saveByteArray("TestPDF", pdfBytes);
        }
    
        saveByteArray(pdfName, byte) {
            var blob = new BlobEvent([byte], { type: "application/pdf" });
            var link = document.createElement("a");
            var fileName = pdfName;
            link.href = window.URL.createObjectURL(blob);
            link.download = fileName;
            link.click();
        }
    }

From PDF-Lib's documentation for load on PDFDocument, I have three choices. I have tried some variations of all three, and given the error, I don't think I am even getting the static resource.

Static load
▸ load(pdf: string | Uint8Array | ArrayBuffer, options: LoadOptions): Promise‹PDFDocument‹››


Load an existing PDFDocument. The input data can be provided in multiple formats:

Type    Contents
string  A base64 encoded string (or data URI) containing a PDF
Uint8Array  The raw bytes of a PDF
ArrayBuffer The raw bytes of a PDF

Thank you in advance!


Error:

btnGenerateBOL.js:44 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'load')
at BtnGenerateBOL.fillBOL (btnGenerateBOL.js:44:69)
at BtnGenerateBOL.invoke (btnGenerateBOL.js:30:20)
at callHook (aura_proddebug.js:24991:35)
at HTMLBridgeElement.<anonymous> (aura_proddebug.js:9461:14)
at HeadlessActionRender.handleClick (executorLwcHeadless.js:62:26)
at callHook (aura_proddebug.js:12161:15)
at aura_proddebug.js:12021:7
at runWithBoundaryProtection (aura_proddebug.js:12839:7)
at invokeEventListener (aura_proddebug.js:12015:5)
at aura_proddebug.js:11240:7
4
  • Can you please put console.log in .then brackets of loadscript, to confirm if loading was successful or not. Plus, please confirm if static resource contains folders inside folders, and also if static resource visibility is not private.
    – user43598
    Mar 1, 2022 at 21:16
  • Also, try loading the script as Promise.all([ loadScript(this, PDFlib) ]);
    – user43598
    Mar 1, 2022 at 21:17
  • @user44598 Thanks for the help, the js library (script) was loading fine, the issue was that the PDF was not loading correctly. I will post the solution in case it helps someone later. Again, thank you and sorry for the late reply... life got in the way Mar 15, 2022 at 15:05
  • great to hear that your problem is resolved. Would love to know the solution.
    – user43598
    Mar 16, 2022 at 1:06

2 Answers 2

2

So as my question said, I had diagnosed that LWC was not loading the PDF Static Resource. The Static Resource was appropriately created (not a folder issue). PDF-Lib was loading (and it is really a great library with AWESOME Documentation).

So, what I did (with a friend's help, thank you, Max) was utilize XMLHttpRequest. We could not get fetch to work. period. So here is the code that works!

import { LightningElement, api } from "lwc";
import PDFLib from "@salesforce/resourceUrl/pdflib";
import BOL from "@salesforce/resourceUrl/fflBOL";
import { loadScript } from "lightning/platformResourceLoader";
import getBillOfLading from "@salesforce/apex/BillOfLadingController.getBillOfLading";

export default class BtnGenerateBOL extends LightningElement {
    isExecuting = false;
    fieldMap = new Map();

    @api recordId;

    @api async invoke() {
        const thisLwc = this;
        var xhr = new XMLHttpRequest();

        if (this.isExecuting) {
            return;
        }
        this.isExecuting = true;
        await loadScript(this, PDFLib);

        xhr.responseType = "blob";
        xhr.onreadystatechange = function () {
            if (this.response !== null)
                thisLwc.processPdf(thisLwc, this.response);
        };
        xhr.open("GET", BOL);
        xhr.send();

        this.isExecuting = false;
    }

    async processPdf(thisLwc, res) {
        const arrayBuf = await res.arrayBuffer();
        const pdfDoc = await window.PDFLib.PDFDocument.load(arrayBuf);
        
        //await get data for Bill of Lading. <await is important here>
        //
       
        const form = pdfDoc.getForm();
        const fields = form.getFields();

        // fill out fields 
        this.fillForm(<fieldName>, <value>);
        // do that lots of times

        
        //get rid of form and make it just a filled-out pdf
        form.flatten();

        //set title which is shown on viewers
        pdfDoc.setTitle("BOL: " + <appropriate title>);

        //save the document and download
        const pdfBytes = await pdfDoc.save();
        this.saveByteArray(title, pdfBytes);
    }


//right now this form has text fields and checkboxes. 
fillField(fieldName, value) {
    var field = this.fieldMap.get(fieldName);
    console.log(`${fieldName}: ${value}`);
    if (value != null && field != null) {
        if (field.constructor.name === "PDFTextField") {
            field.setText(value.toString());
        } else {
            if (value === true) {
                field.check();
            } else {
                field.uncheck();
            }
        }
    }
}
saveByteArray(pdfName, byte) {
    var blob = new Blob([byte], { type: "application/pdf" });
    var link = document.createElement("a");
    var fileName = pdfName;
    link.href = window.URL.createObjectURL(blob);
    link.download = fileName;
    link.click();
}

}

And that is it.

Again the answer to the question is in the async invoke(). LWC doesn't handle the load, you have to make a HttpRequest to get the pdf form template.

1
  • Awesome Work! I would like to see your HTML template for completeness, or a skinny version appropriate for the snippet shown above. Is your invoke fn a button-handler?
    – bkwdesign
    Sep 2, 2022 at 20:31
0

This is just an addendum to the accepted answer if you want to use fetch syntax:

import { LightningElement, api } from 'lwc';
import pdflib   from '@salesforce/resourceUrl/pdf_lib';//for PDF support
import myStaticResource from '@salesforce/resourceUrl/zippedPdfTemplates';
import { loadScript } from "lightning/platformResourceLoader";//load PDF script

export default class MyPdfEditorLWC extends LightningElement {
    librariesLoaded = false;

    page1template_arraybuffer = null;
    page2template_arraybuffer = null;

   renderedCallback() {
        if (this.librariesLoaded) return;
        this.librariesLoaded = true;
        loadScript(this, pdflib)
            .then(async () => {
                console.log("success------");
            })
            .catch(error => {
                console.log("failure--------", error);
            });    
         
    
        this.handleFetch("MyTemplatePg1.pdf").then((pdfBuffer) => {
            mylwc.page1template_arraybuffer = pdfBuffer;
        });
        this.handleFetch("MyTemplatePg2.pdf").then((pdfBuffer) => {
            mylwc.page2template_arraybuffer = pdfBuffer;
        });

    }

    async handleFetch(fileName) {
        let endPoint = myStaticResource + "/" + fileName;

        const headers = [
            ['Accept', 'application/octet-stream']
          ];
        const response = await fetch(endPoint, { headers });
        let blob = await response.blob();
        let buff = await blob.arrayBuffer();
        return buff;
    }
 async showPage1() {
        const pg1 = this.page1template_arraybuffer;
        const pdfDoc = await PDFLib.PDFDocument.load(pg1);
       
        const form = pdfDoc.getForm();
        
        //get rid of form and make it just a filled-out pdf
        form.flatten();

        //set title which is shown on viewers
        pdfDoc.setTitle("Page 1");

        //save the document 
        const pdfBytes = await pdfDoc.save();

        //and download
        this.saveByteArray("Manifest Page 1", pdfBytes);

    }

You must log in to answer this question.

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