Observation now updates the scoring fields.

This commit is contained in:
Wappler 2025-01-01 21:38:09 -06:00
parent 710c07a10f
commit f7b44e14a5
7 changed files with 233 additions and 78 deletions

View File

@ -0,0 +1,3 @@
{
"exec": {}
}

View File

@ -0,0 +1,110 @@
// JavaScript Document
console.log("LIBRARY: libPDFscripts.js")
//Use this function as a start - to pass the path, then datasource to update with pdfbytes, then download -- Maybe combine into one file?
//wich is more efficient?
let popover = new bootstrap.Popover(popoverTrigger, {
content: "This dropdown is disabled.",
placement: "top",
trigger: "manual"
});
//filepath = /PDF/XXX.pdf, dataSource = datastore.var
// *** Use of 'rest paramater' syntax to add extra datasources at the end.
//return array of datasources i.e [datastore,data_view1] and pass that array to updatePdfFields
//Consider default values = filepath = '/PDF/masterTemplate.pdf'
async function toBase64(filePath, ...dataSources) {
console.log(filePath);
const pdfBytes = await fetch(filePath).then((res) => res.arrayBuffer());
// console.log(dataSources); // should be only 2,
updatePdfFields(pdfBytes, dataSources);
//return pdfBytes
}
async function updatePdfFields(pdfBytes, dataSources) {
// Load the PDF document
console.log("pdfBytes.size =", pdfBytes.byteLength);
console.log(dataSources);
const pdfDoc = await PDFLib.PDFDocument.load(pdfBytes);
const form = pdfDoc.getForm();
// Loop over each data source passed to the function
dataSources.forEach((dataSource) => {
// Loop over each record in the current data source
dataSource.forEach((record) => {
// Loop over each key-value pair in the record
Object.entries(record).forEach(([fieldName, fieldValue]) => {
console.log(fieldName, " => ", fieldValue);
// Find the form field in the PDF by name
/** !!! BLOCK 1 Start
const formField = form.getTextField(fieldName);
// If the field exists in the PDF, update it with the value
if (formField) {
formField.setText(String(fieldValue));
}
**/ // BLOCK 1 END
});
});
});
// Serialize the PDF back to bytes
const updatedPdfBytes = await pdfDoc.save();
// Trigger download of the updated PDF
downloadPdf(updatedPdfBytes, "UpdatedDocument.pdf");
}
// Function to trigger download of the updated PDF
function downloadPdf(pdfBytes, fileName) {
// Create a Blob from the PDF bytes
const blob = new Blob([pdfBytes], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
// Create a link element for the download
const link = document.createElement("a");
link.href = url;
link.download = fileName;
// Append the link to the document, trigger a click, and remove it afterward
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Release the object URL to free up memory
URL.revokeObjectURL(url);
}
// Usage Example
async function main() {
// Load your PDF as bytes (e.g., using fetch)
const pdfBytes = await fetch("path/to/your.pdf").then((res) =>
res.arrayBuffer()
);
// Sample data sources from Wappler
const dataSource1 = [{ fieldName1: "Value1", fieldName2: "Value2" }];
const dataSource2 = [
{ fieldName3: "AnotherValue1", fieldName4: "AnotherValue2" },
];
// Call the function with multiple data sources
await updatePdfFields(pdfBytes, dataSource1, dataSource2);
}
function generateRandomFilename() {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let filename = "";
for (let i = 0; i < 8; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
filename += characters[randomIndex];
}
console.log("Random Filename: ", filename);
return filename;
}

View File

@ -40,6 +40,27 @@ dmx.config({
} }
], ],
"local": {} "local": {}
},
"data_view2": {
"meta": [
{
"name": "$id",
"type": "number"
},
{
"type": "text",
"name": "numSection"
},
{
"type": "text",
"name": "pointValue"
},
{
"type": "text",
"name": "description"
}
],
"outputType": "array"
} }
} }
}); });

View File

