Drago DataGrid is a powerful and extendable tabular data component built on top of the Nette Framework. It provides high-performance filtering, sorting, pagination, and row actions with flexible Latte templates for rendering Bootstrap 5 styled tables.
- PHP >= 8.3
- Nette Framework 3.2+
- Dibi 5.0+
- Latte 3.1+
- Bootstrap 5.3+
- Naja 3.2+
- Composer
- Text & Date Filtering – LIKE operator with SQL injection protection
- Column Sorting – Click headers to sort, toggle ASC/DESC
- Smart Pagination – LIMIT/OFFSET at DB level (5.8x faster for large datasets)
- Row Actions – Edit, Delete, or custom actions with callbacks
- Custom Formatting – Format cell values with auto-escaping
- Built-in Security – SQL injection & XSS protection by default
- AJAX Integration – Seamless Naja support, no page reload
- Bootstrap 5 – Beautiful responsive styling
composer require drago-ex/datagriduse Drago\Datagrid\DataGrid;
use Dibi\Connection;
class UserPresenter extends Presenter
{
public function __construct(private Connection $db) {}
// Create the grid component
protected function createComponentGrid(): DataGrid
{
$grid = new DataGrid;
// Set data source (Dibi Fluent query)
$grid->setDataSource($this->db->select('*')->from('users'));
// Add columns to display
$grid->addColumnText('name', 'Name');
$grid->addColumnText('email', 'Email');
$grid->addColumnDate('created_at', 'Created', format: 'Y-m-d H:i');
return $grid;
}
}{control grid}That's it! You have a working data grid with sorting and pagination.
$grid = new DataGrid;
$grid->setDataSource($this->db->select('*')->from('products'));The data source must be a Dibi\Fluent query object. The grid will apply filtering, sorting, and pagination to it automatically.
Define what columns to display:
// Text column (sortable by default)
$grid->addColumnText('name', 'Product Name');
// Text column without sorting
$grid->addColumnText('sku', 'SKU', sortable: false);
// Date column with custom format
$grid->addColumnDate('updated_at', 'Last Updated', format: 'd.m.Y');Column Parameters:
name- Database column namelabel- Header label shown to usersortable- Enable click-to-sort (default: true)formatter- Optional callback to format cell values (optional)
Enable filtering on specific columns:
$grid->addColumnText('name', 'Product Name')
->setFilterText(); // Allows user to search by name
$grid->addColumnText('category', 'Category')
->setFilterText(); // LIKE search
$grid->addColumnDate('created_at', 'Created')
->setFilterDate(); // Date range filter (YYYY-MM-DD format)Filter Types:
setFilterText()- LIKE search with SQL injection protectionsetFilterDate()- Single date or date range (YYYY-MM-DD or YYYY-MM-DD|YYYY-MM-DD)
Add Edit/Delete buttons for each row:
// First, set primary key (required for actions)
$grid->setPrimaryKey('id');
// Add action buttons
$grid->addAction('Edit', 'edit', 'btn btn-sm btn-primary', function($id) {
$this->redirect('edit', $id);
});
$grid->addAction('Delete', 'delete', 'btn btn-sm btn-danger', function($id) {
$this->db->table('products')->get($id)->delete();
$this->redirect('this');
});Action Parameters:
label- Button textsignal- Signal name (internal identifier)class- CSS classes for stylingcallback- Function executed when clicked, receives row ID
Format cell output (automatically escaped):
$grid->addColumnText('status', 'Status', formatter: function($value, $row) {
return match($value) {
'active' => '✓ Active',
'inactive' => '✗ Inactive',
default => 'Unknown'
};
});
$grid->addColumnText('price', 'Price', formatter: function($value, $row) {
return number_format((float)$value, 2) . ' CZK';
});Formatters receive the entire row, so you can use related data:
$grid->addColumnText('author_name', 'Author', formatter: function($value, $row) {
return $row['author_name'] . ' (' . $row['author_email'] . ')';
});// Date column with formatter
$grid->addColumnDate('created_at', 'Created', format: 'Y-m-d',
formatter: function($value, $row) {
// $value is already formatted (Y-m-d), add time info
return $value . ' (' . date('H:i', strtotime($row['created_at'])) . ')';
}
);use Drago\Datagrid\DataGrid;
use Dibi\Connection;
class UserPresenter extends Presenter
{
public function __construct(private Connection $db) {}
protected function createComponentUserGrid(): DataGrid
{
$grid = new DataGrid;
// Data source - Dibi Fluent query
$grid->setDataSource($this->db->select('*')->from('users'));
// Columns with filters
$grid->addColumnText('name', 'Name')
->setFilterText();
$grid->addColumnText('email', 'Email')
->setFilterText();
$grid->addColumnDate('created_at', 'Registered', format: 'd.m.Y')
->setFilterDate();
// Custom formatting
$grid->addColumnText('status', 'Status', formatter: function($value, $row) {
return $value === 'active' ? '✓ Active' : '✗ Inactive';
});
// Actions (requires primary key)
$grid->setPrimaryKey('id')
->addAction('Edit', 'edit', 'btn btn-sm btn-primary', fn($id) => $this->editUser($id))
->addAction('Delete', 'delete', 'btn btn-sm btn-danger', fn($id) => $this->deleteUser($id));
return $grid;
}
private function editUser($id): void
{
// Your edit logic...
}
private function deleteUser($id): void
{
// Your delete logic...
}
}All cell values and formatter output are automatically HTML-escaped to prevent XSS attacks. This is enabled by default and cannot be disabled for regular columns.
// Safe - automatically escaped
$grid->addColumnText('description', 'Description', formatter: function($value) {
return strtoupper($value); // Output will be escaped
});- Text and date filters use parameterized queries via Dibi
- Special characters (
%,_) in LIKE searches are properly escaped - Date filters validate format before executing query
- Primary Key Validation - Primary key existence is checked before rendering actions
- CSRF Protection - Handled automatically by Nette Framework
- Input Validation - Date filter validates YYYY-MM-DD format