Designing Print-Ready Components in Your Web App
Tags: webdev, print, frontend, javascript
Published: 2024-06-05 21:02:01

In many web applications, there comes a time when you need to add print functionality. Whether it's for printing invoices, reports, or any other custom components, having a seamless and efficient print solution is crucial. In this blog post, I'll demonstrate how to handle printing in your Vue.js application. The approach we'll cover is also applicable to other frameworks, enabling you to design your print components directly in Vue and print them without the need for manual HTML and CSS in a custom script tag.

## Approach Explanation

1. Add Specific Route for Print: Create a dedicated route for your print component to ensure it opens in a new window with the correct context.
2. Design Your Print Component: Style your print component with all necessary elements, ensuring it appears exactly as desired for printing.
3. Open Print Page in New Window: Use JavaScript to open the print route in a new window, providing a seamless transition for users.
4. Print the Component on Mounted: Trigger the print function directly once the component is mounted, ensuring the print dialog appears immediately.
5. Close the Window After Print: Automatically close the print window after the user completes the print action, enhancing the user experience.

## Handling Print in Vue.js
First, let's create a Vue component dedicated to printing. This component will be responsible for rendering the content to be printed and initiating the print action, it is just like a template, The template will contain the content we want to print, including an image and a table with sample data.

```
<script setup lang="ts">
import { DUMMY_DATA } from '@/DUMMY';
import { ref, watch } from 'vue';

/**
 * This Component is only for printing
 * Design  in vuejs and print what you see
 */

const imageLoaded = ref(false);

// Once header image loaded print & close window
watch(imageLoaded, () => {
  window.print();
  window.close();
});
</script>

<template>
  <Teleport to="body">
    <div class="modal" style="position: absolute; top: 0; left: 0; width: 100%">
      <div id="printContainer" style="background-color: white">
        <table style="width: 100%">
          <thead>
            <tr>
              <td colspan="7" class="invoice-header">
                <div class="header-container">
                  <div>
                    <img
                      width="100"
                      src="@/assets/logo.svg"
                      @load="imageLoaded = true"
                    />
                  </div>
                  <h1>Your Company</h1>
                  <h3>Your Address</h3>
                </div>
              </td>
            </tr>
            <tr>
              <th>Product</th>
              <th>Unit Price</th>
              <th>Amount</th>
              <th>Total Price</th>
            </tr>
          </thead>

          <tbody>
            <tr v-for="item in DUMMY_DATA" :key="item.id">
              <td>{{ item.product }}</td>
              <td>{{ item.unitPrice }}</td>
              <td>{{ item.amount }}</td>
              <td>{{ item.totalPrice }}</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </Teleport>
</template>
```

Here I've a table that I want to display some data, note that I've used an image in my invoice, so the approach is directly printing the opened window, inorder to make sure image is loaded I've added a simple state to indicate image is loaded or not, so only print after image is completly loaded, you can omit this if you don't have image on your template and directly onMounted the component print that window.

Also note that I use Teleport to move the content to the body for printing.

And here is the important part which is CSS specific styles for printing

```
<style>
/* General print styles here  */
#printContainer {
  table {
    width: 100%;
    border-collapse: collapse;
    break-inside: auto;
    page-break-inside: auto;
  }
  .header-container {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  table td,
  table th {
    padding: 1.5mm;
    border: 2px solid #ccc;
    border: 2px solid #ccc;
    vertical-align: top;
    font-size: inherit;
  }
  table td.invoice-header {
    border: none;
  }

  table th {
    text-align: left;
    vertical-align: bottom;
    color: rgb(0, 0, 30);
    background-color: #04aa6d;
  }

  tr:nth-child(even) {
    background-color: #f2f2f2;
  }

  tr:hover {
    background-color: #ddd;
  }

  thead {
    display: table-header-group;
  }
  tfoot {
    display: table-footer-group;
  }
  tr {
    page-break-inside: avoid;
    page-break-after: auto;
  }
  table td,
  table th,
  table tr {
    /* Prevent elements from being split across pages in paginated media (like print) */
    break-inside: avoid;
    /* Automatically insert a page break after the element, if needed */
    break-after: auto;
  }
}
/* Apply styles only when the document is being printed */
@media print {
  /* Apply styles to the printed page */
  @page {
    size: auto;
    /* Set the page margins, hide default header and footer */
    margin: 0.15in 0.3in 0.15in 0.3in !important;
  }

  body {
    /* Ensure that colors are printed exactly as they appear on the screen */
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}
</style>

```

## Importance of Print-Specific Styles
When implementing print functionality in your web application, it's essential to define specific styles that ensure your content is presented correctly when printed. Here, we'll discuss the crucial styles used in our Vue.js print component and their importance in achieving a high-quality printed output.

```
 table {
    width: 100%;
    border-collapse: collapse;
    break-inside: auto;
    page-break-inside: auto;
  }
```
`break-inside: auto;` `page-break-inside: auto;` Prevents elements from being split across pages.

## Repeat the header and footer when printing
```
thead {
  display: table-header-group;
}
tfoot {
  display: table-footer-group;
}
```
`display: table-header-group;` `display: table-footer-group;` Ensures the table headers and footers are repeated on each printed page.

## Prevent split rows across pages

```
tr {
  page-break-inside: avoid;
  page-break-after: auto;
}
```

`page-break-inside: avoid;` Prevents rows from being split across pages.
`page-break-after: auto;` Automatically inserts a page break after the row if needed.

## Print Media Query

```
@media print {
  @page {
    size: auto;
    margin: 0.15in 0.3in 0.15in 0.3in !important;
  }

  body {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}
```
## Remove default header and footer when printing
Using margin on printed page we can hide the default header and footer of the page, here is the worked value for me is  `margin: 0.15in 0.3in 0.15in 0.3in`

## Print exact colors in the page
`print-color-adjust: exact; -webkit-print-color-adjust: exact;` Ensures that colors are printed exactly as they appear on the screen, maintaining the intended design.

## Conclusion
In this article, we've covered how to handle printing in your Vue.js application by defining print-specific styles and ensuring components are print-friendly. This approach, applicable to other frameworks as well, helps create a seamless print experience. Key elements include setting appropriate print styles, managing page breaks, and ensuring color accuracy. For a complete working sample and detailed implementation, check out the [GitHub repository](https://github.com/Joseph42A/Printing-with-VueJS). Thank you for reading!