Building VSCode Extension: Angular Snippets for Dynamic Web TWAIN
Tags: vscode, angular, webdev, documentscanning
Published: 2022-10-31 08:58:30

There are many web developers who prefer using Angular and Dynamic Web TWAIN to build web document scanning and management applications. The official [Dynamic Web TWAIN samples](https://github.com/Dynamsoft/Dynamic-Web-TWAIN/tree/master/samples) are written in JavaScript. Although TypeScript is compatible with JavaScript, it is not just a copy-and-paste job to use these JavaScript code in an Angular project. In this article, we will demonstrate how to migrate Dynamic Web TWAIN samples from JavaScript to Angular, as well as how to build a vscode extension that contains the Angular code snippets and sample project.

![TypeScript snippets for Dynamic Web TWAIN](https://www.dynamsoft.com/codepool/img/2022/10/dynamic-web-twain-typescript-snippet.gif)

## Download from Visual Studio Code Marketplace
[Angular Snippets for Dynamic Web TWAIN](https://marketplace.visualstudio.com/items?itemName=yushulx.dwt-code-generator)

## Migrating Dynamic Web TWAIN Samples from JavaScript to Angular
Dynamic Web TWAIN samples are written in JavaScript. You can find them on [GitHub](https://github.com/Dynamsoft/Dynamic-Web-TWAIN/tree/master/samples).

The samples can be categorized into two types: document scanning and document editing. Therefore, we create a new Angular project and add two Angular components for them respectively. 

```bash
npm install -g @angular/cli
ng new dwt-angular-samples
ng generate component acquire-image
ng generate component image-editor
```

To make Web TWAIN API work, we need to set the license key and configure the resource path, which could be implemented using an Angular service. Run the following command to create a service.

```bash
ng generate service dynamic-web-twain
```

The command generates a `dynamic-web-twain.service.ts` file, in which we add the code for global settings.


```typescript
import { Injectable, Optional } from '@angular/core';
import Dynamsoft from 'dwt';

@Injectable({
  providedIn: 'root'
})
export class DynamicWebTWAINService {

  constructor() {
    Dynamsoft.DWT.ProductKey = "LICENSE-KEY";
    Dynamsoft.DWT.ResourcesPath = "assets/dynamic-web-twain";
  }
}
```

If you don't have a license key, you can get it from [the online customer portal](https://www.dynamsoft.com/customer/license/trialLicense?product=dwt). 

The resource path must be configured in `angular.json` file as well.

```json
{
"glob": "**/*",
"input": "./node_modules/dwt/dist",
"output": "assets/dynamic-web-twain"
}
```

Next, we inject the service into the components.

```typescript
export class AcquireImageComponent implements OnInit {

  constructor(private dynamicWebTwainService: DynamicWebTWAINService) {
  }
}

export class ImageEditorComponent implements OnInit {

  constructor(private dynamicWebTwainService: DynamicWebTWAINService) { }
}
```

The preparation work is done. A quick test is to run `ng serve` and verify the app status through the console log. If there is no resource loading error, we can get started to migrate JavaScript to TypeScript.

### Document Scanning
The document scanning sample includes the following features:
- Acquire documents from a scanner.
- Load images from local disk.
- Save images as JPEG, TIFF, and PDF.

Here is the UI implementation in HTML:

```html
<div class="row">
    <label for="BW">
        <input type="radio" value="0" name="PixelType">B&amp;W </label>
    <label for="Gray">
        <input type="radio" value="1" name="PixelType">Gray</label>
    <label for="RGB">
        <input type="radio" value="2" name="PixelType" checked="checked">Color</label>

    <label>
        <input type="checkbox" id="ADF" checked="checked">Auto Feeder</label>
    <div>&nbsp;&nbsp;</div>
    <select id="Resolution">
        <option value="100">100</option>
        <option value="150">150</option>
        <option value="200">200</option>
        <option value="300">300</option>
    </select>
</div>

<select id="sources"></select><br />
<button (click)="acquireImage()">Scan Documents</button>
<button (click)="openImage()">Load Documents</button>

<div id="dwtcontrolContainer"></div>

<div class="row">
    <label style="font-size: x-large;">
        <input type="radio" value="jpg" name="ImageType" id="imgTypejpeg" />JPEG</label>
    <label style="font-size: x-large;">
        <input type="radio" value="tif" name="ImageType" id="imgTypetiff" />TIFF</label>
    <label style="font-size: x-large;">
        <input type="radio" value="pdf" name="ImageType" id="imgTypepdf" checked="checked" />PDF</label>
</div>
<button (click)="downloadDocument()">Download Documents</button>
```

It's almost the same as the JavaScript version. The only difference is that we use the `click` event instead of `onclick` in the button element.

Now we make comparisons between the JavaScript code and TypeScript code.

- Initialize Dynamic Web TWAIN object.

    JavaScript
    ```javascript
    Dynamsoft.DWT.Containers = [{ContainerId:'dwtcontrolContainer', Width:600, Height:800}];
    Dynamsoft.DWT.Load();
    Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', onReady); 
    function onReady() {
      dwtObject = Dynamsoft.DWT.GetWebTwain('dwtcontrolContainer');
    }
    ```

    TypeScript
    ```typescript
    ngOnInit(): void {
        Dynamsoft.DWT.Containers = [{ ContainerId:'dwtcontrolContainer', Width:600, Height:800 }];
        Dynamsoft.DWT.Load();
        Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => { this.onReady(); });
    }

    onReady(): void {
        this.dwtObject = Dynamsoft.DWT.GetWebTwain(this.containerId);
    }
    ```

- Scan documents.
    
    JavaScript
    ```javascript
    dwtObject.SelectSourceByIndex(document.getElementById("source").selectedIndex);
    const onAcquireImageSuccess = () => { if (this.dwtObject) this.dwtObject.CloseSource(); };
    const onAcquireImageFailure = onAcquireImageSuccess;
    dwtObject.OpenSource();
    var pixelType = 2;
    var pixelTypeInputs = document.getElementsByName("PixelType");
    for (var i = 0; i < pixelTypeInputs.length; i++) {
        if (pixelTypeInputs[i].checked) {
        pixelType = pixelTypeInputs[i].value;
        break;
        }
    }
    dwtObject.AcquireImage(
        {
        IfShowUI: document.getElementById("ShowUI").checked,
        IfFeederEnabled: document.getElementById("ADF").checked,
        PixelType: pixelType,
        Resolution: parseInt(document.getElementById("Resolution").value),
        IfDisableSourceAfterAcquire: true
        },
        onAcquireImageSuccess,
        onAcquireImageFailure
    );
    ```

    TypeScript
    ```typescript
    this.dwtObject.SelectSourceByIndex(this.selectSources.selectedIndex)
    const onAcquireImageSuccess = () => { if (this.dwtObject) this.dwtObject.CloseSource(); };
    const onAcquireImageFailure = onAcquireImageSuccess;
    this.dwtObject.OpenSource();
    let pixelType = '2';
    var pixelTypeInputs = document.getElementsByName("PixelType");
    for (var i = 0; i < pixelTypeInputs.length; i++) {
    if ((<HTMLInputElement>pixelTypeInputs[i]).checked) {
        pixelType = (<HTMLSelectElement>pixelTypeInputs[i]).value;
        break;
    }
    }
    this.dwtObject.AcquireImage({
    IfFeederEnabled: (<HTMLInputElement>document.getElementById("ADF"))!.checked,
    PixelType: pixelType,
    Resolution: parseInt((<HTMLSelectElement>document.getElementById("Resolution"))!.value),
    IfDisableSourceAfterAcquire: true
    }, onAcquireImageSuccess, onAcquireImageFailure);
    ```
- Load images.

    JavaScript
    ```javascript
    dwtObject.LoadImageEx("", Dynamsoft.DWT.EnumDWT_ImageType.IT_ALL, () => {}, () => {})
    ```

    TypeScript
    ```typescript
    this.dwtObject.LoadImageEx("", Dynamsoft.DWT.EnumDWT_ImageType.IT_ALL, () => {}, () => {});
    ```
- Download documents as PDF, TIFF, or JPEG.

    JavaScript
    ```javascript
    if (document.getElementById("imgTypejpeg").checked == true) {
        if (dwtObject.GetImageBitDepth(dwtObject.CurrentImageIndexInBuffer) == 1)
            dwtObject.ConvertToGrayScale(dwtObject.CurrentImageIndexInBuffer);
        dwtObject.SaveAsJPEG("DynamicWebTWAIN.jpg", dwtObject.CurrentImageIndexInBuffer);
    }
    else if (document.getElementById("imgTypetiff").checked == true)
        dwtObject.SaveAllAsMultiPageTIFF("DynamicWebTWAIN.tiff", OnSuccess, OnFailure);
    else if (document.getElementById("imgTypepdf").checked == true)
        dwtObject.SaveAllAsPDF("DynamicWebTWAIN.pdf", OnSuccess, OnFailure);
    ```

    TypeScript
    ```typescript
    if ((<HTMLInputElement>document.getElementById("imgTypejpeg")).checked == true) {
        if (this.dwtObject.GetImageBitDepth(this.dwtObject.CurrentImageIndexInBuffer) == 1)
            this.dwtObject.ConvertToGrayScale(this.dwtObject.CurrentImageIndexInBuffer);
        this.dwtObject.SaveAsJPEG("DynamicWebTWAIN.jpg", this.dwtObject.CurrentImageIndexInBuffer);
    }
    else if ((<HTMLInputElement>document.getElementById("imgTypetiff")).checked == true)
    this.dwtObject.SaveAllAsMultiPageTIFF("DynamicWebTWAIN.tiff", () => { }, () => { });
    else if ((<HTMLInputElement>document.getElementById("imgTypepdf")).checked == true)
    this.dwtObject.SaveAllAsPDF("DynamicWebTWAIN.pdf", () => { }, () => { });
    ```

The primary difference between the JavaScript and TypeScript code is that the TypeScript requires type casting. Besides, because Angular has its own lifecycle, we also need to destroy the Dynamic Web TWAIN object when `ngOnDestroy()` is triggered.

```typescript
ngOnDestroy() {
    Dynamsoft.DWT.Unload();
}
```

![Angular web TWAIN document scanning](https://www.dynamsoft.com/codepool/img/2022/10/web-twain-document-scanning.png)

### Document Editing
The document editing sample does not only demonstrates the basic image editing APIs of Dynamic Web TWAIN, but also shows how to use the built-in image editor.

```html
<select id="sources"></select><br />
<div class="row">
    <button (click)="acquireImage()">Scan</button>
    <button (click)="openImage()">Load</button>
</div>

<div class="row">
    <div id="dwtcontrolContainer"></div>
    <div id="dwtcontrolContainerLargeViewer"></div>
</div>

<div style="width: 800px;">
    <input style="font-size: x-large;" type="button" value=" |< " (click)="firstImage()"/>
    <input style="font-size: x-large;" type="button" value=" < " (click)="preImage()"/>
    <input style="font-size: x-large;" type="text" size="2" id="DW_CurrentImage" readonly="readonly" value="0" /> /
    <input style="font-size: x-large;" type="text" size="2" id="DW_TotalImage" readonly="readonly" value="0" />
    <input style="font-size: x-large;" type="button" value=" > " (click)="nextImage()"/>
    <input style="font-size: x-large;" type="button" value=" >| " (click)="lastImage()"/> Preview Mode:
    <select style="font-size: x-large;" size="1" id="DW_PreviewMode" (change)="setMode()">
        <option value="0">1X1</option>
        <option value="1">2X2</option>
        <option value="2">3X3</option>
        <option value="3">4X4</option>
        <option value="4">5X5</option>
    </select>
</div>
<div class="row">
    <button (click)="removeSelected()">Remove Selected</button>
    <button (click)="removeAll()">Remove All</button>
    <button (click)="rotateLeft()">Rotate Left</button>
    <button (click)="rotateRight()">Rotate Right</button>
    <button (click)="mirror()">Mirror</button>
    <button (click)="flip()">Flip</button>
    <button (click)="showImageEditor()" id="imageEditor">Hide Editor</button>
</div>
```

![Angular web TWAIN document editing](https://www.dynamsoft.com/codepool/img/2022/10/angular-web-twain-sample.png)

The corresponding implementation of button click events is as follows:

```typescript
removeSelected() {
    this.dwtObject.RemoveAllSelectedImages();
}

removeAll() {
    this.dwtObject.RemoveAllImages();
}

rotateLeft() {
    this.dwtObject.RotateLeft(this.dwtObject.CurrentImageIndexInBuffer);
}

rotateRight() {
    this.dwtObject.RotateRight(this.dwtObject.CurrentImageIndexInBuffer);
}

mirror() {
    this.dwtObject.Mirror(this.dwtObject.CurrentImageIndexInBuffer);
}

flip() {
    this.dwtObject.Flip(this.dwtObject.CurrentImageIndexInBuffer);
}

createImageEditor() {
    this.imageEditor = this.dwtObject!.Viewer.createImageEditor({
    element: <HTMLDivElement>document.getElementById('dwtcontrolContainerLargeViewer'),
    width: 750,
    height: 800,
    buttons: {
        visibility: {
        close: false
        }}
    });
    this.imageEditor.show();
}
```

## Building a VSCode Extension for Dynamic Web TWAIN Angular Development
To facilitate the development of Angular applications with Dynamic Web TWAIN, we can create a VSCode extension, which includes TypeScript code snippets and Angular project templates. 

### Create a VSCode Extension Skeleton
According to Microsoft's [documentation](https://code.visualstudio.com/api/get-started/your-first-extension), we can create a VSCode extension skeleton by running the following command in the terminal:

```bash
npm install -g yo generator-code
yo code
```

### Add TypeScript Code Snippets
1. Create a `typescript.json` file under the `snippets` folder. 
2. Follow the [Snippet Guide](https://code.visualstudio.com/api/language-extensions/snippet-guide) to add the snippets for using Dynamic Web TWAIN.

    ```json:
    {
        "import": {
            "prefix": "dwt import",
            "description": "Import Dynamic Web TWAIN",
            "body": [
                "import { WebTwain } from 'dwt/dist/types/WebTwain';",
                "import Dynamsoft from 'dwt';"
            ]
        },
        ...
    }
    ```
3. Configure the file path of code snippets in `package.json`.

    ```json
    "contributes": {
        "snippets": [
            {
                "language": "typescript",
                "path": "./snippets/typescript.json"
            }
        ]
    }
    ```
4. Use the snippets in a TypeScript file.

    ![Angular web TWAIN TypeScript code snippets](https://www.dynamsoft.com/codepool/img/2022/10/dynamic-web-twain-angular-snippet.png)

### Add Angular Project Templates
1. Copy the Angular project we created in the previous section to the extension project. To avoid compiling the Angular project when debugging the VSCode extension, we need to exclude the project folder in `tsconfig.json`.

    ```json
    "exclude": [
		"<angular-project-template>"
	]
    ```

2. Register a command named `quickstart` in `package.json`.

    ```json
    "activationEvents": [
        "onCommand:dwt.quickstart"
    ]
    "contributes": {
        "commands": [
            {
                "command": "dwt.quickstart",
                "title": "dwt: Create Angular Project for Dynamic Web TWAIN"
            }
        ]
    }
    ```

3. In `extension.ts`, create a new project via `vscode.window.showInputBox` and then copy the Angular project template to the new project folder.

    ```typescript
    async openFolder() {
        const projectName = await vscode.window.showInputBox({
            prompt: 'Enter a name for the new project',
            validateInput: (value: string): string => {
                if (!value.length) {
                    return 'A project name is required';
                }
                return '';
            }
        });
        if (!projectName) {
            return '';
        }

        let workspace = '';
        const folderUris = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, openLabel: 'Select folder' });
        if (!folderUris) {
            return '';
        }

        let workspaceFolderUri = folderUris[0];
        workspace = workspaceFolderUri.fsPath;
        let projectFolder = path.join(workspace, projectName);
        if (!fs.existsSync(projectFolder)) {
            fs.mkdirSync(projectFolder);
        }

        console.log("Open " + projectFolder);
        await vscode.commands.executeCommand("vscode.openFolder", Uri.file(projectFolder), { forceNewWindow: true });

        return projectFolder;
    }

    async createProject() {
        let src: string = path.join(__dirname, '../res/quickstart/');

        // Select the project folder
        const answer = await vscode.window.showQuickPick(['Yes', 'No'], { placeHolder: 'Do you want to create a new folder?' });
        if (!answer) { return; }

        let des: string = '';
        if (answer === "Yes") {
            des = await this.openFolder();
            if (des !== '') {
                copyFolder(src, des);
            }
        }
        else {
            let folders = vscode.workspace.workspaceFolders;
            if (!folders) {
                des = await this.openFolder();
                if (des !== '') {
                    copyFolder(src, des);
                }
            }
            else {
                des = folders[0].uri.fsPath;
                vscode.window.showInformationMessage(folders[0].uri.fsPath);
                copyFolder(src, des);
            }
        }
    }
    ```

4. Press `F2` to run the `dwt.quickstart` command.

    ![Create an Angular project for Dynamic Web TWAIN](https://www.dynamsoft.com/codepool/img/2022/10/web-twain-vscode-command.png)

### Build and Publish the VSCode Extension
1. Build the extension package:

    ```bash
    vsce package
    ```

    If some important template files are missing, check out whether they are included in the `.vscodeignore` file. For example, if `*.ts` is included in the `.vscodeignore` file, all `*.ts` files will not be packaged into the extension.

2. Sign in [Visual Studio Marketplace](https://marketplace.visualstudio.com/manage) to upload the extension package.

## Source Code

[https://github.com/yushulx/vscode-web-twain-angular-snippets](https://github.com/yushulx/vscode-web-twain-angular-snippets)