Blog

Twill CMS data export from custom page

January 21, 2022 • ☕️ 5 min read

Of current 2.6 version of Twill CMS provides an easy way to enable quick actions on tables. You can do that on a module controller like this:

public function additionalTableActions()
{
    return [
        'exportAction' => [ // Action name.
            'name' => 'Export Newsletter List', // Button action title.
            'variant' => 'primary', // Button style variant. Available variants; primary, secondary, action, editor, validate, aslink, aslink-grey, warning, ghost, outline, tertiary
            'size' => 'small', // Button size. Available sizes; small
            'link' => route('newsletter.export'), // Button action link.
            'target' => '', // Leave it blank for self.
            'type' => 'a', // Leave it blank for "button".
        ]
    ];
}

This really provides a quick and nice way to trigger some action (or redirect), and from my usage, this was enough to start. The problem came later, since users wanted to select a specific range of exported data, and this, unfortunately, didn’t provide a request.

Note: with this method, you also can make usage of route params passing if your parameters are static.

You will need to make a custom controller for this exporter.

public function additionalTableActions()
{
    return [
        'exportAction' => [ // Action name.
            'name' => 'Export Newsletter List', // Button action title.
            'variant' => 'primary', // Button style variant. Available variants; primary, secondary, action, editor, validate, aslink, aslink-grey, warning, ghost, outline, tertiary
            'size' => 'small', // Button size. Available sizes; small
            'link' => route('newsletter.export', ['order' => 'ASC', 'emails' => 'yes']);, // Button action link.
            'target' => '', // Leave it blank for self.
            'type' => 'a', // Leave it blank for "button".
        ]
    ];
}

How do custom pages work?

From docs we can see that custom pages on Twill are piece of cake.

Firstly, let’s generate a controller in Admin.

php artisan make:controller "Admin/ExportController"

This will generate the default Laravel controller, but we will need to overwrite it.

namespace App\Http\Controllers\Admin;

class ExportController
{
    public function show()
    {
        ...
    }
}

After overwriting the controller we are provided with show() method which is able to return a specific view file for a custom page.

public function show()
{
    return view('admin.custom.export');
}

Now let’s create that blade file.

$ cd ./resources/views/admin/custom
$ touch export.blade.php

export.blade.php

@extends('twill::layouts.free')

@section('customPageContent')

    <a17-fieldset title="Categories" id="type" :open="false">
        <form method="POST" action="{{route('admin.exportsSubmit')}}">

            @csrf
            
            <div class="wrapper">
                <div class="col--double col--double-wrap">
                    <a17-inputframe label="Start date" note="Starting date range">
                        <a17-datepicker enableTime="true" name="minDate"></a17-datepicker>
                    </a17-inputframe>
                </div>
                <div class="col--double col--double-wrap">
                    <a17-inputframe label="End date" note="End date range">
                        <a17-datepicker enableTime="true" name="maxDate"></a17-datepicker>
                    </a17-inputframe>
                </div>
            </div>
                
            <br>

            <a17-button variant="validate" name="submit" type="submit" value="Categories">Generate</a17-button>
        </form>
    </a17-fieldset>


    ...

@stop

In this blade file, we used Twill blade components and we passed some props, regarding what component accepts.

The form also includes 2 date pickers which allow us to select specific ranges. The date picker accepts range prop, but it seems that is not working so I went with two date pickers.

Also to note we used native HTML elements like form here to make a working form that will submit POST requests to the specific endpoint.

route('admin.exportsSubmit') 

Let’s register routes in routes/admin.php

/**
 * Custom CMS pages
 */

Route::name('exports')->get('/export', 'ExportController@show');
Route::name('exportsSubmit')->post('/export', 'ExportController@exportData');

After this, you should be able to access it on the URL twill.test/{handle}/export

To easily navigate to this page we have two easy options.

The first is to register this route in config/twill-navigation.php

return [
    'exportPage' => [
        'title' => 'Export',
        'route' => 'admin.export',
    ],
];

Make sure that you have named the GET route in admin.php for showing this page.

Now lets create exportData method on ExportController.

/**
 * Handle incoming POST requests from the form
 *
 * @param Request $request
 * @return void
 */
public function exportData(Request $request)
{

    try {

        // Operation requested from admin
        $operation = $request->input('submit');

        // Define static file class namespace
        $exportclass = '\App\Exports\\' . $operation;

        // Check if we have generate action
        $exportclass .= 'Export';

        // Resolve export class (dynamicly)
        return new $exportclass($request->input('minDate'), $request->input('maxDate'));

        //Self-explanatory
        die('You should not be here. Refresh the page.');

    } catch (\Throwable $th) {
        throw $th;
    }
}

As we can see from the method above we need to create specific directories in the Laravel app directory.

Let’s create an export file handler for “Categories”.

Note: for exports here we use maatwebsite/excel All of the documentation for this package is available here laravel-excel.com

app\Exports\CategoriesExport.php

<?php

namespace App\Exports;

use App\Models\Category;
use Maatwebsite\Excel\Excel;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Contracts\Support\Responsable;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithMapping;

class CategoriesExport implements FromCollection, WithHeadings, ShouldAutoSize, WithStyles, Responsable, WithMapping
{

    use Exportable;

    // Define starting date
    public $from;

    // Define ending date
    public $to;

    /**
     * Constructor
     *
     * @param [date] $from
     * @param [date] $to
     */
    public function __construct($from = null, $to = null)
    {

        // Set properties
        $this->from = $from;
        $this->to = $to; 
    }
    
    /**
    * @var Category $category
    */
    public function map($category): array
    {
        return [
            $category->title,
            collect($category->brands)->pluck('title')->implode(', '), // Repeater
            $category->created_at,
            $category->updated_at,
        ];
    }

    /**
    * It's required to define the fileName within
    * the export class when making use of Responsable.
    */
    private $fileName = 'categories.xlsx';


    /**
    * Optional Writer Type
    */
    private $writerType = Excel::XLSX;

    /**
     * Set styles for Excel file
     *
     * @param Worksheet $sheet
     * @return void
     */
    public function styles(Worksheet $sheet)
    {
        return [
            // Style the first row as bold text.
            1    => ['font' => ['bold' => true]],
        ];
    
    }

    /**
     * Return XLSX column headings
     *
     * @return array
     */
    public function headings(): array
    {
        return [
            'Name',
            'Brands',
            'Created at',
            'Updated at'
        ];
    }

    /**
     * Return all records for partners
     *
     * @return void
     */
    public function collection()
    {
        return Category::exportStrategy($this->from, $this->to);
    }
}

Notice method collection() here. This is the default export method that returns collection from our Category model.

Lets create exportStrategy() method in model.

/**
 * Get custom records for categories export
 *
 * @return array
 */
public static function exportStrategy($from = null, $to = null)
{

    // Here we handle specific date range exporting
    // Make sure that query returns collection
    ...

    // Lastly, return all records with "brands" 
    return self::with('brands')->get();
}

Example of exported categories.xlsx file

example