@ -49,18 +49,25 @@ async function fetchPdfBytes(filePath) {
} }
// Update form fields in the PDF // Update form fields in the PDF
async function updatePdfFields( async function updatePdfFields(
pdfBytes, pdfBytes,
dataSources dataSources,
//skipMissingFields = false
) { ) {
console.log("Datasources Count:", dataSources.length) console.log("Datasources Count:", dataSources.length)
console.log(JSON.stringify(dataSources, null, 2)); console.log(JSON.stringify(dataSources, null, 2));
try {
// try {
const pdfDoc = await PDFLib.PDFDocument.load(pdfBytes); const pdfDoc = await PDFLib.PDFDocument.load(pdfBytes);
const form = pdfDoc.getForm(); const form = pdfDoc.getForm();
dataSources.forEach((dataSource, dsIndex) => { dataSources.forEach((dataSource, dsIndex) => {
console.log(`Processing dataSource[${dsIndex}]:`, dataSource); console.log(`Processing dataSource[${dsIndex}]:`, dataSource);
@ -68,7 +75,7 @@ async function updatePdfFields(
console.warn(`dataSource[${dsIndex}] is not an array. Skipping.`); console.warn(`dataSource[${dsIndex}] is not an array. Skipping.`);
return; return;
} }
let ii = 0
dataSource.forEach((record, recIndex) => { dataSource.forEach((record, recIndex) => {
console.log(`Processing record[${recIndex}]:`, record); console.log(`Processing record[${recIndex}]:`, record);
@ -90,6 +97,7 @@ async function updatePdfFields(
const formField = form.getTextField(numSection); const formField = form.getTextField(numSection);
if (formField) { if (formField) {
ii++
formField.setText(String(pointValue)); formField.setText(String(pointValue));
console.log(`Field "${numSection}" updated with value "${pointValue}".`); console.log(`Field "${numSection}" updated with value "${pointValue}".`);
} else { } else {
@ -102,48 +110,22 @@ async function updatePdfFields(
console.warn(`Missing numSection or pointValue in record[${recIndex}].`); console.warn(`Missing numSection or pointValue in record[${recIndex}].`);
} }
}); });
console.log(`${ii} Form field updates complete.`);
}); });
/** return await pdfDoc.save(); /// MOVE TO the Main PROCESS and DOWNLOD PDF function or as last call. (need await??) Maybe make it an actual 'ready' to download button.
dataSources.forEach((dataSource) => {
//console.log("Current Datasource", dataSource)
dataSource.forEach((record) => {
console.log(record)
//console.log("Working in: ", record)
Object.entries(record).forEach(([fieldName, fieldValue]) => {
dd(`Field: ${fieldName} Value: ${fieldValue}`) //debugger
try {
console.log(`----------------> form ${fieldName}`)
const formField = form.getTextField(fieldName);
if (formField) {
// console.info(`Field "${fieldName}" OK`);
dd(`${fieldName} --> ${fieldValue} OK`);
formField.setText(String(fieldValue));
} else if (!skipMissingFields) {
//dd(`Skipping Field: ${fieldName} NOT FOUND`)
console.warn(`Field "${fieldName}" not found.`);
}
} catch (err) {
console.error(`Error updating field "${fieldName}":`); //console.error(`Error updating field "${fieldName}":`, err);
}
});
});
});
*/
// console.log(JSON.stringify(dataSources, null, 2));
return await pdfDoc.save();
} catch (err) {
console.error("Error updating PDF fields:", err);
throw err;
}
} }
// Function to trigger download of the updated PDF // Function to trigger download of the updated PDF
function downloadPdf(pdfBytes, fileName) { function downloadPdf(pdfBytes, fileName) {
const blob = new Blob([pdfBytes], { type: "application/pdf" }); const blob = new Blob([pdfBytes], { type: "application/pdf" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@ -151,7 +133,7 @@ function downloadPdf(pdfBytes, fileName) {
href: url, href: url,
download: fileName, download: fileName,
}); });
console.log("DOWNLOAD Filled PDF")
link.click(); link.click();
URL.revokeObjectURL(url); // Clean up the URL immediately URL.revokeObjectURL(url); // Clean up the URL immediately
} }
@ -164,7 +146,7 @@ function generateRandomFilename(length = 8, prefix = "", suffix = "") {
); );
const filename = `${prefix}${array.join("")}${suffix}`; const filename = `${prefix}${array.join("")}${suffix}`;
dd("Random Filename:", filename); //dd("Random Filename:", filename);
return filename; return filename;
} }

View File

@ -1,10 +1,43 @@
<!doctype html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <base href="/">
<title>Untitled Document</title> <meta charset="UTF-8">
<!-- New Wappler Page --> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Form Fields</title>
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.js"></script>
</head> </head>
<body> <body>
<h1>Hardcoded PDF Form Fields</h1>
<ul id="field-list"></ul>
<script>
const filePath = "/PDF/Template-EDV-ERT_v4.1.pdf"
const pdfBytes = await fetchPdfBytes(filePath);
// Function to load a PDF and list all form fields
async function listPdfFields(pdfBytes) {
const pdfDoc = await PDFLib.PDFDocument.load(pdfBytes);
const form = pdfDoc.getForm();
const fields = form.getFields();
const fieldList = document.getElementById('field-list');
fieldList.innerHTML = '';
fields.forEach(field => {
const type = field.constructor.name;
const name = field.getName();
const listItem = document.createElement('li');
listItem.textContent = `Field: ${name}, Type: ${type}`;
fieldList.appendChild(listItem);
});
}
// Fetch the hardcoded PDF from a URL (this can be a server-hosted or local file)
fetch('/PDF/.pdf')
.then(response => response.arrayBuffer())
.then(pdfBytes => listPdfFields(pdfBytes))
.catch(error => console.error('Error loading PDF:', error));
</script>
</body> </body>
</html> </html>

