Kreyu/data-table-bundle

Array source

easis opened this issue · 3 comments

Hi! I'm trying to build a datatable with an array as a source. My goal is to allow the users to filter some values (select one or more employees from a list, date rangee and some other checkboxes) and perform some queries to gather data, do some business logic and present the results in the datatable.

I did not find any example in the docs about how to this and actually don't know if this is possible.

Hey. For now, only Doctrine ORM queries are supported, although I want it to be supported by default as well. Creating an array adapter would require adding a ProxyQuery class accepting arrays and handling pagination and sorting.

Ok, I'll wait. Thanks!

I'm trying to achieve same goal. Hope can this steps can help you @easis

In my case, I have a custom Doctrine query (from WorkOrderResultRepository) that obtains an array result (plain associative array).

// Controller
    public function customDatatableAction(Request $request, WorkOrderResultRepository $wor): Response
    {
        $data = $wor->batchAnalysis();
        /**
            [
              [
                "batchCode" => "2416",
                "totalWorkstationSeconds" => "815448"
               ],
               [...]
            ]
        */
        $batchAnalysisArrayProxyQuery = new ArrayProxyQuery(
            data: $data,
            page: $request->query->has('page_batch_analysis') ? (int) $request->query->get('page_batch_analysis') : 1
        );
        $dataTable = $this->createDataTable(BatchAnalysisDataTableType::class, $batchAnalysisArrayProxyQuery);
        $dataTable->handleRequest($request);
        if ($dataTable->isExporting()) {
            return $this->file($dataTable->export());
        }

        return $this->render('@App/admin/reports/batch_analysis.html.twig', [
            'batchAnalysisTable' => $dataTable->createView(),
        ]);
    }

Then I've created a custom ArrayProxyQuery with this code:

<?php

namespace App\Datatable\Query;

use App\Doctrine\Enum\SortOrderEnum;
use Kreyu\Bundle\DataTableBundle\Filter\Filter;
use Kreyu\Bundle\DataTableBundle\Filter\FilterData;
use Kreyu\Bundle\DataTableBundle\Pagination\PaginationData;
use Kreyu\Bundle\DataTableBundle\Query\ResultSet;
use Kreyu\Bundle\DataTableBundle\Sorting\SortingData;
use Kreyu\Bundle\DataTableBundle\Query\ProxyQueryInterface;
use Kreyu\Bundle\DataTableBundle\Query\ResultSetInterface;

final class ArrayProxyQuery implements ProxyQueryInterface
{
    public function __construct(
        private array $data,
        private readonly int $page = 1,
    ) {
    }

    public function sort(SortingData $sortingData): void
    {
        foreach ($sortingData->getColumns() as $column) {
            usort($this->data, function ($a, $b) use ($column) {
                if ($column->getDirection() === strtolower(SortOrderEnum::ASC->value)) {
                    return ($a[$column->getName()] < $b[$column->getName()]) ? -1 : 1;
                } else {
                    return ($a[$column->getName()] > $b[$column->getName()]) ? -1 : 1;
                }
            });
        }
    }

    public function paginate(PaginationData $paginationData): void
    {
        if ($this->page > 1) {
            $total = count($this->data);
            $limit = $paginationData->getPerPage();
            $totalPages = ceil($total / $limit);
            $page = max($this->page, 1);
            $page = min($page, $totalPages);
            $offset = ($page - 1) * $limit;
            if( $offset < 0 ) {
                $offset = 0;
            }
            $this->data = array_slice($this->data, $offset, $limit);
        }
    }

    public function getResult(): ResultSetInterface
    {
        return new ResultSet(
            iterator: new \ArrayIterator(iterator_to_array($this->data)),
            currentPageItemCount: $this->page,
            totalItemCount: count($this->data),
        );
    }

    public function filterDataBy(FilterData $filterData, Filter $filter): void
    {
        $this->data = array_filter($this->data, function ($item) use ($filterData, $filter) {
            return $item[$filter->getConfig()->getName()] === $filterData->getValue();
        });
    }
}

Finally, I've implemented my own BatchAnalysisDataTableType like this:

<?php

namespace App\Datatable\Type;

use App\Datatable\Query\ArrayProxyQuery;
use App\Doctrine\Enum\SortOrderEnum;
use Kreyu\Bundle\DataTableBundle\Filter\FilterData;
use Kreyu\Bundle\DataTableBundle\Filter\FilterInterface;
use Kreyu\Bundle\DataTableBundle\Filter\Operator;
use Kreyu\Bundle\DataTableBundle\Filter\Type\CallbackFilterType;
use Kreyu\Bundle\DataTableBundle\Bridge\PhpSpreadsheet\Exporter\Type\CsvExporterType;
use Kreyu\Bundle\DataTableBundle\Bridge\PhpSpreadsheet\Exporter\Type\XlsxExporterType;
use Kreyu\Bundle\DataTableBundle\Column\Type\NumberColumnType;
use Kreyu\Bundle\DataTableBundle\Column\Type\TextColumnType;
use Kreyu\Bundle\DataTableBundle\Pagination\PaginationData;
use Kreyu\Bundle\DataTableBundle\Sorting\SortingData;
use Kreyu\Bundle\DataTableBundle\Type\AbstractDataTableType;
use Kreyu\Bundle\DataTableBundle\DataTableBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class BatchAnalysisDataTableType extends AbstractDataTableType
{
    public function buildDataTable(DataTableBuilderInterface $builder, array $options): void
    {
        $builder
            ->addColumn(
                'batchCode',
                TextColumnType::class,
                [
                    'label' => 'batchCode',
                    'property_path' => '[batchCode]',
                    'sort' => true,
                    'export' => true,
                ]
            )
            ->addColumn(
                'totalWorkstationSeconds',
                NumberColumnType::class,
                [
                    'label' => 'totalWorkstationSeconds',
                    'property_path' => '[totalWorkstationSeconds]',
                    'sort' => true,
                    'export' => true,
                ]
            )
            ->addFilter(
                'batchCode',
                CallbackFilterType::class,
                [
                    'default_operator' => Operator::Equals,
                    'callback' => function (ArrayProxyQuery $query, FilterData $data, FilterInterface $filter): void {
                        $query->filterDataBy($data, $filter);
                    },
                ]
            )
            ->addFilter(
                'totalWorkstationSeconds',
                CallbackFilterType::class,
                [
                    'default_operator' => Operator::Equals,
                    'callback' => function (ArrayProxyQuery $query, FilterData $data, FilterInterface $filter): void {
                        $query->filterDataBy($data, $filter);
                    },
                ]
            )
            ->addExporter('csv', CsvExporterType::class)
            ->addExporter('xlsx', XlsxExporterType::class)
            ->setDefaultSortingData(SortingData::fromArray([
                'batchCode' => strtolower(SortOrderEnum::ASC->value),
            ]))
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'filtration_enabled' => true,
        ]);
    }
}