How to Build a Document Scanning Component with LitElement
Tags: webdev, javascript, tutorial
Published: 2023-11-06 07:33:20

Lit is a simple library for building fast, lightweight web components. At Lit's core is a boilerplate-killing component base class that provides reactive state, scoped styles, and a declarative template system that's tiny, fast and expressive.

In this article, we are going to build a web component with LitElement to scan documents in an HTML website based on [Dynamic Web TWAIN](https://www.dynamsoft.com/web-twain/overview/).

## New Project

Although Lit can be used without a build system, in this article, we are going to use webpack with the following template (clone with git):

```bash
git clone https://github.com/wbkd/webpack-starter
``` 

## Install Dependencies

1. Install Lit.

   ```bash
   npm install lit
   ```


2. Install Dynamic Web TWAIN.

   ```bash
   npm install dwt
   ```
   
   In addition, we need to copy the resources of Dynamic Web TWAIN to the public folder.
   
   1. Install `ncp`.
   
      ```bash
      npm install --save-dev ncp
      ```
      
   2. Modify `package.json` to copy the resources for the build and start commands.
   
      ```diff
       "scripts": {
         "lint": "npm run lint:styles; npm run lint:scripts",
         "lint:styles": "stylelint src",
         "lint:scripts": "eslint src",
         "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.config.prod.js",
         "start": "webpack serve --config webpack/webpack.config.dev.js"
      +  "build": "ncp node_modules/dwt/dist public/dwt-resources && cross-env NODE_ENV=production webpack --config webpack/webpack.config.prod.js",
      +  "start": "ncp node_modules/dwt/dist public/dwt-resources && webpack serve --config webpack/webpack.config.dev.js"
       },
      ```
      
   3. Modify `webpack.common.js` to copy the files in the `public` folder to the output folder instead of the `public` folder inside the output folder.
   
      ```diff
       new CopyWebpackPlugin({
      -  patterns: [{ from: Path.resolve(__dirname, '../public'), to: 'public' }],
      +  patterns: [{ from: Path.resolve(__dirname, '../public'), to: '' }],
       }),
      ```

## Write a Document Scanner Component

1. Create a new `documentscanner.js` file under `src\scripts` with the following template.

   ```js
   import {LitElement, html, css} from 'lit';

   export class DocumentScanner extends LitElement {
     static properties = {
     };
     DWObject;
     static styles = css`
       :host {
         display: block;
       }
       `;
     constructor() {
       super();
     }

     render() {
       return html``;
     }
   }
   customElements.define('document-scanner', DocumentScanner);
   ```

2. Add a `div` element as the container for the controls of Dynamic Web TWAIN (mainly to view documents).

   ```js
   render() {
     return html`<div id="dwtcontrolContainer"></div>`;
   }
   ```
 
3. Configure Dynamic Web TWAIN in the `constructor`. You need a license to use Dynamic Web TWAIN. You can apply for a license [here](https://www.dynamsoft.com/customer/license/trialLicense?product=dwt).

   ```js
   constructor() {
     super();
     Dynamsoft.DWT.AutoLoad = false;
     Dynamsoft.DWT.ResourcesPath = "/dwt/dist";
     Dynamsoft.DWT.ProductKey = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day trial
   }
   ```
 
4. Initialize Dynamic Web TWAIN in the `firstUpdated` lifecycle after the dom is first updated and bind it to the container in the previous step. In addition, dispatch a custom event with the object of Dynamic Web TWAIN so that we can control it in a parent node.

   ```js
   DWObject;
   firstUpdated() {
     let pThis = this;
     let dwtContainer = this.renderRoot.getElementById("dwtcontrolContainer");
     Dynamsoft.DWT.CreateDWTObjectEx(
       {
         WebTwainId: 'dwtcontrol'
       },
       function(obj) {
         pThis.DWObject = obj;
         pThis.DWObject.Viewer.bind(dwtContainer);
         pThis.DWObject.Viewer.show();
         pThis.DWObject.Viewer.width = "100%";
         pThis.DWObject.Viewer.height = "100%";
         const event = new CustomEvent('initialized', {
           detail: {
             DWObject: pThis.DWObject
           }
         });
         pThis.dispatchEvent(event);
       },
       function(err) {
         console.log(err);
       }
     );
   } 
   ```
   
5. Add one button to scan documents and one button to save the document images as a PDF file.

   ```js
   render() {
     return html`
     <div class="buttons">
       <button @click=${this.scan}>Scan</button>
       <button @click=${this.save}>Save</button>
     </div>
     <div id="dwtcontrolContainer"></div>`;
   }
   scan(){
     let pThis = this;
     if (pThis.DWObject) {
       pThis.DWObject.SelectSource(function () {
         pThis.DWObject.OpenSource();
         pThis.DWObject.AcquireImage();
       },
         function () {
           console.log("SelectSource failed!");
         }
       );
     }
   }

   save(){
     if (this.DWObject) {
       this.DWObject.SaveAllAsPDF("Scanned.pdf");
     }
   }
   ```
   
6. Add a reactive property named `total` to reflect how many documents are scanned. We can update its value in the `OnBufferChanged` event of Web TWAIN.

   ```js
   export class DocumentScanner extends LitElement {
     static properties = {
       total: {},
     };
     constructor() {
       super();
       this.total = 0;
       //...
     }
     render() {
       return html`
       <div class="buttons">
         <button @click=${this.scan}>Scan</button>
         <button @click=${this.save}>Save</button>
       </div>
       <div id="dwtcontrolContainer"></div>
       <div class="status">Total: ${this.total}</div>`;
     }
     
     firstUpdated() {
       //...
       Dynamsoft.DWT.CreateDWTObjectEx(
         {
           WebTwainId: 'dwtcontrol'
         },
         function(obj) {
           //...
           pThis.DWObject.RegisterEvent('OnBufferChanged',function () {
             pThis.total = pThis.DWObject.HowManyImagesInBuffer;
           });
           //...
         },
         function(err) {
           console.log(err);
         }
       );
     } 
   }
   ```
   
7. Set the styles for the component.

   ```js
   static styles = css`
     :host {
       display: block;
     }
     .buttons {
       height: 25px;
     }
     #dwtcontrolContainer {
       width: 100%;
       height: calc(100% - 50px);
     }
     .status {
       height: 25px;
     }
     `;
   ```

## Use the Document Scanner Component

1. Import the component in the `index.js` file.

   ```js
   import { DocumentScanner } from './documentscanner'; // eslint-disable-line
   ```
   
2. Add the component in the `index.html`.

   ```html
   <document-scanner 
     style="width:320px;height:480px;"
   ></document-scanner>
   ```
   
All right, we can now scan documents from the browser.

![Lit document scanner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3pzt2tzjic87nfueg1aq.jpg)

[Online demo](https://bejewelled-alfajores-51b60d.netlify.app/)

## Source Code

Get the source code of the demo to have a try:

<https://github.com/tony-xlh/document-scanner-lit-element>