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.

new project

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

  1. Run SFDX: Authorize an Org to log in to your Salesforce org.
  2. 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>
    
  3. Deploy the component to the org.

    deploy

  4. In Salesforce, edit one page and you can add the component to the page to test it.

    settings page

    edit page

Implement the Component

Next, let’s implement the component.

Add Static Resources

  1. 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
  2. Create a metadata file named dwt.resource-meta.xml in the staticresources 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.

trusted urls

Use Dynamic Web TWAIN to Access Document Scanners

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

  2. 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);
        }
    }
    
  3. 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;
    }
    
  4. 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);
        }
    }
    
  5. Rotate the selected document image.

    async rotate() {
        try {
            await this.DWTObject.RotateAsync(this.DWTObject.CurrentImageIndexInBuffer, 90, true);
        }
        catch(error)
        {
            this.showMessage('rotating', error.message);
        }
    }
    
  6. 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);
        }
    }
    
  7. 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