How to Build a Document Scanning Component with LitElement

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.

Getting Started With Dynamic Web TWAIN

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):

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

Install Dependencies

  1. Install Lit.

    npm install lit
    
  2. Install Dynamic Web TWAIN.

    npm install dwt
    

    In addition, we need to copy the resources of Dynamic Web TWAIN to the public folder.

    1. Install ncp.

      npm install --save-dev ncp
      
    2. Modify package.json to copy the resources for the build and start commands.

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

       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.

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

    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.

    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.

    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.

    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.

    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.

    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.

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

    <document-scanner 
      style="width:320px;height:480px;"
    ></document-scanner>
    

All right, we can now scan documents from the browser.

Lit document scanner

Online demo

Source Code

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

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