deviantlab/tabulator-bundle

Tabulator Symfony bundle

Maintainers

Package info

github.com/6insanes/tabulator-bundle

Type:symfony-bundle

pkg:composer/deviantlab/tabulator-bundle

Transparency log

Statistics

Installs: 1 443

Dependents: 0

Suggesters: 0

Stars: 4

Open Issues: 0

0.4.4 2025-02-25 09:47 UTC

This package is auto-updated.

Last update: 2026-07-16 07:37:56 UTC


README

A Symfony bundle that integrates Tabulator (a JavaScript table/datagrid library) into Symfony applications via Stimulus. Tables are declared as PHP classes, rendered with a single Twig function, and — for server-side pagination/sorting/filtering — backed by an auto-registered controller that queries Doctrine ORM entities or native DBAL connections on your behalf.

Installation

Before you start, make sure you have StimulusBundle configured in your app.

Install the bundle using Composer and Symfony Flex:

composer require deviantlab/tabulator-bundle

If you're using WebpackEncore, install your assets and restart Encore (not needed if you're using AssetMapper):

npm install --force
npm run watch

# or use yarn
yarn install --force
yarn watch

Usage

1. Define a table type

A table is described by a class implementing TableInterface. The bundle ships two abstract base classes depending on your data source:

  • AbstractOrmTableType — builds a Doctrine ORM QueryBuilder against an entity.
  • AbstractNativeTableType — builds a Doctrine\DBAL\Query\QueryBuilder against a raw DBAL connection (no entity mapping required).

Any service tagged (auto-configured) with TableInterface is automatically registered as a deviantlab.tabulator.table_type, so as long as autowiring/autoconfiguration is enabled for your services, no extra wiring is needed.

namespace App\Table;

use App\Entity\Product;
use DeviantLab\TabulatorBundle\AbstractOrmTableType;
use DeviantLab\TabulatorBundle\Column;
use DeviantLab\TabulatorBundle\FilterMode;
use DeviantLab\TabulatorBundle\Pagination;
use DeviantLab\TabulatorBundle\PaginationMode;
use DeviantLab\TabulatorBundle\SortMode;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;

final class ProductTable extends AbstractOrmTableType
{
    public static function getName(): string
    {
        return 'product_table';
    }

    public function getEntityClass(): string
    {
        return Product::class;
    }

    public function getQueryBuilder(EntityRepository $repo, array $params): QueryBuilder
    {
        return $repo->createQueryBuilder('p');
    }

    public function getColumns(): iterable
    {
        yield new Column('Name', 'name');
        yield new Column('Price', 'price', hozAlign: \DeviantLab\TabulatorBundle\HozAlign::RIGHT);
    }

    public function getPagination(): ?Pagination
    {
        return new Pagination(mode: PaginationMode::REMOTE, size: 20);
    }

    public function getSortMode(): SortMode
    {
        return SortMode::REMOTE;
    }

    public function getFilterMode(): FilterMode
    {
        return FilterMode::REMOTE;
    }
}

The getName() value must be unique across all table types — it identifies the table in the auto-generated data endpoint (/{_tableName}, route deviantlab_tabulatorbundle_get_data) and is used to resolve the type from the container.

For a native/DBAL-backed table, extend AbstractNativeTableType instead and implement getConnectionName() and getQueryBuilder(Connection $connection, array $params).

2. Build and render the table

Use TableFactory to turn a table type into a Table instance (this also wires up the Ajax endpoint automatically), then render it with the render_table() Twig function:

use App\Table\ProductTable;
use DeviantLab\TabulatorBundle\TableFactory;

final class ProductController
{
    public function list(TableFactory $tableFactory): Response
    {
        $table = $tableFactory->create(ProductTable::class, params: ['categoryId' => 5]);

        return $this->render('product/list.html.twig', [
            'table' => $table,
        ]);
    }
}
{{ render_table(table) }}

This renders a single <div> with Stimulus controller attributes; the deviantlab--tabulator-bundle--tabulator Stimulus controller (bundled in assets/dist/tabulator_controller.js) reads the serialized options and boots the Tabulator instance in the browser.

3. Configuring columns

