deviantlab / tabulator-bundle
Tabulator Symfony bundle
Package info
github.com/6insanes/tabulator-bundle
Type:symfony-bundle
pkg:composer/deviantlab/tabulator-bundle
Requires
- php: >=8.1
- doctrine/doctrine-bundle: ^2.12
- doctrine/orm: ^3.0
- symfony/flex: ^2.0
- symfony/serializer: ^6.4|^7.0
- symfony/stimulus-bundle: ^2.13
- symfony/twig-bundle: ^6.4|^7.0
Requires (Dev)
- symfony/framework-bundle: ^6.0|^7.0
- symfony/phpunit-bridge: ^7.1
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 ORMQueryBuilderagainst an entity.AbstractNativeTableType— builds aDoctrine\DBAL\Query\QueryBuilderagainst 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— anyFormatterInterface(e.g.HtmlFormatter,LinkFormatter,MoneyFormatter,DateTimeFormatter,TickCrossFormatter,RowNumFormatter,RowSelectionFormatter,TextAreaFormatter).editor— anyEditorInterface(e.g.InputEditor,TextareaEditor,NumberEditor,SelectEditor,CheckboxEditor,DateEditor,DateTimeEditor,TimeEditor,RangeEditor,StarRatingEditor,ProgressBarEditor).mutator/accessor— anyMutatorInterface/AccessorInterface.sorter— anySorterInterface(e.g.AlphanumericSorter,NumberSorter,StringSorter,DateSorter,DateTimeSorter,TimeSorter,BooleanSorter,ArraySorter,ExistsSorter).validator— a singleValidatorInterfaceor array of them (e.g.Required,MinLength,MaxLength).topCalc/bottomCalc— aColumnCalculationInterface(Sum,Average,Count,Minimum,Maximum,Unique,Concatenate).headerFilter— aHeaderFiltercombining an editor, aFilterFunction(=,!=,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