September 2, 2021 | Tutorial

DocuVieware HTML5 Viewer and Document Management Kit Integration with Electron


Add the capabilities of DocuVieware HTML5 Viewer and Document Management Kit into your Electron-based web application in a few easy steps

  1. What is Electron?
  2. Prepare your environment
  3. Create your Electron sample
  4. Integrate DocuVieware
  5. Add the switch language feature
  6. Add the custom signature field feature
  7. Run it!

1. What is Electron?

Electron is a framework for building desktop applications using JavaScript, HTML, and CSS. Experimenting with Chromium and Node.js in its binary, Electron allows you to maintain a JavaScript codebase and build cross-platform applications that run on Windows, MacOS, and Linux – no native required.

2. Prepare your environment

In order to create an Electron Sample, you will need to have DocuVieware, Node JS, and Electron installed on your machine. We recommend that you use the latest LTS version available.

Also, DocuViewer needs at least the .NET Framework 4.6 or .NET Core 3.0 to run properly.

3. Create your Electron sample

At the end of this tutorial, you will have an Electron app with DocuVieware embedded within. The application will let you use DocuVieware’s default features, choose the language of the viewer, and dynamically create a custom signature field and sign it.

Create a new folder, “DocuVieware-electron-sample” and within, another one called “Client.”
Once the folder is created, launch the CMD at its path and run the “npm init” command. Set the package name and the information asked by NodeJS.
This command creates a package.json file for your project’s frontend. A package.json file is a file that contains information about the project’s packages and dependencies. It also contains metadata for the project, such as version number, author, and description.
Once it’s done, launch “npm i –save-dev electron.” It will add the third-party package to the package’s development dependencies.
In the package.json, under “scripts”, add “start”: “electron .”, it sets node scripts to run.
Change the “main” parameter to “app/server.js.”

Create an “app” folder, and add an index.html file with this HTML code to give your application a nice loading screen.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>DocuVieware Sample</title>
  <style>
    html,
    body {
      margin: 0px;
    }
  </style>
</head>
<body style="height: 100vh; width:100vw;">
<img src="https://www.pdfa.org/wp-content/uploads/2019/02/Logo_DocuVieware_no_version-wpv_x200.png" style="position: absolute; position: absolute; margin: auto; top: 0; left: 0; right: 0; bottom: 0;" />
</body>
</html>

Create a server.js file within the app folder, and add this code to create the default window:

const { appBrowserWindowMenu } = require('electron'
const path = require("path"
require('dotenv').config() 
var win = nullfunction createWindow() 

win = new BrowserWindow({ width: 800height: 600 });
win.loadFile('app/index.html')

app.whenReady().then(createWindow)

Now, in the “Client” folder, run the “npm i dotenv” command in the CMD.
At this step, if you run the npm start, the app should load with the DV image.

4. Integrate DocuVieware

Create a “Server” folder beside the “Client” one, and create a Visual Studio project within.
Run Visual Studio and click on “Create a new project.”

Choose the “ASP.NET Core Empty” project.

Give your project a name and choose the “Server” folder as a location, then click “next.”

Choose a target framework (4.6 or upper) and click on “create.”

Now, add the reference to GdPicture.NET.14.WEB.DocuVieware.Core.dll that is found in: [INSTALLATION FOLDER]RedistDocuVieware (.NET Core 3.0)

It is also required to add Microsoft.AspNetCore.Mvc.NewtonsoftJson with NuGet manager:

Now that the references are properly set, you need to create a Controllers folder and add a DocuVieware3Controller.cs file in your project to add some mandatory imports and handle the licensing part and the configuration part of DocuVieware™ as well.

DocuVieware3Controller.cs

using Microsoft.AspNetCore.Mvc;
using GdPicture14.WEB;
using System.Net.Http;
using System.Net;
namespace DocuViewareElectronServer.Controllers
{
    [Route("api/docuvieware3")]
    public class DocuVieware3Controller : Controller
    {
        [HttpGet("ping")]
        public string ping()
        {
            return "pong";
        }
 
        [HttpPost("baserequest")]
        public string baserequest([FromBodyobject jsonString)
        {
            return DocuViewareControllerActionsHandler.baserequest(jsonString);
        }
                               
                                                
        [HttpGet("print")]
        public HttpResponseMessage Print(string sessionIDstring pageRangebool printAnnotations)
        {
            return DocuViewareControllerActionsHandler.print(sessionIDpageRangeprintAnnotations);
        }
 
        [HttpGet("save")]
        public IActionResult Save(string sessionIDstring fileNamestring formatstring pageRangebool dropAnnotationsbool flattenAnnotations)
        {
            DocuViewareControllerActionsHandler.save(sessionIDref fileNameformatpageRange,dropAnnotationsflattenAnnotationsout HttpStatusCode statusCodeout string reasonPhraseout byte[] contentout string contentType);
            if (statusCode == HttpStatusCode.OK)
            {
                return File(contentcontentTypefileName);
            }
            else
            {
                return StatusCode((int)statusCodereasonPhrase);
            }
        }
 
        [HttpGet("twainservicesetupdownload")]
        public IActionResult TwainServiceSetupDownload(string sessionID)
        {
            DocuViewareControllerActionsHandler.twainservicesetupdownload(sessionIDout HttpStatusCode statusCodeout byte[] contentout string contentTypeout string fileNameout string reasonPhrase);
            if (statusCode == HttpStatusCode.OK)
            {
                return File(contentcontentTypefileName);
            }
            else
            {
                return StatusCode((int)statusCodereasonPhrase);
            }
        }
 
        [HttpPost("formfieldupdate")]
        public string FormfieldUpdate([FromBody]object jsonString)
        {
            return DocuViewareControllerActionsHandler.formfieldupdate(jsonString);
        }
 
        [HttpPost("annotupdate")]
        public string AnnotUpdate([FromBody]object jsonString)
        {
            return DocuViewareControllerActionsHandler.annotupdate(jsonString);
        }
                  
        [HttpPost("loadfromfile")]
        public string LoadFromFile([FromBody]object jsonString)
        {
            return DocuViewareControllerActionsHandler.loadfromfile(jsonString);
        }
 
        [HttpPost("loadfromfilemultipart")]
        public string LoadFromFileMultipart()
        {
            return DocuViewareControllerActionsHandler.loadfromfilemultipart(Request);
        }                        
    }
}

In the Startup.cs file, add the following application URI and the DocuVieware basic configuration.

const string APP_URI = "http://localhost:5000";
public Startup(IConfiguration configuration)
{
DocuViewareLicensing.RegisterKEY("YOUR_KEY_HERE");
    Configuration = configuration;
    DocuViewareManager.SetupConfiguration(trueGlobals.DOCUVIEWARE_SESSION_STATE_MODEPath.Combine(Globals.GetCacheDirectory(), "/""api/docuvieware3"), APP_URI"api/docuvieware3");
    DocuViewareEventsHandler.CustomAction += Globals.CustomActionHandler;
}

It is also needed to enable Cors:

public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}
public void Configure(IApplicationBuilder appIWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseCors(options => options
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader()
    );
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Create a Models folder, then create a DocuViewareConfiguration class in it to manage the SessionId and ControlId:

namespace DocuViewareElectronServer.Models
{
    public class DocuViewareConfiguration
    {
        public string SessionId { getset; }
        public string ControlId { getset; }
    }
}

Create a DocuViewareRESTOutputResponse class in the model folder, and declare the HtmlContent that will be communicated between the client and the server:

namespace DocuViewareElectronServer.Models
{
    public class DocuViewareRESTOutputResponse
    {
        public string HtmlContent { getset; }
    }
}

Create a new controller “DocuViewareController”:

namespace DocuViewareElectronServer.Controllers
{
    public class DocuViewareController : Controller
    {
        [HttpPost]
        [Route("GetDocuViewareControl")]
        public DocuViewareRESTOutputResponse GetDocuViewareControl(DocuViewareConfiguration controlConfiguration)
        {
            if (!DocuViewareManager.IsSessionAlive(controlConfiguration.SessionId))
            {
                if (!string.IsNullOrEmpty(controlConfiguration.SessionId) && !string.IsNullOrEmpty(controlConfiguration.ControlId))
                {
                    DocuViewareManager.CreateDocuViewareSession(controlConfiguration.SessionIdcontrolConfiguration.ControlId20);
                }
                else
                {
                    throw new Exception("Invalid session identifier and/or invalid control identifier.");
                }
            }
 
            using (DocuViewareControl docuVieware = new DocuViewareControl(controlConfiguration.SessionId))
            {
                docuVieware.MaxUploadSize = 36700160// 35MB
                docuVieware.ShowDigitalSignatureSnapIn = true;
 
 
                using (StringWriter controlOutput = new StringWriter())
                {
                    docuVieware.RenderControl(controlOutput);
                    DocuViewareRESTOutputResponse output = new DocuViewareRESTOutputResponse
                    {
                        HtmlContent = controlOutput.ToString()
                    };
                    return output;
                }
            }
        }
    }
}

Create a new class file called “Globals” and add these lines:

using GdPicture14;
using GdPicture14.WEB;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
 
namespace DocuViewareElectronServer
{
    public static class Globals
    {
        private static readonly string m_rootDirectory = Directory.GetCurrentDirectory();
        public static readonly int SESSION_TIMEOUT = 20;
        public const bool STICKY_SESSION = true;
        public const DocuViewareSessionStateMode DOCUVIEWARE_SESSION_STATE_MODE = DocuViewareSessionStateMode.File;
 
 
        public static string GetCacheDirectory()
        {
            return m_rootDirectory + "\\cache";
        }
 
 
        public static string GetDocumentsDirectory()
        {
            return m_rootDirectory + "\\documents";
        }
 
 
        public static string BuildDocuViewareControlSessionID(HttpContext HttpContextstring clientID)
        {
            if (HttpContext.Session.GetString("DocuViewareInit") == null)
            {
                HttpContext.Session.SetString("DocuViewareInit""true");
            }
 
            return HttpContext.Session.Id + clientID;
        }
    }
}

Create a .env file, and set your hostname and port:

SERVER_SCHEME=http 
SERVER_HOSTNAME=localhost 
SERVER_PORT=5000

You will also need to add this line in the first line of the server.js file:

require('dotenv').config()

Add this object in the BrowserWindow constructor (server.js):

webPreferences: {
          preloadpath.join(__dirname'preload.js'),
          nodeIntegrationtrue,
          contextIsolationfalse,
          webSecurityfalse
        }    

Add a preload.js file and add this lines to manage the preload process of your application:

import { ipcRenderer } from 'electron'
window.addEventListener('DOMContentLoaded', () => { 
window.processId = `${process.pid}`
ipcRenderer.emit("preload-done"); });

Now, create an index.js file and add this code to wait until the end of the preload script to launch the application and let Electron execute the javascript:

import { ipcRenderer } from 'electron';
 
//We have to wait the end of the preload script to start the app
ipcRenderer.once('preload-done', () => launchControl());
 
//This is necessary because when we set the dv control in the innerHTML of a HTMLElement, electron does not execute the JS inside 
const evalPageScripts = () => {
    const scripts = document.querySelectorAll('#dvcontrol script');
 
    scripts.forEach((script=> {
        const newScript = document.createElement('script');
        newScript.type = 'text/javascript';
        newScript.innerText = script.innerText;
 
        if (script.parentNode) {
            script.parentNode.removeChild(script);
        }
        return document.body.appendChild(newScript);
    })
};
 
//Get the DV control from the server and display it in the window
function launchControl() {
    fetch(${process.env.SERVER_SCHEME}://${process.env.SERVER_HOSTNAME}:${process.env.SERVER_PORT}/api/DocuVieware/GetDocuViewareControl, {
        method'POST',
        bodyJSON.stringify({
            SessionId: ${window.processId},
            ControlId: "dvcontrol"
        }),
        headersnew Headers({
            "Content-Type": "application/json"
        })
    })
        .then(response => response.json())
        .then(result => {
            document.body.innerHTML = result.htmlContent;
            evalPageScripts();
        });
}

Note: we need the evalpagescripts method because when we add raw HTML within the DOM, Electron does not evaluate them. So we need to implement the evalpagescript to make it work.

Create an “asset” folder and copy-paste the min-js/min-css from the redist.

In the head of index.html, add:

<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="assets/docuvieware-min.js"></script>
<link rel="stylesheet" href="assets/docuvieware-min.css">
<script src="./index.js"></script>

At this point, if you run the server through Visual Studio and the client by running it through the command “npm start” in the CMD, the DocuVieware control should be displayed in the electron’s window.

5. Add the switch language feature

Go to the DocuViewareConfiguration class in the DocuViewareConfiguration.cs file, and add this property:

public DocuViewareLocale Lang { getset; }

In the GetDocuViewareControl method, add this line:

docuVieware.Locale = controlConfiguration.Lang;

In the DocuViewareController.cs file, add a method that returns the supported DocuVieware language:

[HttpGet]
        [Route("GetDocuviewareLanguages")]
        public IActionResult GetSupportedLanguages()
        {
            var languages = new List<string>();
            foreach (var lang in Enum.GetValues(typeof(DocuViewareLocale)))
            {
                if ((DocuViewareLocale)lang == DocuViewareLocale.Auto)
                {
                    continue;
                }
 
                languages.Add($"{(int)lang}|{lang}");
            }
            return Ok(languages);
        }

In order to send simple request, we will install electron-fetch tough this command : npm i –save electron-fetch
In the createwindow method in the Server.js file, add this method after the “win” affectation. It will get the available DocuVieware’s language list sent by the previous method.

fetch(`${process.env.SERVER_SCHEME}://${process.env.SERVER_HOSTNAME}:${process.env.SERVER_PORT}/api/DocuVieware/GetDocuviewareLanguages`)
    .then(response => response.json())
    .then(result => {
      var languages = [];
      result.forEach(lang => {
        var infos = lang.split('|');
        var code = parseInt(infos[0]);
        var label = infos[1];
        languages.push({
          label: label,
          click: () => changeControlLang(code)
        })
      });
      let menu = Menu.buildFromTemplate([
        {
          label: 'Lang',
          submenu: languages
        },
        {
          label: "Dev",
          submenu: [{
            label: "DevTool",
            role: 'toggleDevTools'
          },{
            label: "Reload",
            role: 'reload'
          }]
        }
      ]);
      Menu.setApplicationMenu(menu);
    }).catch(console.error);

Add this code in the index.js file to get the event of a language change:

//Get language change event from the menu and reload control
ipcRenderer.on('change-lang'function (eventlang) {
    launchControl(lang);
});

To support the language management on the client-side, replace the loadControl method by this one:

//Get the DV control from the server and display it in the window
function launchControl(lang) {
    //if no lang is provided, use Auto
    if (!lang) {
        lang = 0;
    }
    fetch(${process.env.SERVER_SCHEME}://${process.env.SERVER_HOSTNAME}:${process.env.SERVER_PORT}/api/DocuVieware/GetDocuViewareControl, {
        method'POST',
        bodyJSON.stringify({
            SessionId: ${window.processId},
            ControlId: "dvcontrol",
            Lang: lang
        }),
        headersnew Headers({
            "Content-Type": "application/json"
        })
    })
        .then(response => response.json())
        .then(result => {
            document.body.innerHTML = result.htmlContent;
            evalPageScripts();
        });
}

Run both server and client, and a new menu should be displayed and offer to change the Docuvieware language.

6. Add the custom signature field feature

On the server-side, in the Startup.cs file, add a CustomActionHandler event to let your client communicate with a custom server method.

DocuViewareEventsHandler.CustomAction += Globals.CustomActionHandler;

And then, add the method in the Global.cs file with the custom creation of a Signature field:

public static void CustomActionHandler(object senderCustomActionEventArgs e)
{
    var status = e.docuVieware.GetNativePDF(out GdPicturePDF pdf);
 
    if (status != GdPictureStatus.OK)
    {
        e.message = new DocuViewareMessage("The document is not a PDF !");
        return;
    }
 
    switch (e.actionName)
    {
        case "AddSignatureFormField":
            //The GetSelectionAreaCoordinates return value in Inch
            pdf.SetMeasurementUnit(PdfMeasurementUnit.PdfMeasurementUnitInch);
            pdf.SetOrigin(PdfOrigin.PdfOriginTopLeft);
 
            var addSignatureFormFieldArgs = JsonSerializer.Deserialize<PostCustomActionModels.AddSignatureFormFieldModel>(
                e.args.ToString(), 
                new JsonSerializerOptions { PropertyNameCaseInsensitive = trueNumberHandling = JsonNumberHandling.Strict });
            
            pdf.AddSignatureFormField(
                addSignatureFormFieldArgs.Region.Left
                addSignatureFormFieldArgs.Region.Top
                addSignatureFormFieldArgs.Region.Width
                addSignatureFormFieldArgs.Region.Height
                addSignatureFormFieldArgs.FormFieldName);
 
            status = pdf.GetStat();
 
            if (status != GdPictureStatus.OK)
            {
                e.message = new DocuViewareMessage("The signature could not be added");
                return;
            }
            
            pdf.DrawRectangle(
                addSignatureFormFieldArgs.Region.Left,
                addSignatureFormFieldArgs.Region.Top,
                addSignatureFormFieldArgs.Region.Width,
                addSignatureFormFieldArgs.Region.Height,
                false,
                true);
 
            e.docuVieware.RedrawPage();
 
            break;
    }
}

In order to define the area where the signature field should be placed, you will need to create a custom class:

public static class PostCustomActionModels
{
    public class AddSignatureFormFieldModel
    {
        public string FormFieldName { getset; }
        public Region Region { getset; }
    }
 
    public class Region
    {
        public float Width { getset; }
        public float Height { getset; }
        public float Top { getset; }
        public float Left { getset; }
    }
}

7. Run it!

You’re done! Launch your sample by running the server through Visual Studio, and launch the “npm start” through the CMD on the Electron project.

Add custom Signature field from the Electron main bar
Sign your document with DocuVieware

You can find the complete sample in the installation folder of DocuVieware on your machine.

Change the DocuVieware default language through the Electron’s main menu

Let us know how you get on!

Fabio


Tags: