Dynamic table on PDF report

Hi Developers
I want to dynamically display data in HTML table in PDF report.
The report already has another table (static)

Thank you

Hello @desiremuson

You may reference the CloudCode template for PDF reports (see screenshot) to implement dynamic tables using Handlebars.js

Let us know if this is helpful or if you have additional questions.

Screenshot 2022-11-07 at 3.58.24 PM

Hi @travisroach
I created the Cloudcode task to generate PDF report. I want to display data in table.
Since the number of rows will not always be the same, I want the table to be dynamic.

e.g: The PDF has 2 tables. The user on the 1st table and the second all the customers that belong to that user.

Thank you

 <model name="user" label="User">             
        <field name="name" label="Name" type="text:name"/>
        <field name="email" label="Email address" type="text:email" />       
        <has-many model="customer" name="Customer" />      
        <display>{name}</display>
</model>

 <model name="customer" label="Customer">             
        <field name="firstName" label="Name" type="text:name"/>
        <field name="surname" label="Name" type="text:name"/>
        <field name="email" label="Email address" type="text:email" />       
        <belongs-to model="user" name="User" />       
        <display>{firstname}</display>
</model>

Hi @desiremuson

Here is a snippet using the example you’ve provided:

JS

const pdfTemplate = handlebars.compile(fs.readFileSync(__dirname + '/my_html_file.html', 'utf8'));
const CSS = handlebars.compile(fs.readFileSync(__dirname + '/my_css_file.html', 'utf8'));

let currentUser = await DB.user.first();
let customers = await DB.customer.where('user_id = ?', currentUser.id).toArray();

const data = {
    currentUser: currentUser,
    customers: customers
}

// Generate HTML using Handlebars
const pdfHtml = pdfTemplate({ data: data, css: CSS });

HTML

<h2>User Information</h2>
<table>
    <tr>
        <td style="width: 50%;">Name</td>
        <td style="width: 50%;">Email</td>
    </tr>
    <tr>
        <td style="width: 50%;">{{currentUser.name}}</td>
        <td style="width: 50%;">{{currentUser.email}}</td>
    </tr>
</table>

<h2>Customers</h2>
<table>
    <tr>
        <td style="width: 33%;">First Name</td>
        <td style="width: 33%;">Surname</td>
        <td style="width: 33%;">Email</td>
    </tr>
    {{#each data.customers}}
        <tr>
            <td style="width: 33%;">{{this.firstName}}</td>
            <td style="width: 33%;">{{this.surname}}</td>
            <td style="width: 33%;">{{this.email}}</td>
        </tr>
    {{/each}}
</table>
2 Likes

Dear @travisroach
Based on the above example, the customer and user models have image fields.
When displaying on PDF, the customer image shows a string while user image shows the picture.

Below is my code.

const data = {
    currentUser: currentUser,
     customers: customers
}
{{#each data.customers}}
        <tr>
            <td style="width: 25%;">{{this.firstName}}</td>
            <td style="width: 25%;">{{this.surname}}</td>
            <td style="width: 25%;">{{this.email}}</td>
            <td style="width: 25%;">
        <img src="{{this.image}}" onerror="this.style.display='none'" width="150" height="150">
         </td>
        </tr>
    {{/each}}

Hi @desiremuson

You may compile the image fields to base64, then pass the data strings however is best fit for your use case. We tend to store them by reference to the image object ID, then register a Handlebars helper function like so:

JS

let photosLookup = {};
for (let customer of customers) {
    photosLookup[customer.id] = 'data:image/png;, ' + await customer.image.toBase64();
}

handlebars.registerHelper('getPhoto', function(customerId) {
    return photosLookup[customerId];
});

const data = {
    currentUser: currentUser,
    customers: customers,
    customerPhotos: photosLookup
}

HTML

<img src="{{getPhoto this.id}}" alt="customer photo" width="100px" />

@desiremuson You could alternatively use a reference to the image URL. The tradeoff being less data needing to be passed into the HTML, but the URL requests would need to resolve during report generation.

JS

let photosLookup = {};
for (let customer of customers) {
    photosLookup[customer.id] = customer.image.url();
}

handlebars.registerHelper('getPhoto', function(customerId) {
    return photosLookup[customerId];
});

const data = {
    currentUser: currentUser,
    customers: customers,
    customerPhotos: photosLookup
}

HTML

<img src="{{getPhoto this.id}}" alt="customer photo" width="100px" />
1 Like