Column maps closely to Tabulator's own column definition options (title, field, width, widthGrow, widthShrink, resizable, minWidth, maxWidth, frozen, headerSort, headerHozAlign, hozAlign, vertAlign, print, etc.) plus a set of strategy objects:

  • formatter / topCalcFormatter / bottomCalcFormatter — any FormatterInterface (e.g. HtmlFormatter, LinkFormatter, MoneyFormatter, DateTimeFormatter, TickCrossFormatter, RowNumFormatter, RowSelectionFormatter, TextAreaFormatter).
  • editor — any EditorInterface (e.g. InputEditor, TextareaEditor, NumberEditor, SelectEditor, CheckboxEditor, DateEditor, DateTimeEditor, TimeEditor, RangeEditor, StarRatingEditor, ProgressBarEditor).
  • mutator / accessor — any MutatorInterface / AccessorInterface.
  • sorter — any SorterInterface (e.g. AlphanumericSorter, NumberSorter, StringSorter, DateSorter, DateTimeSorter, TimeSorter, BooleanSorter, ArraySorter, ExistsSorter).
  • validator — a single ValidatorInterface or array of them (e.g. Required, MinLength, MaxLength).
  • topCalc / bottomCalc — a ColumnCalculationInterface (Sum, Average, Count, Minimum, Maximum, Unique, Concatenate).
  • headerFilter — a HeaderFilter combining an editor, a FilterFunction (=, !=, starts, ends, >, <, >=, <=, in, keywords, like, regex) and an optional initial value/placeholder.

4. Table-level options

Table exposes fluent setters for the most common Tabulator options: layout, height/minHeight/maxHeight, rowHeight, autoResize, resizableColumnFit, selectableRows, locale, placeholder, printHeader, dataTree (and its dataTreeStartExpanded/dataTreeFilter/dataTreeSort flags), columnCalcs / groupClosedShowCalcs / dataTreeChildColumnCalcs, rowHeader (a RowHeader for row selection/handle columns), editTriggerEvent, columnHeaderSortMulti, headerSortClickElement / headerSortElement, popupContainer, initialSort / initialFilter, and the dataLoader/dataLoaderLoading/dataLoaderError/ dataLoaderErrorTimeout options.

Ajax configures the request Tabulator makes for remote data: url, params, method, contentType and arbitrary headers (addHeader()).

Pagination configures mode (PaginationMode::LOCAL/REMOTE), size, initialPage, the query parameter names sent to the server (pageParamName, sizeParamName), sizeSelector and counter. Remote pagination requires the table to already have an Ajax configured, otherwise a LogicException is thrown.

5. Server-side pagination, sorting and filtering

When SortMode::REMOTE, FilterMode::REMOTE or PaginationMode::REMOTE are used, Tabulator sends the current page/sort/filter state to the Ajax URL, and the bundle's TableController (mounted on /{_tableName}) resolves the matching OrmTableHandler or NativeTableHandler to build the query, apply pagination/sorting/filtering, and serialize the result as JSON.

You can customize how a specific field is sorted or filtered by returning a SortOverride / FilterOverride from your table type:

use DeviantLab\TabulatorBundle\FilterFunction;
use DeviantLab\TabulatorBundle\Server\Filter\FilterOverride;
use DeviantLab\TabulatorBundle\Server\Sorter\SortOverride;

public function getSortOverride(): ?SortOverride
{
    return (new SortOverride())->add('fullName', function (QueryBuilder $qb, string $field, string $dir) {
        $qb->addOrderBy('CONCAT(p.firstName, p.lastName)', $dir);
    });
}

public function getFilterOverride(): ?FilterOverride
{
    return (new FilterOverride())->add('fullName', FilterFunction::LIKE, function (QueryBuilder $qb, mixed $value) {
        $qb->andWhere('CONCAT(p.firstName, p.lastName) LIKE :fullName')
            ->setParameter('fullName', "%{$value}%");
    });
}

Use configureQuery(Query $query) (ORM tables) to tweak the built Doctrine Query (e.g. hints, result cache), and doTransform(array $items, array $params): array to post-process the fetched rows before they're serialized to the client.

Testing

composer test