safeer-pdf-generator
Version:
Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery
167 lines (151 loc) • 6.2 kB
JavaScript
// Custom Template Examples
// Shows three approaches: inline function, customCss/customHtml, and template registry
import { generatePdf, registerTemplate, consoleLogger } from 'safeer-pdf-generator';
const sampleData = [
{ name: 'Alice Johnson', department: 'Engineering', salary: 95000, status: true },
{ name: 'Bob Smith', department: 'Marketing', salary: 72000, status: true },
{ name: 'Carol White', department: 'Sales', salary: 68000, status: false },
];
const columns = [
{ key: 'name', title: 'Employee', dataIndex: 'name' },
{ key: 'department', title: 'Department', dataIndex: 'department' },
{ key: 'salary', title: 'Salary', dataIndex: 'salary' },
{ key: 'status', title: 'Active', dataIndex: 'status' },
];
// ── Approach 1: Use a built-in template with customCss and customHtml ──
async function withCustomCss() {
const result = await generatePdf({
title: 'Employee Report (Custom Styled)',
data: sampleData,
columns,
template: 'modern', // or 'default' or 'simple'
customCss: `
.modern-table th {
background: linear-gradient(135deg, #7c3aed, #2563eb) !important;
color: #fff !important;
}
.row-even { background: #faf5ff !important; }
`,
customHtml: {
beforeTable: `
<div style="background: #f5f3ff; border-left: 4px solid #7c3aed; padding: 12px; margin-bottom: 12px; border-radius: 4px;">
<strong>Note:</strong> This report covers all employees as of Q4 2025.
</div>
`,
afterTable: `
<div style="margin-top: 16px; padding: 12px; background: #f8fafc; border-radius: 4px; font-size: 11px; color: #64748b;">
Generated by HR Department • Confidential
</div>
`,
},
logging: consoleLogger,
});
console.log(`✅ Custom CSS example: ${result.fileName} (${result.pageCount} pages)`);
}
// ── Approach 2: Pass a custom template function inline ──
async function withInlineTemplate() {
const result = await generatePdf({
title: 'Employee Directory',
data: sampleData,
columns,
// Pass a function directly — full control over HTML
template: (params) => {
const { title, data, columns } = params;
const html = `
<html>
<head>
<meta charset="UTF-8">
<title>${title}</title>
<style>
body { font-family: Georgia, serif; margin: 0; padding: 20px; }
h1 { color: #1a1a2e; border-bottom: 3px double #1a1a2e; padding-bottom: 8px; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th { background: #1a1a2e; color: #fff; padding: 12px 8px; text-align: left; }
td { padding: 10px 8px; border-bottom: 1px solid #eee; }
tr:nth-child(even) { background: #f9f9f9; }
</style>
</head>
<body>
<h1>${title}</h1>
<p style="color: #666;">Total records: ${data.length}</p>
<table>
<thead>
<tr>${columns.map(c => `<th>${c.title}</th>`).join('')}</tr>
</thead>
<tbody>
${data.map(row => `
<tr>${columns.map(c => `<td>${row[c.dataIndex] ?? '—'}</td>`).join('')}</tr>
`).join('')}
</tbody>
</table>
</body>
</html>
`;
return {
html,
header: '', // Custom header for Puppeteer (or empty)
footer: '',
};
},
logging: consoleLogger,
});
console.log(`✅ Inline template example: ${result.fileName} (${result.pageCount} pages)`);
}
// ── Approach 3: Register a reusable template in the global registry ──
async function withRegisteredTemplate() {
// Register once — available everywhere
registerTemplate('branded', (params) => {
const { title, data, columns, userInfo = {} } = params;
const html = `
<html>
<head>
<meta charset="UTF-8">
<title>${title}</title>
<style>
body { font-family: 'Segoe UI', sans-serif; margin: 0; padding: 24px; }
.brand-bar { background: #0f172a; color: #fff; padding: 16px 24px; border-radius: 8px; margin-bottom: 20px; }
.brand-bar h1 { margin: 0; font-size: 18px; }
.brand-bar .subtitle { color: #94a3b8; font-size: 12px; margin-top: 4px; }
table { width: 100%; border-collapse: collapse; }
th { background: #f1f5f9; padding: 10px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #475569; }
td { padding: 10px; border-bottom: 1px solid #f1f5f9; font-size: 12px; }
</style>
</head>
<body>
<div class="brand-bar">
<h1>${title}</h1>
<div class="subtitle">Prepared for ${userInfo.name || 'Management'} • ${new Date().toLocaleDateString()}</div>
</div>
<table>
<thead><tr>${columns.map(c => `<th>${c.title}</th>`).join('')}</tr></thead>
<tbody>
${data.map(row => `
<tr>${columns.map(c => `<td>${row[c.dataIndex] ?? '—'}</td>`).join('')}</tr>
`).join('')}
</tbody>
</table>
</body>
</html>
`;
return { html, header: '', footer: '' };
});
// Now use it by name
const result = await generatePdf({
title: 'Quarterly Review',
data: sampleData,
columns,
template: 'branded', // Refers to the registered template
userInfo: { name: 'Hassan', email: 'hassan@example.com' },
logging: consoleLogger,
});
console.log(`✅ Registered template example: ${result.fileName} (${result.pageCount} pages)`);
}
// Run all examples
async function main() {
await withCustomCss();
await withInlineTemplate();
await withRegisteredTemplate();
}
main().catch(console.error);