Available Now: Explore our latest release with enhanced accessibility and powerful IDP features
By Thomas Winter | 2021 Feb 08
9 min
Tags
tutorial
salesforce
form
Many Salesforce users would like an automated flow to populate fields in document templates with Salesforce data — whether to send auto-filled letters to customers or quickly sign their internal documents, like contracts.
In an earlier post, we showed an easy way to embed features to view, edit, and annotate a Salesforce record attachment in a Salesforce app. This post considers how to use the same technologies to add and programmatically fill form fields as part of an automated workflow.
We'll use the same Salesforce attachment component featured in our previous guide, along with the powerful WebViewer API. On the WebViewer side, we'll follow this example of creating text fields and text widget annotations using JavaScript. Let’s dive in!
Streamline your Salesforce workflows with Apryse WebViewer. Enhance security and simplify development.
To get started with WebViewer and Salesforce:
We recommend using Salesforce DX when deploying this app to your scratch org or sandbox. For quick intro tutorials, those new to the tool can visit the Quick Start: Salesforce DX Trailhead Project or the App Development with Salesforce DX Trailhead modules.
Setup steps include:
Now you need to optimize Apryse WebViewer code for Salesforce. Extract the WebViewer.zip
you downloaded earlier into a folder and run this npm script:
$ npm run optimize
You will encounter the following prompts. Answer y/n as indicated:
Optimize: Do you want us to backup your files before optimizing? [y/n]: y
Optimize: Will you be using WebViewer Server? ... [y/n]: n
Optimize: Will you be converting all your documents to XOD? ... [y/n]: n
Optimize: Do you need client side office support? [y/n]: y
Optimize: Do you need the full PDF API? ... [y/n]: y
Optimize: Do you want to use the production version of PDFNet.js? ... [y/n]: n
Optimize: Do you need to deploy to Salesforce? ... [y/n]: y
Note: Make sure you answer this prompt with n
:
Optimize: Will you be converting all your documents to XOD? ... [y/n]: n
After answering y
to “Do you need to deploy to Salesforce?”, the script produces .zip
files no more than 5 MB in size, allowing you to upload them as static resources.
Next, you need to clone our sample salesforce-webviewer-prepopulate
project. To configure the sample and get it running, follow these steps:
1. Clone the webviewer-salesforce
GitHub repo:
git clone git@github.com:PDFTron/salesforce-webviewer-prepopulate.git
cd salesforce-webviewer-prepopulate
2. Copy all the zip files generated after running the npm optimizing script from the output folder webviewer-salesforce
into the force-app/main/default/staticresources
folder of your newly cloned project.
The files you will need to copy from the “webviewer-salesforce” directory.
3. You can add your WebViewer license key in staticresources/myfiles/config_apex.js
file or add it in your WebViewer constructor by passing l: "LICENSE_KEY"
option.
WebViewer({
l: ‘LICENSE_KEY’
})
4. If you haven’t done so, authenticate your org and provide it an alias (DevHub in the command below) from your terminal (macOS) or cmd (Windows). Execute the following command as is.
sfdx force:auth:web:login --setalias [your-alias] [--instanceurl https://test.salesforce.com for sandboxes]
Alternatively, you can authenticate from VS Code:
5. Enter your org credentials in the browser that opens. Type dev hub
in the quick find search and toggle to enable
as shown in the picture below.
6. Create a scratch org using the config/project-scratch-def.json
file, set the username as your default, and assign it an alias by replacing my-scratch-org
with your own alias name. Alternatively, you can also deploy to your sandbox and skip this step.
sfdx force:org:create --setdefaultusername -f config/project-scratch-def.json --setalias my-scratch-org
7. Push the app to your org:
sfdx force:source:push -f [-a your-alias if default not specified]
Or right click + Deploy Source to Org.
8. Open the org:
sfdx force:org:open [-a your-alias]
9. Navigate to the Object you would like to use for editing your record files. Click on the gear wheel and select ‘Edit Page’ to open the Lightning App Builder.
Now select where to launch the Lightning Web Component from and drag and drop the pdftronWebviewerContainer
component there.
You can use the Sample PDF provided in the /staticresources/
folder of the repository for testing this sample. After adding your pdftronWebviewerContainer
to a record page, upload the attach the sample PDF file to your records Notes & Attachments
or Files
.
Once your document opens in WebViewer inside Salesforce, you can then use JavaScript to draw form fields over top and populate these fields with data! You can hook into the documentLoaded
event or execute code based on your custom events.
To pass Salesforce data to WebViewer, leverage our previously created ContentVersionWrapper and include any needed object data like so:
//ContentVersionWrapper.cls - used as inner class
public class ContentVersionWrapper {
@AuraEnabled
public String name {get; set;}
@AuraEnabled
public String body {get; set;}
@AuraEnabled
public Account acc {get; set;}
public ContentVersionWrapper(ContentVersion contentVer, Account acc) {
this.name = contentVer.Title + '.' + contentVer.FileExtension;
this.body = EncodingUtil.base64Encode(contentVer.VersionData);
this.acc = acc;
}
}
Once you have your wrapper class set up and ready to go, query for ContentVersion data and your sObject
data in the same method and pass them to your wrapper. In our sample, we grab Account record data from the current record like so:
// PDFTron_ContentVersionController.cls - snipped for brevity
@AuraEnabled(Cacheable=true)
public static List<ContentVersionWrapper> getAttachments(String recordId){
try {
List<String> cdIdList = new List<String> ();
List<ContentVersionWrapper> cvwList = new List<ContentVersionWrapper> ();
// Query for account data to be included on PDF document
Account acc = [ SELECT Name, ShippingAddress, SLAExpirationDate__c, Phone FROM Account WHERE Id = :recordId ];
// Find links between record & document
for(ContentDocumentLink cdl :
[ SELECT id, ContentDocumentId, ContentDocument.LatestPublishedVersionId
FROM ContentDocumentLink
WHERE LinkedEntityId = :recordId ]) {
cdIdList.add(cdl.ContentDocumentId);
}
// Use links to get attachments
for(ContentVersion cv :
[ SELECT Id, Title,FileExtension, VersionData
FROM ContentVersion
WHERE ContentDocumentId IN :cdIdList
AND IsLatest = true ]) {
if(fileFormats.contains(cv.FileExtension.toLowerCase())) {
// pass account data to ContentVersionWrapper
cvwList.add(new ContentVersionWrapper(cv, acc));
}
}
return cvwList;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
You can now modify the payload passed to WebViewer and include your account data:
// pdftronWvInstance.js - snipped for brevity
handleBlobSelected(record) {
record = JSON.parse(record);
const blob = new Blob([_base64ToArrayBuffer(record.body)], {
type: mimeTypes[record.FileExtension]
});
const payload = {
blob: blob,
extension: record.cv.FileExtension,
filename: record.cv.Title + "." + record.cv.FileExtension,
documentId: record.cv.Id,
account: record.acc // account data added to payload here
};
this.iframeWindow.postMessage({type: 'OPEN_DOCUMENT_BLOB', payload} , '*');
}
Finally, in your config_apex.js
file, you can now process the Account data. In our sample, we are using a for … of
loop to iterate through Object.entries(account)
to bulkify this sample for our Salesforce context. Next, we are using Annotations.WidgetFlags
to determine which fields are required or multi-line. As we iterate through account record fields, we dynamically create WebViewer’s counterpart Annotations.Forms.Field
for each form field we want included. In the same iteration, we use the WebViewer field to create a Annotations.WidgetAnnotation
and dynamically place it on our form.
8.0JS:
// config_apex.js
function receiveMessage(event) {
if (event.isTrusted && typeof event.data === 'object') {
switch (event.data.type) {
case 'OPEN_DOCUMENT_BLOB':
const { blob, extension, filename, documentId, account } = event.data.payload;
instance.loadDocument(blob, { extension, filename, documentId });
const docViewer = instance.Core.documentViewer;
const annotManager = docViewer.getAnnotationManager();
// standard text flag
const flags = new Annotations.WidgetFlags();
flags.set('Multiline', false);
flags.set('Required', false);
// multiline address flag
const addressFlags = new Annotations.WidgetFlags();
flags.set('Multiline', true);
flags.set('Required', false);
let yVal = 142;
let widgets = [];
for (const [key, val] of Object.entries(account)) {
let field = {};
if (key.toLowerCase().includes('id')) {
continue;
} else if(key.toLowerCase().includes('address')) {
field = new Annotations.Forms.Field(key, {
type: 'Tx',
value: val.street,
addressFlags,
});
} else {
field = new Annotations.Forms.Field(key, {
type: 'Tx',
value: val,
flags,
});
}
// create a widget annotation
const widgetAnnot = new Annotations.TextWidgetAnnotation(field);
// Add customization here
// "Annotations" can be directly accessed since we're inside the iframe
widgetAnnot.PageNumber = 1;
widgetAnnot.X = 150;
widgetAnnot.Y = yVal;
widgetAnnot.Width = 400;
widgetAnnot.Height = 30;
//add the form field and widget annotation
annotManager.addAnnotation(widgetAnnot);
widgets.push(widgetAnnot);
annotManager.getFieldManager().addField(field);
yVal += 60;
}
annotManager.drawAnnotationsFromList(widgets);
break;
default:
break;
}
}
}
6.0JS:
// config_apex.js
function receiveMessage(event) {
if (event.isTrusted && typeof event.data === 'object') {
switch (event.data.type) {
case 'OPEN_DOCUMENT_BLOB':
const { blob, extension, filename, documentId, account } = event.data.payload;
event.target.readerControl.loadDocument(blob, { extension, filename, documentId });
const docViewer = readerControl.docViewer;
const annotManager = docViewer.getAnnotationManager();
// standard text flag
const flags = new Annotations.WidgetFlags();
flags.set('Multiline', false);
flags.set('Required', false);
// multiline address flag
const addressFlags = new Annotations.WidgetFlags();
flags.set('Multiline', true);
flags.set('Required', false);
let yVal = 142;
let widgets = [];
for (const [key, val] of Object.entries(account)) {
let field = {};
if (key.toLowerCase().includes('id')) {
continue;
} else if(key.toLowerCase().includes('address')) {
field = new Annotations.Forms.Field(key, {
type: 'Tx',
value: val.street,
addressFlags,
});
} else {
field = new Annotations.Forms.Field(key, {
type: 'Tx',
value: val,
flags,
});
}
// create a widget annotation
const widgetAnnot = new Annotations.TextWidgetAnnotation(field);
// Add customization here
// "Annotations" can be directly accessed since we're inside the iframe
widgetAnnot.PageNumber = 1;
widgetAnnot.X = 150;
widgetAnnot.Y = yVal;
widgetAnnot.Width = 400;
widgetAnnot.Height = 30;
//add the form field and widget annotation
annotManager.addAnnotation(widgetAnnot);
widgets.push(widgetAnnot);
annotManager.getFieldManager().addField(field);
yVal += 60;
}
annotManager.drawAnnotationsFromList(widgets);
break;
default:
break;
}
}
}
This approach works well for creating new fields and filling them. When updating existing fields with a value, you can also call annotManager.getFieldManager().addField(field);
which either creates a new field or updates an existing field by name.
To better understand how you can interact with the WebViewer iframe, you can read documentation about config.js files here.
Check out some of our previous guides to learn more about WebViewer use cases in Salesforce.
You can review this segment to learn more about file-size limitations.
Optimizing the original WebViewer source code for the Salesforce platform means that we will also have to set a few paths in config.js
. Get more details in our Salesforce as a Lightning Web Component blog.
For more on what you can do with Salesforce and WebViewer, see our Salesforce documentation section or watch our previously recorded webinar covering integrating WebViewer into Salesforce. We’ll also be adding another article in our WebViewer and Salesforce series soon—how to draw fields on a document via the UI. So stay tuned to our blog for more!
If you have any questions about this guide or our Salesforce-specific build of WebViewer, please feel free to get in touch, and we will be happy to help!
Tags
tutorial
salesforce
form
Thomas Winter
Related Products
Share this post
PRODUCTS
Enterprise
Small Business
Popular Content