Skip to content
On this page

HTML Template Example

A Simple HTML Template Using Vue.js and Paged.js

Below is a complete template example. A full example is often easier to understand than reading through documentation alone. This example uses Vue.js to hydrate the template with data and Paged.js to control the page layout.

TIP

Comments are included to help you understand how data injection works. Feel free to adapt it to your needs

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example invoice template with Vue3 and Paged.js</title>
<style>
/* Specifics CSS for PagedJS */
@page {
    size: A4;
    margin: 20mm;
    /* Top-center Margin with PagedJS */
    @top-center {
        vertical-align: center;
        text-align: center;
        background: #322d48;
        color: white;
        content: "Invoice top-center margin";
    }
    /* Counter page in bottom-right corner with PagedJS */
    @bottom-right-corner {
        vertical-align: center;
        text-align: center;
        background: #322d48;
        color: white;
        content: "P. " counter(page);
    }
}
h2 {
    break-before: page;
}
</style>
</head>
<body>

<div id="app">
    <h3>Example invoice template with Vue3 and Paged.js</h3>
    <p>
        This template uses the Vue 3 templating engine to integrate data into the invoice structure,
        while Paged.js provides sophisticated page layout controls, ensuring every document is well rendered for Doppio's rendering (format, margin, page counter ...).
    </p>
    <p>
        Please note that JavaScript is not executed in this preview.
        To see the true result, including the execution of JavaScript, you can click the "Real Doppio Render" button.
    </p>
    <div class="invoice-header">
        <p>Your Company</p>
        <p>Address</p>
        <p>Phone</p>

        <!-- Simple variable hydratation by Doppio (in "templateVariables") -->
        <p>{* DOP_COUNTRY *}</p> <!-- This will be replaced by the DOP_COUNTRY value -->
    </div>

    <div class="invoice-body">
        <table>
            <tr>
                <th>Article</th>
                <th>Description</th>
                <th>Quantity</th>
                <th>Unit Price ($)</th>
                <th>Total ($)</th>
            </tr>
            <tr v-for="item in invoiceItems" :key="item.id">
                <td>{{ item.name }}</td>
                <td>{{ item.description }}</td>
                <td>{{ item.quantity }}</td>
                <td>${{ item.unitPrice.toFixed(2) }}</td>
                <td>${{ (item.quantity * item.unitPrice).toFixed(2) }}</td>
            </tr>
        </table>
        <p>Total : ${{ total.toFixed(2) }}</p>
    </div>

    <h2>Yeah new page!</h2>

    <div class="invoice-footer">
        <p>Thank you for your purchase!</p>
        <p>Serving up <strong>PDF solutions</strong> double shot, no waiting! ☕⏱️📖</p>
        <img src="https://doppio.sh/logo.png" />
    </div>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
<script>
// Data from Doppio
const invoiceItems = {* DOP_INVOICE_ITEMS *}; // This will be replaced by the DOP_INVOICE_ITEMS value; DOP_INVOICE_ITEMS is not a string but an array of objects; invoiceItems will be a literal array of objects here.

// Now remember that {* DOP_COUNTRY *} you saw in the template? It will be replaced by the literal DOP_COUNTRY value.
// What it means is that if you want to use the DOP_COUNTRY value inside the JS, you need to add quotes around it.

// const country = "{* DOP_COUNTRY *}"; // This will be replaced by the DOP_COUNTRY value; DOP_COUNTRY is a string; country will be a literal string here; if quotes are removed it will not create valid JS code.

// If you want, you can also use window.doppioData to access any of your data, as parsed objects.
// const myData = window.doppioData;
// const country = myData.DOP_COUNTRY;
// const invoiceItems = myData.DOP_INVOICE_ITEMS;

// It's up to you to choose the method that suits you best.

// Initialize a small vue app for templating
const app = Vue.createApp({
    data() {
        return {
            invoiceItems,
        };
    },
    computed: {
        total() {
            return this.invoiceItems.reduce((acc, item) => acc + (item.quantity * item.unitPrice), 0);
        }
    }
});

app.mount('#app');
</script>

<style>
     html, body {
        margin: 0;
        padding: 0;
        height: auto;
    }
    #app {
        padding: 0 10px;
    }
    body {
        font-family: Arial, sans-serif;
        background: #AAA0D4;
    }
    img {
        margin-top: 4rem;
        max-width: 200px;
    }
    .invoice-header, .invoice-body, .invoice-footer {
        width: 100%;
        margin: 50px 0;
    }
    .invoice-header p, .invoice-footer p {
        margin: 0;
        padding: 0;
    }
    .invoice-body table {
        width: 100%;
        border-collapse: collapse;
        margin-bottom: 20px;
        background: #5935DD;
        color: rgba(255, 255, 255, 0.7);
    }
    .invoice-body table, .invoice-body th, .invoice-body td {
        border: 1px solid #000;
    }
    .invoice-body th, .invoice-body td {
        padding: 8px;
        text-align: left;
        font-size: 12px;
    }
    .invoice-body td {
        background: #322d48;
    }
</style>
</body>
</html>

All rights reserved