April 22, 2017

Beautifying Tables when Exporting PDF with Laravel

In many occation, my clients wanted their data to be exported into a PDF file so that it is portable and ready to print whenever the needed to. There are times when we, as a developer, need to generate those data in form of tables. The tricky part is to make tables look nice in a PDF file when exporting data with the help of PHP. In this article I am going to share some experience to make your tables look nice on a export-to-pdf process.

 Suppose that a client has an employees table that he wants to export into a PDF file with table-formatted like this:
Employees table

Step 1 : Add an "Export to PDF" Button

The first thing that we should do, of course, is to add an "Export to PDF" button somewhere on the page. In this case, I put it below the pagination buttons. Open the view file and add these html lines:

<div class="text-center">
    <a href="{{ url('/employees/export-to-pdf') }}" class="btn btn-primary">
        Export to PDF
    </a>
</div>

Notice that the href attribute of the anchor tag (the button) is leading to /employees/export-to-pdf. So later we need to make a route for it. For now, let's just save the view file, and refresh the page in the browser. It should look like this:

An "Export to PDF" button has been added

Step 2 : Install a PDF Library

Next we need to install a tcpdf library that works well with laravel. I'm not recommending any specific package, but since use it in most of my projects, I will be using tcpdf-laravel from elibyy throughout this article.

Open the terminal, enter your laravel project directory, and run this artisan command:

composer require elibyy/tcpdf-laravel

After composer is done installing the package, open up the config/app.php file, and add this line somewhere inside the providers array

Elibyy\TCPDF\ServiceProvider::class,

Also, add this line somewhere inside the aliases array:

'PDF' => Elibyy\TCPDF\Facades\TCPDF::class,

Now save the file and open the routes/web.php file.

Step 3 : Add a Route

Add this line to make a route for our /employees/export-to-pdf url:

Route::get('/employees/export-to-pdf', 'EmployeeController@exportToPdf');
Note that if you don't have the controller, create it before you add the route. This is how you create a controller from the terminal:

php artisan make:controller EmployeeController

Now save the file, and get ready put some code in the controller.

Step 4 : Add a Method in The Controller

Next, open up the app/Http/Controllers/EmployeeController.php file, and add a public method as we defined previously in the route.

public function exportToPdf()
{
    $employees = \App\User::all();
    $html = view('employees-pdf', compact('employees'));

    PDF::SetTitle('Employees Table');
    PDF::SetPrintHeader(false);
    PDF::SetPrintFooter(false);
    PDF::SetMargins(20, 10, 15);
    PDF::AddPage('P','LEGAL');

    PDF::writeHTML($html, true, false, true, false, '');
    $filename = public_path() . '/files/employees.pdf';
    PDF::output($filename,'I');
}

Here is how it works:
  • The first two lines of the method, are to gather all of the employees data, and load it into a view file named resources/views/employees-pdf.blade.php. Then store the view to a variable $html.
  • The next five lines are the PDF settings.
  • The last three lines are to save the view to a pdf file named /files/employees.pdf, relative to the public directory (also relative to the domain name of the application).
  • Note that on the last line, the second parameter 'I' is to directly stream the pdf to the browser. So you will be able to instantly see the PDF as a response of the /employee/esport-to-pdf url.

Step 5 : Create a View File

According to the exportToPdf method on the EmployeeController, There is supposed to be a view file named resource/views/employees-pdf.blade.php. so create the file if it hasn't been created yet. Open the file and put these html code:

<!DOCTYPE html>
<html>
<head>
    <title>Employees</title>
</head>
<body>
        <h1>Employees Table</h1>
        <table class="table table-bordered" cellpadding="5" width:"100%">
            <tr>
                <th><div>ID</div></th>
                <th><div>Name</div></th>
                <th><div>Age</div></th>
                <th><div>Division</div></th>
                <th><div>Salary</div></th>
                <th><div>Performance</div></th>
            </tr>
            @foreach($employees as $employee)
            <tr nobr="true">
                <td>{{ $employee->id }}</td>
                <td>{{ $employee->name }}</td>
                <td>{{ $employee->age }}</td>
                <td>{{ $employee->division }}</td>
                <td>{{ $employee->salary }}</td>
                <td>{{ $employee->performance }}</td>
            </tr>
            @endforeach
        </table>
