Field Notes Template Generator
Live Template Preview
No fields added yet. Go to the 'Template Configuration' tab to build
your notes template.
No fields added yet.
";
return;
}
templateFields.forEach((field, index) => {
const item = document.createElement("div");
item.className = "fntg-field-item";
const typeMap = {
'header': 'Header',
'text_single': 'Single-Line Text',
'text_multi': 'Multi-Line Text',
'checkbox': 'Checkbox Group'
};
item.innerHTML = `
${escapeHTML(field.label)}
${typeMap[field.type]}
`;
fieldList.appendChild(item);
});
// Add listeners to new buttons
fieldList.querySelectorAll('.fntg-edit-btn').forEach(b => b.addEventListener('click', () => handleEditClick(b.dataset.id)));
fieldList.querySelectorAll('.fntg-delete-btn').forEach(b => b.addEventListener('click', () => handleDeleteClick(b.dataset.id)));
fieldList.querySelectorAll('.fntg-move-up-btn').forEach(b => b.addEventListener('click', () => handleMoveClick(b.dataset.id, 'up')));
fieldList.querySelectorAll('.fntg-move-down-btn').forEach(b => b.addEventListener('click', () => handleMoveClick(b.dataset.id, 'down')));
}
/**
* Handles config form submission for add/edit.
*/
function handleFieldFormSubmit(e) {
e.preventDefault();
const id = fieldIdInput.value;
const field = {
type: fieldTypeSelect.value,
label: fieldLabelInput.value.trim(),
options: fieldOptionsInput.value.split('\n').filter(opt => opt.trim() !== "")
};
if (!field.label) {
alert("Please enter a field label.");
return;
}
if (id) { // Update
const index = templateFields.findIndex(f => f.id == id);
if (index !== -1) {
templateFields[index] = { ...templateFields[index], ...field };
}
} else { // Add new
field.id = Date.now();
templateFields.push(field);
}
renderAll();
resetForm();
}
/**
* Resets the config form.
*/
function resetForm() {
if (!formTitle || !fieldIdInput || !fieldForm || !saveBtn) return;
formTitle.textContent = "Add New Field";
fieldIdInput.value = "";
fieldForm.reset();
saveBtn.textContent = "Add Field";
toggleOptionsInput(); // Hides options group
}
/**
* Populates form for editing a field.
*/
function handleEditClick(id) {
const field = templateFields.find(f => f.id == id);
if (!field) return;
formTitle.textContent = "Edit Field";
fieldIdInput.value = field.id;
fieldTypeSelect.value = field.type;
fieldLabelInput.value = field.label;
fieldOptionsInput.value = field.options.join('\n');
saveBtn.textContent = "Update Field";
toggleOptionsInput();
fieldLabelInput.focus();
}
/**
* Deletes a field.
*/
function handleDeleteClick(id) {
if (confirm("Are you sure you want to delete this field?")) {
templateFields = templateFields.filter(f => f.id != id);
renderAll();
}
}
/**
* Moves a field up or down in the list.
*/
function handleMoveClick(id, direction) {
const index = templateFields.findIndex(f => f.id == id);
if (index === -1) return;
if (direction === 'up' && index > 0) {
[templateFields[index-1], templateFields[index]] = [templateFields[index], templateFields[index-1]];
} else if (direction === 'down' && index < templateFields.length - 1) {
[templateFields[index], templateFields[index+1]] = [templateFields[index+1], templateFields[index]];
}
renderAll();
}
// --- Tab 1: Dashboard Logic ---
/**
* Renders the live preview form.
*/
function renderPreview() {
if (!previewArea || !placeholderMsg) return;
previewArea.innerHTML = ""; // Clear
if (templateFields.length === 0) {
placeholderMsg.style.display = "block";
downloadPdfBtn.style.display = "none";
} else {
placeholderMsg.style.display = "none";
downloadPdfBtn.style.display = "inline-block";
templateFields.forEach(field => {
switch (field.type) {
case 'header':
const header = document.createElement("h3");
header.className = "fntg-preview-header";
header.textContent = field.label;
previewArea.appendChild(header);
break;
case 'text_single':
const singleWrapper = document.createElement("div");
singleWrapper.className = "fntg-preview-field";
singleWrapper.innerHTML = `
`;
previewArea.appendChild(singleWrapper);
break;
case 'text_multi':
const multiWrapper = document.createElement("div");
multiWrapper.className = "fntg-preview-field";
multiWrapper.innerHTML = `
`;
previewArea.appendChild(multiWrapper);
break;
case 'checkbox':
const checkWrapper = document.createElement("div");
checkWrapper.className = "fntg-preview-field";
const checkLabel = document.createElement("label");
checkLabel.textContent = escapeHTML(field.label);
checkWrapper.appendChild(checkLabel);
const checkGroup = document.createElement("div");
checkGroup.className = "fntg-checkbox-group";
field.options.forEach((opt, index) => {
const item = document.createElement("div");
item.className = "fntg-checkbox-item";
item.innerHTML = `
`;
checkGroup.appendChild(item);
});
checkWrapper.appendChild(checkGroup);
previewArea.appendChild(checkWrapper);
break;
}
});
}
}
/**
* Generates and downloads a printable PDF template.
*/
function downloadPDF() {
if (typeof jspdf === "undefined") {
alert("Error: PDF generation library failed to load.");
return;
}
try {
const doc = new jsPDF();
const pageMargin = 20;
const pageWidth = doc.internal.pageSize.getWidth() - pageMargin * 2;
const lineSpacing = 7;
let yPos = 25;
const title = "Field Notes";
doc.setFont("helvetica", "bold");
doc.setFontSize(20);
doc.setTextColor("#004d40");
doc.text(title, pageMargin, yPos);
yPos += 15;
const checkPageBreak = () => {
if (yPos > 270) {
doc.addPage();
yPos = 20;
}
};
templateFields.forEach(field => {
checkPageBreak();
switch (field.type) {
case 'header':
doc.setFont("helvetica", "bold");
doc.setFontSize(14);
doc.setTextColor("#004d40");
yPos += lineSpacing * 1.5; // Extra space before header
doc.text(field.label, pageMargin, yPos);
doc.setDrawColor(200);
doc.line(pageMargin, yPos + 2, pageMargin + pageWidth, yPos + 2);
yPos += lineSpacing * 1.5;
break;
case 'text_single':
doc.setFont("helvetica", "normal");
doc.setFontSize(11);
doc.setTextColor("#333");
const labelWidth = doc.getTextWidth(field.label) + 2;
doc.text(field.label, pageMargin, yPos);
// Draw line
doc.setDrawColor(150);
doc.line(pageMargin + labelWidth, yPos, pageMargin + pageWidth, yPos);
yPos += lineSpacing * 1.5;
break;
case 'text_multi':
doc.setFont("helvetica", "normal");
doc.setFontSize(11);
doc.setTextColor("#333");
doc.text(field.label, pageMargin, yPos);
yPos += lineSpacing;
// Draw box
doc.setDrawColor(150);
doc.rect(pageMargin, yPos, pageWidth, 40); // 40px height for box
yPos += 40 + lineSpacing;
break;
case 'checkbox':
doc.setFont("helvetica", "normal");
doc.setFontSize(11);
doc.setTextColor("#333");
doc.text(field.label, pageMargin, yPos);
yPos += lineSpacing;
let xPos = pageMargin + 5;
doc.setDrawColor(100);
field.options.forEach(opt => {
const optWidth = doc.getTextWidth(opt) + 10;
if (xPos + optWidth > pageMargin + pageWidth) {
xPos = pageMargin + 5;
yPos += lineSpacing;
}
checkPageBreak();
doc.rect(xPos, yPos - 4, 4, 4); // Checkbox
doc.text(opt, xPos + 6, yPos);
xPos += optWidth;
});
yPos += lineSpacing * 1.5;
break;
}
});
doc.save("Field-Notes-Template.pdf");
} catch (error) {
console.error("Failed to generate PDF:", error);
alert("An error occurred while generating the PDF.");
}
}
/**
* Utility to escape HTML.
*/
function escapeHTML(str) {
if (!str) return "";
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// --- Event Listeners ---
if (tabButtons[1]) tabButtons[1].onclick = () => showTab(1);
if (tabButtons[2]) tabButtons[2].onclick = () => showTab(2);
if (prevBtn) prevBtn.onclick = () => showTab(currentTab - 1);
if (nextBtn) nextBtn.onclick = () => showTab(currentTab + 1);
// Tab 1
if (downloadPdfBtn) downloadPdfBtn.addEventListener("click", downloadPDF);
// Tab 2
if (fieldForm) fieldForm.addEventListener("submit", handleFieldFormSubmit);
if (clearBtn) clearBtn.addEventListener("click", resetForm);
if (fieldTypeSelect) fieldTypeSelect.addEventListener("change", toggleOptionsInput);
// --- Initialization ---
function init() {
loadSampleData();
renderAll();
showTab(1); // Set initial tab state
}
init();
});