drago-ex / datagrid
A simple datagrid for sorting and filtering data.
Requires
- php: >=8.3 <9
- dibi/dibi: ^5.0
- latte/latte: ^3.1
- nette/application: ^3.2
- nette/forms: ^3.2
- tracy/tracy: ^2.11
Requires (Dev)
- nette/tester: ^2.3
- phpstan/phpstan-nette: ^2.0
Suggests
- drago-ex/translator: Configures Latte translation support for bundled templates using translation macros.
README
Drago DataGrid is a Nette component for rendering Bootstrap 5 tables with filtering, sorting, pagination and row actions.
Requirements
- PHP >= 8.3
- Nette Framework
- Dibi
- Latte
- Bootstrap 5
- Naja
Installation
composer require drago-ex/datagrid
Frontend Assets
Add the Composer package as a local npm dependency:
{
"type": "module",
"dependencies": {
"drago-datagrid": "file:vendor/drago-ex/datagrid"
}
}
Install dependencies:
npm install
Import the assets:
import naja from 'naja'; import DataGrid from 'drago-datagrid'; import 'drago-datagrid/styles/datagrid'; naja.initialize(); new DataGrid().initialize(naja);
Basic Usage
use Dibi\Connection; use Drago\Datagrid\DataGrid; use Nette\Application\UI\Presenter; final class ProductPresenter extends Presenter { public function __construct( private readonly Connection $db, ) { } protected function createComponentGrid(): DataGrid { $grid = new DataGrid; $grid->setDataSource( $this->db->select('*')->from('products') ); $grid->addColumnText('name', 'Name')->setFilterText(); $grid->addColumnText('status', 'Status')->setFilterSelect([ 'active' => 'Active', 'inactive' => 'Inactive', ]); $grid->addColumnDate('created_at', 'Created', format: 'd.m.Y')->setFilterDate(); return $grid; } }
Render the component in Latte:
{control grid}
Data Source
The grid expects a Dibi\Fluent data source. Filtering, sorting and pagination are applied to a cloned query during rendering.
$grid->setDataSource( $this->db->select('*')->from('products')->orderBy('id DESC') );
A default orderBy() can be used for the initial render. When the user clicks a sortable column, the grid replaces the default order with the selected grid sort.
Columns
$grid->addColumnText('name', 'Product Name'); $grid->addColumnText('sku', 'SKU', sortable: false); $grid->addColumnDate('updated_at', 'Updated', format: 'd.m.Y');
Column arguments:
name- database column namelabel- column labelsortable- enables header sorting, default istrueformatter- optional callback for rendering cell values
Natural numeric sorting can be enabled on text columns:
$grid->addColumnText('code', 'Code')->setNaturalSort();
Column text alignment can be changed with Bootstrap alignment helpers:
$grid->addColumnText('id', 'ID')->alignRight(); $grid->addColumnText('status', 'Status')->alignCenter(); $grid->addColumnText('name', 'Name')->alignLeft();
Alignment affects only table rendering. It does not validate or convert the column value.
Filters
$grid->addColumnText('name', 'Name')->setFilterText(); $grid->addColumnText('status', 'Status')->setFilterSelect([ 'active' => 'Active', 'inactive' => 'Inactive', ]); $grid->addColumnDate('created_at', 'Created')->setFilterDate();
Filter types:
setFilterText()- LIKE searchsetFilterSelect(array $items)- exact match selectsetFilterDate()- date filter inYYYY-MM-DDformat
Date filters also support range values in the form YYYY-MM-DD|YYYY-MM-DD.
Active filters can be cleared individually with the small clear button next to the filter input. The reset button clears all filters at once.
Filter Modes
Filters can be rendered in two modes:
$grid->setFilterMode('top');
top is the default mode. Filters are displayed in a toolbar above the table.
$grid->setFilterMode('inline');
inline displays filters in a second table header row under the related columns.
Both modes use the same filter definitions and AJAX behavior.
Row Actions
Set the primary key before adding actions:
$grid->setPrimaryKey('id');
Add action callbacks:
$grid->addAction('Edit', 'edit!', 'ajax btn btn-sm btn-primary', function (int $id): void { $this->redirect('edit', $id); }); $grid->addAction('Delete', 'delete!', 'ajax btn btn-sm btn-danger', function (int $id): void { // Delete row });
Actions can be shown or hidden per row:
$grid->addAction( 'Activate', 'activate!', 'ajax btn btn-sm btn-success', callback: fn(int $id) => $this->activate($id), condition: fn(array $row): bool => !$row['active'], );
The condition callback receives the current row as array<string, mixed>.
Row Click
The whole row can trigger an action:
$grid->setPrimaryKey('id'); $grid->setRowClickAction('edit!'); $grid->addAction('Edit', 'edit!', 'ajax btn btn-sm btn-primary', function (int $id): void { $this->redirect('edit', $id); });
If the action signal matches setRowClickAction(), the action button is hidden and only the row click is used.
Action Display
Show row actions only on hover:
$grid->setAutoHideActions();
Actions with signals edit! and delete! are displayed after other custom actions.
Formatting Values
Simple values are escaped automatically:
$grid->addColumnText('price', 'Price', formatter: function (mixed $value, array $row): string { return number_format((float) $value, 2) . ' CZK'; });
Return Nette\Utils\Html when you want to render HTML:
use Nette\Utils\Html; $grid->addColumnText('status', 'Status', formatter: function (mixed $value, array $row): Html { return Html::el('span') ->class($value === 'active' ? 'badge bg-success' : 'badge bg-secondary') ->setText((string) $value); });
Localization
The grid supports Nette\Localization\Translator:
$grid->setTranslator($this->translator);
Translated values include column labels, action labels, filter labels, reset button and pagination text.
AJAX
The grid is built for Naja. Filtering, sorting, pagination, page size changes and row actions are handled through AJAX and keep browser history updated.
Text filters are submitted by pressing Enter. Select and date filters are submitted automatically when their value changes.
Security
- Cell values are escaped unless a formatter returns
Nette\Utils\Html. - Filters use parameterized Dibi queries.
- Sort direction is normalized to
ASCorDESCbefore it is applied to SQL. - Empty filter values are removed from persistent parameters.