Automated Form Filling
The PrizmDoc™ e-Signer supports filling out forms created using the PrizmDoc™ Template Designer, demonstrated here. Using the e-Signer, form fields can be filled out manually, filled out programmatically, or pre-populated with default values.
Once the fields are filled out, the user can click the Download Signed Form button to download a PDF with the filled in data. When this button is pressed, the e-Signer uses the PrizmDoc Server “MarkupBurner” API to “burn” the data into the document.
But what if you already have all of your user’s data, and you want to skip straight to burning the data into the document without loading the form into the e-Signer? In this post, I will outline how to create a Node.js service to do so.
The example code and instructions for running the service are available in the Accusoft prizmdoc-form-burner-example GitHub repository.
1. Handle Routing Form Burner GET Requests
This service will handle routing GET requests for burning data into a form, and this initial step sets up the route handling.
For this example, you will need to put your form definition file (or files) in a folder named “FormDefinitions” along with a form data file (with the same name but with a “data.json” file extension). Your form data file must contain key-value pairs, where each key is a form field ID and each value is the value to use to fill in the field. For example:
{
"Text1": "Doe",
"Text2": "John"
}
The corresponding form document will need to be put into a folder named “Documents”.
You will need to have Node.js and PrizmDoc Server installed. As an alternative to having PrizmDoc Server installed, you could connect to PrizmDoc Cloud.
Use npm to install express and axios. Express will be used to listen on port 3001 and route a GET request for a “form burner,” as shown below. Axios will be used to make http requests to PrizmDoc Server (in step 3).
Create a main.js file and, as shown below, require express, axios, and fs (which will be used to load a form data file, as shown below, as well as the form document and form definition file).
const express = require('express');
const axios = require('axios');
const app = express();
const fs = require('fs');
// PrizmDoc Server must be installed to use this.
const host = 'http://localhost:18681';
// Use the following instead to use PrizmDoc Cloud.
// const host = 'https://api.accusoft.com';
const apiKey = 'YOUR_API_KEY';
app.get('/formBurner/:id', function (req, res) {
// This example uses the field value provided in the data.json file in the FormDefinitions folder.
// You can update the code to instead load the data from elsewhere, such as a database.
fs.readFile(`${__dirname}/FormDefinitions/${req.params.id}.data.json`, 'utf8', function (err, data) {
const fieldValues = !err ? JSON.parse(data) : {};
// See step 2 for the implementation of convertForm.
convertForm(fieldValues, req.params.id, res);
});
});
var server = app.listen(3001, () => console.log('listening on port 3001'));
2. Convert Each Form Field to Markup
The PrizmDoc Server MarkupBurner API takes markup JSON as input. It is necessary to convert each form field to the proper markup format. The convertForm method below opens the form definition file with the specified form definition ID and converts each field to markup, using the specified field values to fill in the fields. The markup is then burned into the form document (see step 3).
This example only demonstrates converting text fields but could be updated to also convert other field types, such as signatures and checkboxes.
const convertForm = (fieldValues, formDefinitionId, res) => {
fs.readFile(`${__dirname}/FormDefinitions/${formDefinitionId}.json`, 'utf8', function (err, data) {
const formDefinition = JSON.parse(data);
let marks = [];
const globalFontName = (formDefinition.globalSettings && formDefinition.globalSettings.fontName) || 'Fira Sans';
const globalFontColor = (formDefinition.globalSettings && formDefinition.globalSettings.fontColor) || '#000000';
formDefinition.formData.forEach(field => {
if (field.template === 'TextTemplate') {
let mark = {};
if (field.multiline) {
mark.type = 'TextAreaSignature';
mark.maxFontSize = field.fontSize || 8;
mark.fontStyle = [];
} else {
mark.type = 'TextInputSignature';
}
mark.uid = field.fieldId;
mark.interactionMode = 'Full';
mark.creationDateTime = '2019-06-25T19:28:13.396Z';
mark.modificationDateTime = '2019-06-25T19:28:13.396Z';
mark.mask = null;
mark.maxLength = 0;
mark.rectangle = { x: field.rectangle.x, y: field.rectangle.y, width: field.rectangle.width, height: field.rectangle.height };
mark.pageData = { width: field.pageData.width, height: field.pageData.height };
mark.pageNumber = field.pageNumber;
mark.fontColor = (field.fontColor === 'UseGlobalFontColorSetting') ? globalFontColor : field.fontColor;
mark.fontName = (field.fontName === 'UseGlobalFontNameSetting') ? globalFontName : field.fontName;
mark.horizontalAlignment = field.horizontalAlignment ? (field.horizontalAlignment.charAt(0).toUpperCase() + field.horizontalAlignment.slice(1)) : 'Left';
// If a field value is not provided, this example uses the value of "example".
mark.text = (fieldValues[field.fieldId] !== undefined) ? fieldValues[field.fieldId] : 'example';
marks.push(mark);
}
});
// See step 3 for the implementation of burnForm.
burnForm(marks, formDefinition.templateDocumentId, res);
});
};
3. Burn the Markup into the Form
Use the PrizmDoc Server MarkupBurner API to burn the markup into the form document, as shown below. The burned document is returned in the response to the formBurner request.
const burnForm = async (marks, documentName, res) => {
const { affinityToken, markupFileId } = await postMarkup(marks);
console.log(`markupFileId: ${markupFileId}`);
const documentFileId = await postDocument(documentName, affinityToken);
console.log(`documentFileId: ${documentFileId}`);
const processId = await postBurner(documentFileId, markupFileId, affinityToken);
console.log(`processId: ${processId}`);
const burnedDocumentFileId = await getBurner(processId, affinityToken);
console.log(`burnedDocumentFileId: ${burnedDocumentFileId}`);
const burnedDocument = await getBurnedDocument(burnedDocumentFileId, documentName, affinityToken);
res.end(burnedDocument, 'binary');
};
First, the markup and form document are uploaded to PrizmDoc Server as “work files”.
const postMarkup = async marks => {
const response = await axios({
url: `${host}/PCCIS/V1/WorkFile?FileExtension=json`,
data: {
marks
},
method: 'POST',
headers: {
'Content-Type': 'octet-stream',
'acs-api-key': apiKey
}
});
return { markupFileId: response.data.fileId, affinityToken: response.data.affinityToken };
};
const postDocument = async (documentName, affinityToken) => {
const response = await axios({
url: `${host}/PCCIS/V1/WorkFile`,
data: fs.readFileSync(__dirname + '/Documents/' + documentName),
method: 'POST',
headers: {
'Content-Type': 'octet-stream',
'acs-api-key': apiKey,
'Accusoft-Affinity-Token': affinityToken || ''
}
});
return response.data.fileId;
};
Next, a markup burner is created, using the uploaded markup and form document as input.
const postBurner = async (documentFileId, markupFileId, affinityToken) => {
const response = await axios({
url: `${host}/PCCIS/V1/MarkupBurner`,
data: {
'input': {
'documentFileId': documentFileId,
'markupFileId': markupFileId
}
},
method: 'POST',
headers: {
'Content-Type': 'application/json',
'acs-api-key': apiKey,
'Accusoft-Affinity-Token': affinityToken || ''
}
});
return response.data.processId;
};
Then, the status of the markup burner is checked until it’s completed.
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
};
const getBurner = async (processId, affinityToken) => {
const response = await axios({
url: `${host}/PCCIS/V1/MarkupBurner/${processId}`,
method: 'GET',
headers: {
'Content-Type': 'application/json',
'acs-api-key': apiKey,
'Accusoft-Affinity-Token': affinityToken || ''
}
});
console.log(`MarkupBurner percentComplete: ${response.data.percentComplete}`);
if (response.data.state === 'complete') {
return response.data.output.documentFileId;
}
if (response.data.state === 'error') {
return;
}
await sleep(1000);
return getBurner(processId, affinityToken);
};
Finally, the burned document is retrieved.
const getBurnedDocument = async (documentFileId, documentName, affinityToken) => {
const response = await axios({
url: `${host}/PCCIS/V1/WorkFile/${documentFileId}`,
method: 'GET',
responseType: 'arraybuffer',
headers: {
'Content-Type': 'application/pdf',
'acs-api-key': apiKey,
'Accusoft-Affinity-Token': affinityToken || ''
}
});
// Uncomment the line below to save the burned document to disk.
// fs.writeFileSync(`${__dirname}/${documentName}_burned.pdf`, response.data);
return response.data;
};
Using the Service
Once you’ve followed these steps, you can use the service to directly burn your user data into your forms.
Run “npm install” and then “node main.js” to run the service. Then make a GET request to http://localhost:3001/formBurner/{your-form-id} to get a PDF of your form with filled data burned in.
The example code and more information are available in the Accusoft prizmdoc-form-burner-example GitHub repository.
Jared Jacoby, Software Engineer, PrizmDoc
Jared Jacoby has worked at Accusoft since 2005. Starting as a support engineer and promoted to a software engineer a couple years later, Jared has worked on numerous Accusoft products including the ImagXpress family, ImageGear, and PrizmDoc. He graduated from the University of Florida in 2004 with a degree in Computer Science. In his spare time, Jared enjoys playing board games, music, and writing.