How to Scan Documents Inside a Shadow DOM Web Component with Dynamic Web TWAIN

Shadow DOM is a web standard designed to encapsulate HTML and CSS into web components. It allows for the creation of separate DOM trees and specifies how these trees interact with each other in a document. Polymer is a library developed by Google to make it easier to create reusable web components. It utilizes Shadow DOM as a core part of its architecture. This article shares how to integrate Dynamic Web TWAIN into a Polymer project for implementing document scanning functions in web browsers.

What you’ll build: A browser-based document scanning application that runs inside a Shadow DOM web component, using Polymer and the Dynamic Web TWAIN SDK to acquire scanned images from TWAIN-compatible scanners.

Key Takeaways

  • Dynamic Web TWAIN can be embedded inside Shadow DOM by binding the viewer to a shadowRoot element instead of the global document.
  • The CreateDWTObjectEx method with Viewer.bind(this.shadowRoot.querySelector(...)) is the key pattern for Shadow DOM integration.
  • Polymer’s ready() lifecycle hook is the correct place to initialize the Dynamic Web TWAIN object so that the Shadow DOM tree is fully rendered.
  • This approach applies to any Shadow DOM-based framework, not only Polymer — the same shadowRoot binding pattern works with Lit, vanilla Web Components, and other libraries.

Common Developer Questions

How do I scan documents inside a Shadow DOM web component?

Initialize Dynamic Web TWAIN inside the component’s ready() lifecycle and bind the viewer with this.shadowRoot.querySelector('#dwtcontrolContainer') instead of using a global document lookup. Once the object is created, the scan flow works the same way as a normal browser integration.

Why does Dynamic Web TWAIN not render when used inside Shadow DOM, and how do I fix it?

The usual failure is binding to document.getElementById() instead of querying inside shadowRoot, which leaves the SDK with no valid target element. Fix it by creating the object after the Shadow DOM tree is ready and always binding against the encapsulated container.

How do I bind the Dynamic Web TWAIN viewer to a shadowRoot element instead of the main document?

Use CreateDWTObjectEx() and pass the element returned by this.shadowRoot.querySelector(...) into the viewer bind step. That keeps all viewer rendering inside the component boundary instead of leaking into the main document.

Prerequisites

Step 1: Scaffold a Polymer Project

mkdir polymer-document-scan
cd polymer-document-scan
polymer init polymer-3-starter-kit
npm start

Polymer project

Step 2: Install the Dynamic Web TWAIN SDK

In the project directory, install the SDK:

npm install dwt

Step 3: Include the Dynamic Web TWAIN Script in index.html

<script src="node_modules/dwt/dist/dynamsoft.webtwain.min.js"></script>

Step 4: Add the Scanner UI to the Polymer Component Template

Add a div element and a button element to the HTML template in src/my-view1.js:

class MyView1 extends PolymerElement {
  MyView1() {
    this.dwtObj;
    this.deviceList = [];
  }
  static get template() {
    return html`
      <style include="shared-styles">
        :host {
          display: block;

          padding: 10px;
        }
      </style>

      <div class="card">
        <h1>Web Document Scan</h1>
        <select size="1" id="source" style="position: relative; width: 220px;"></select>
        <div id="dwtcontrolContainer"></div>
        <button on-click="handleClick">scan</button>
      </div>
    `;
  }
 
  handleClick() {
    if (DWObject) {
      
    }
  }
}
  • The div element with the ID dwtcontrolContainer is used to initialize the Dynamic Web TWAIN object.
  • The button element with the handleClick event triggers the document scanning process.

Step 5: Initialize Dynamic Web TWAIN Inside the Shadow DOM

Instantiate a Dynamic Web TWAIN object with a valid license key and the div element in the ready() method:

ready() {
    super.ready();
    // TODO: initialization
    this.initializeDWT();
  }

  initializeDWT() {
    if (Dynamsoft) {
      Dynamsoft.DWT.AutoLoad = true;
      Dynamsoft.DWT.UseLocalService = true;
      // https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
      Dynamsoft.DWT.ProductKey = 'LICENSE-KEY';
      Dynamsoft.DWT.ResourcesPath = 'node_modules/dwt/dist/';
      Dynamsoft.DWT.CreateDWTObjectEx({ WebTwainId: 'Viewer' }, (obj) => {
        this.dwtObj = obj;
        obj.Viewer.bind(this.shadowRoot.querySelector('#dwtcontrolContainer'));
        obj.Viewer.width = 560;
        obj.Viewer.height = 600;
        obj.Viewer.show();

        let select = this.shadowRoot.querySelector('#source');
        let deviceList = this.deviceList = [];
        obj.GetDevicesAsync().then(function (devices) {
          for (var i = 0; i < devices.length; i++) {
            let option = document.createElement('option');
            option.value = devices[i].displayName;
            option.text = devices[i].displayName;
            deviceList.push(devices[i]);
            select.appendChild(option);
          }
        }).catch(function (exp) {
          alert(exp.message);
        });

      }, function (e) {
        console.log(e)
      });
    }
  }

Note: You must set the ResourcesPath to the path where the Dynamic Web TWAIN resources are located. The CreateDWTObjectEx method creates a Dynamic Web TWAIN object. The bindmethod binds the object to the div element. The GetDevicesAsync method is used to retrieve the list of available devices.

Step 6: Trigger Document Scanning on Button Click

Implement the handleClick method to trigger the document scanning process:

handleClick() {
    var obj = this.dwtObj;
    if (obj) {
      obj.SelectDeviceAsync(this.deviceList[this.shadowRoot.querySelector('#source').selectedIndex]).then(function () {
        return obj.AcquireImageAsync({
          IfShowUI: false,
          IfCloseSourceAfterAcquire: true
        });
      }).catch(function (exp) {
        alert(exp.message);
      });
    }
  }

Step 7: Run and Test the Scanning App

npm start

Open a web browser and navigate to http://localhost:8081/ to see the document scanning app implemented in Polymer.

Polymer Shadow DOM document scan

Common Issues and Edge Cases

  • Dynamic Web TWAIN viewer does not appear inside Shadow DOM: This happens when you use document.getElementById instead of this.shadowRoot.querySelector. Shadow DOM encapsulation prevents global DOM queries from finding elements inside the component. Always use this.shadowRoot.querySelector('#dwtcontrolContainer') when binding the viewer.
  • Scanner list is empty or GetDevicesAsync returns no devices: Ensure the Dynamsoft Service is installed and running on the client machine. Dynamic Web TWAIN requires its local service for TWAIN scanner communication. Check that UseLocalService is set to true.
  • Resource files fail to load (404 errors for .wasm or worker files): Verify that Dynamsoft.DWT.ResourcesPath points to the correct node_modules/dwt/dist/ directory relative to your served index.html. Path mismatches are the most common setup failure.

Source Code

Get the complete sample project source code on GitHub