View File

@ -10,7 +10,7 @@
columns: [ columns: [
{table: "da", column: "db_signatureData"} {table: "da", column: "db_signatureData"}
], ],
table: {name: "da"}, table: {name: "db_training", alias: "da"},
primary: "_id", primary: "_id",
joins: [], joins: [],
wheres: { wheres: {

View File

@ -1,4 +1,5 @@
<!-- Wappler include head-page="layouts/main" fontawesome_5="cdn" bootstrap5="local" is="dmx-app" id="observationPage" appConnect="local" components="{dmxBootstrap5TableGenerator:{},dmxMasonry:{},dmxFormatter:{},dmxBootstrap5Popovers:{},dmxBootstrap5Toasts:{},dmxDataTraversal:{},dmxStateManagement:{},dmxDatastore:{},dmxValidator:{},dmxBootstrap5Offcanvas:{},dmxBootstrap5Modal:{},dmxBootstrap5Navigation:{},dmxBootstrap5Tooltips:{},dmxBootbox5:{},dmxNotifications:{},dmxPouchDB:{},dmxBootstrap5Alert:{},dmxBootstrap5Collapse:{}}" jquery_slim_35="cdn" moment_2="cdn" --> <!-- Wappler include head-page="layouts/main" fontawesome_5="cdn" bootstrap5="local" is="dmx-app" id="observationPage" appConnect="local" components="{dmxBootstrap5TableGenerator:{},dmxMasonry:{},dmxFormatter:{},dmxBootstrap5Popovers:{},dmxBootstrap5Toasts:{},dmxDataTraversal:{},dmxStateManagement:{},dmxDatastore:{},dmxValidator:{},dmxBootstrap5Offcanvas:{},dmxBootstrap5Modal:{},dmxBootstrap5Navigation:{},dmxBootstrap5Tooltips:{},dmxBootbox5:{},dmxNotifications:{},dmxPouchDB:{},dmxBootstrap5Alert:{},dmxBootstrap5Collapse:{}}" jquery_slim_35="cdn" moment_2="cdn" -->
<dmx-data-view id="data_view2" dmx-bind:data="datastore1.data" filter="!numSection.isEmpty()&amp;&amp;!pointValue.isEmpty()"></dmx-data-view>
<dmx-datastore id="datastore1"></dmx-datastore> <dmx-datastore id="datastore1"></dmx-datastore>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-timepicker/1.9.1/jquery.timepicker.min.css" integrity="sha512-UimcIlYKETYXXjgBbgq45ZXCLXI+d1O43cZXVTdhtCYYGomJVM2Ahz+L19UEWBIH4f/A/qmlvCaOWiaNMcaw3w==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-timepicker/1.9.1/jquery.timepicker.min.css" integrity="sha512-UimcIlYKETYXXjgBbgq45ZXCLXI+d1O43cZXVTdhtCYYGomJVM2Ahz+L19UEWBIH4f/A/qmlvCaOWiaNMcaw3w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
@ -309,7 +310,12 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="container"></div> <div class="container">
<div class="row">
<div class="col">
</div>
</div>
</div>
<div class="container" dmx-show="toggle1.checked"> <div class="container" dmx-show="toggle1.checked">
<div class="row" dmx-hide="offcanvas1.CKBinput2.checked"> <div class="row" dmx-hide="offcanvas1.CKBinput2.checked">
<div class="col-5"> <div class="col-5">
@ -360,7 +366,7 @@
<td> <td>
<!--select id="selPoints" class="form-select" name="sPoints" dmx-on:changed="datastore1.upsert({numSection: numSection, $id: numSection, description: txtSection},{numSection: numSection, Points: value, description: txtSection})" dmx-bind:id="numSection" dmx-bind:value="selectedValue"--> <!--select id="selPoints" class="form-select" name="sPoints" dmx-on:changed="datastore1.upsert({numSection: numSection, $id: numSection, description: txtSection},{numSection: numSection, Points: value, description: txtSection})" dmx-bind:id="numSection" dmx-bind:value="selectedValue"-->
<select id="selPoints" class="form-select" name="sPoints" dmx-bind:id="numSection" dmx-bind:value="selectedValue" dmx-on:changed="datastore1.upsert({numSection: numSection},{numSection: numSection, pointValue: selectedValue, description: txtSection})"> <select id="selPoints" class="form-select" name="sPoints" dmx-bind:id="" dmx-bind:value="selectedValue" dmx-on:changed="datastore1.upsert({numSection: numSection},{numSection: numSection, pointValue: selectedValue, description: txtSection})">
<option value="0">0 points</option> <option value="0">0 points</option>
<option value="1">1 point</option> <option value="1">1 point</option>