</body>
</html>

Save it and test the "Export to PDF" button from the browser. It should look something like this:

Messy Employees Table

Okay it works.. But.. It is sooo ugly..
We need to tweak the HTML mockup to beautify the table. Back to the employees-pdf.blade.php file.

Add some styling just above the closing </head> tag

...
<style>
    .text-center{text-align:center;}
    .text-right{text-align:right;}
    td,th{border:1px solid #000000;}
</style>
...

Add a pair of <thead></thead> tag after the first closing </tr> tag, and fill it with these:

...
<thead>
    <tr>
        <th width="50"><div class="text-center">1</div></th>
        <th width="200"><div class="text-center">2</div></th>
        <th width="50"><div class="text-center">3</div></th>
        <th><div class="text-center">4</div></th>
        <th width="70"><div class="text-center">5</div></th>
        <th><div class="text-center">6</div></th>
    </tr>
</thead>
...

The thead pairs will allow anything it contained to be displayed on each and every page.

Next, add an opening <tbody> tag before the @foreach statement, and also a closing </tbody> tag after the @endforeach statement.

...
<tbody>
    @foreach($employees as $employee)
        <tr>
            ...
        </tr>
    @endforeach
</tbody>
...

Next, adjust the width of the column titles, by following the width we defined in thead above

...
<tr>
    <th width="50">ID</th>
    <th width="200">Name</th>
    <th width="50">Age</th>
    <th>Division</th>
    <th width="70">Salary</th>
    <th>Performance</th>
</tr>
...

Lastly, center some columns by applying a corresponding class that we defined in the style tag above. Overall, the final version of the employees-pdf.blade.php file will be like this:

<!DOCTYPE html>
<html>
<head>
    <title>Employees</title>
    <style>
        .text-center{text-align:center;}
        .text-right{text-align:right;}
        td,th{border:1px solid #000000;}
    </style>
</head>
<body>
        <h1>Employees Table</h1>
        <table class="table table-bordered" cellpadding="5" width:"100%">
                <tr>
                    <th width="50"><div class="text-center">ID</div></th>
                    <th width="200"><div class="text-center">Name</div></th>
                    <th width="50"><div class="text-center">Age</div></th>
                    <th><div class="text-center">Division</div></th>
                    <th width="70"><div class="text-center">Salary</div></th>
                    <th><div class="text-center">Performance</div></th>
                </tr>
            <thead>
                <tr>
                    <th width="50"><div class="text-center">1</div></th>
                    <th width="200"><div class="text-center">2</div></th>
                    <th width="50"><div class="text-center">3</div></th>
                    <th><div class="text-center">4</div></th>
                    <th width="70"><div class="text-center">5</div></th>
                    <th><div class="text-center">6</div></th>
                </tr>
            </thead>
            <tbody>
                @foreach($employees as $employee)
                <tr>
                    <td><div class="text-center">{{ $employee->id }}</div></td>
                    <td>{{ $employee->name }}</td>
                    <td><div class="text-center">{{ $employee->age }}</div></td>
                    <td>{{ $employee->division }}</td>
                    <td>
                        <div class="text-right">
                            $ {{ number_format($employee->salary,0,',','.') }}
                        </div>
                    </td>
                    <td>
                        <div class="text-center">
                            {{ $employee->performance }}
                        </div>
                    </td>
                </tr>
                @endforeach
            </tbody>
        </table>
</body>
</html>

Save the file and test it out in the browser. It will look something like this:

Good Looking Table

Better.. But hey, if you look at the bottom at each page, there will be an ugly breaking.

Static Column Title, But Ugly Breaks

Well.. That is eazy.. Just add a nobr="true" attribute on the tr tag under the @foreach statement line

...
<tbody>
    @foreach($employees as $employee)
        <tr nobr="true">
            ...
        </tr>
    @endforeach
</tbody>
...

Save the file again, and test it in the browser. It will look something like this:

Nice Break

Amazing.. We just got ourselves a nice and neat table. Why don't you give it a try and leave a comment below.

Happy coding ^_^

No comments:

Post a Comment