Access Document Scanners in Salesforce's Lightning Web Component
In this article, we are going to create a lightning web component to access document scanners in Salesforce, the famous CRM system. Adding extra features to your Salesforce system can provide an integrated user experience.
The document scanner feature will be provided by the Dynamic Web TWAIN SDK. It provides a JavaScript library which interacts with document scanners via a local service.
Demo video:
Environment Setup
- Install Salesforce CLI.
- Install Visual Studio Code and extensions for Salesforce DX.
You can find the detailed guide here.
New Salesforce DX Project
Open Visual Studio Code, open the Command Palette by pressing Ctrl+Shift+P (Windows) or Cmd+Shift+P (macOS) and input SFDX to select the Create Project
operation.
Here, we use the standard option and use webTWAIN
as the name.
Then, run SFDX: Create Lightning Web Component
to create a component named webTWAIN
.
Use the Component in Salesforce
- Run
SFDX: Authorize an Org
to log in to your Salesforce org. -
Edit
webTWAIN.js-meta.xml
to make the following changes to make it available in lightning app builder.<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>59.0</apiVersion> - <isExposed>false</isExposed> + <isExposed>true</isExposed> + <targets> + <target>lightning__AppPage</target> + <target>lightning__RecordPage</target> + <target>lightning__HomePage</target> + </targets> </LightningComponentBundle>
-
Deploy the component to the org.
-
In Salesforce, edit one page and you can add the component to the page to test it.
Implement the Component
Next, let’s implement the component.
Add Static Resources
- Download the static resource of Dynamic Web TWAIN 18.5 for salesforce here and unzip it to
force-app-dwt\force-app\main\default\staticresources
-
Create a metadata file named
dwt.resource-meta.xml
in thestaticresources
folder with the following content to describe the resource.<?xml version="1.0" encoding="UTF-8"?> <StaticResource xmlns="http://soap.sforce.com/2006/04/metadata"> <cacheControl>Private</cacheControl> <contentType>application/zip</contentType> </StaticResource>
Load Static Resources
In Lightning Web Component, we can load third-party libraries stored as static resources using the loadScript
and loadStyle
methods.
In connectedCallback
when the component is inserted to DOM, load the library of Dynamic Web TWAIN.
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import dwt from '@salesforce/resourceUrl/dwt';
connectedCallback() {
loadScript(this, dwt + '/dynamsoft.webtwain.config.js')
.then(() => {
Promise.all([
loadScript(this, dwt + '/dynamsoft.webtwain.initiate.js'),
loadStyle(this, dwt + '/src/dynamsoft.webtwain.css'),
loadStyle(this, dwt + '/src/dynamsoft.webtwain.viewer.css'),
])
.then(() => {
this.createDWT();
});
})
.catch(error => {
this.showMessage("loading dwt js", error.message);
}
);
}
Add Trusted URLs
We need to add the following URLs as trusted URLs so that the app can communicate with a local service to scan documents.
Use Dynamic Web TWAIN to Access Document Scanners
-
Open
webTWAIN.html
and add the following conent:<template> <lightning-card title="Web TWAIN"> <div class="slds-m-around_medium"> <lightning-select name="devices" label="Scanners: " value={curScannerIndex} options={scannerOptions} onchange={handleDeviceChange} ></lightning-select> <lightning-button label="acquire" onclick={acquire} ></lightning-button> <lightning-button label="rotate" onclick={rotate} ></lightning-button> <lightning-button label="delete" onclick={deleteImage} ></lightning-button> <lightning-button label="showEditor" onclick={showEditor} ></lightning-button> <lightning-button label="save" onclick={save} ></lightning-button> <div id = "dwt" class="dwtContainer" lwc:dom="manual" ></div> </div> </lightning-card> </template>
It adds a scanner selection element, several buttons and a container to display the scanned documents.
-
After the library is successfully loaded, create an instance of Dynamic Web TWAIN. Several properties are set here for the Salesforce environment. You also need a trial license. You can apply for one here.
dwtWidth = 320; dwtHeight = 480; async createDWT() { if (this.DWTObject) { this.DWTObject = undefined; Dynamsoft.DWT.DeleteDWTObject('dwtObj'); } try{ const pThis = this; Dynamsoft.DWT.ResourcesPath = dwt; Dynamsoft.DWT.ServiceInstallerLocation = "https://demo.dynamsoft.com/DWT/Resources/dist/"; //request your trial license here: https://www.dynamsoft.com/customer/license/trialLicense/?product=dwt Dynamsoft.DWT.ProductKey = "YOUR LICENSE"; const container = pThis.template.querySelector('div.dwtContainer'); Dynamsoft.DWT.RootNode = container; Dynamsoft.DWT.AutoLoadCSS = false; Dynamsoft.DWT.CreateDWTObjectEx({WebTwainId: 'dwtObj', container: container}, function (obj) { pThis.DWTObject = obj; //instance of Dynamic Web TWAIN const succeed = pThis.DWTObject.Viewer.bind(container); pThis.DWTObject.Viewer.width = pThis.dwtWidth; pThis.DWTObject.Viewer.height = pThis.dwtHeight; pThis.DWTObject.Viewer.show(); }, function(error) { console.log(error.message);}); }catch(error) { this.showMessage("creating dwt object", error.message); } }
-
List connected scanner devices.
curScannerIndex = -1; @api scannerOptions = []; listDevices() { const pThis = this; this.DWTObject.GetDevicesAsync().then(devices=> { pThis.curScannerIndex = -1; pThis.devices = devices; let options = []; for (let index = 0; index < pThis.devices.length; index ++) { options.push({label: pThis.devices[index].displayName, value: index}); } pThis.scannerOptions = options; // to select a default device if (pThis.devices && pThis.devices.length != 0) { pThis.curScannerIndex = 0; } }); } handleDeviceChange(event) { this.curScannerIndex = event.detail.value; }
-
Scan documents using the selected scanner.
async acquire() { try { if (this.curScannerIndex >= 0 && this.devices) { await this.DWTObject.SelectDeviceAsync(this.devices[this.curScannerIndex]); } // await this.DWTObject.SelectSourceAsync(); // not support, ui has some problems await this.DWTObject.AcquireImageAsync({Resolution: 200, PixelType: 2, ShowUI: true}); }catch(error) { this.showMessage('acquiring', error.message); } }
-
Rotate the selected document image.
async rotate() { try { await this.DWTObject.RotateAsync(this.DWTObject.CurrentImageIndexInBuffer, 90, true); } catch(error) { this.showMessage('rotating', error.message); } }
-
Open a built-in image editor.
showEditor() { const pThis = this; try { const editorSettings = { /* Show the editor within the DIV 'imageEditor'*/ element: this.template.querySelector('div.dwtContainer'), workMode: Dynamsoft.DWT.EnumDWT_WorkMode.balance, width: this.dwtWidth, height: this.dwtHeight } if (!this.imageEditor) { this.imageEditor = this.DWTObject.Viewer.createImageEditor(editorSettings); this.DWTObject.RegisterEvent('CloseImageEditorUI', function(){ pThis.DWTObject.Viewer.show(); pThis.imageEditor = undefined; // closed already, need recreate again }); } this.DWTObject.Viewer.hide(); this.imageEditor.show(); } catch(error) { this.showMessage('showing editor', error.message); } }
-
Save all the images as a PDF file.
save() { const pThis = this; try { this.DWTObject.SaveAllAsPDF("output.pdf", function(){ }, function(errorCode, errorString){ pThis.showMessage('saving', errorString); }); } catch(error) { this.showMessage('saving', error.message); } }
All right, we’ve now completed the component.
Source Code
Check out the source code to have a try.
https://github.com/tony-xlh/Dynamic-Web-TWAIN-samples/tree/main/Salesforce/webTWAIN
References
- https://developer.salesforce.com/docs/component-library/overview/components
- https://developer.salesforce.com/docs/platform/lwc/guide/reference-lightning-ui-api-record.html
- https://trailhead.salesforce.com/content/learn/projects/quick-start-lightning-web-components/create-a-hello-world-lightning-web-component
- Salesforce CRM Administration Handbook
- Ultimate Salesforce LWC Developers’ Handbook