Install the Bundle
composer require alexain/ux-gridjs
Ensure your project meets the requirements:
symfony/asset-mapper and symfony/stimulus-bundle installed.Register Grid.js via AssetMapper
php bin/console importmap:require gridjs
php bin/console importmap:install
This auto-generates the importmap for Grid.js.
Enable the Stimulus Controller
The bundle ships with grid_controller.js. Ensure it’s registered in your Stimulus app (check config/packages/stimulus.yaml or your Stimulus entrypoint).
Create a Grid Presenter
In your controller or service, use GridPresenter to define columns and data:
use Alexain\UXGridJs\GridPresenter;
$grid = (new GridPresenter())
->setColumns([
['name' => 'id', 'title' => 'ID'],
['name' => 'name', 'title' => 'Name'],
])
->setData($yourDataArray);
Render in Twig Pass the grid to your template:
{{ gridjs_render(grid) }}
This outputs a <div data-controller="grid"> with the grid configuration.
View the Table The Stimulus controller initializes Grid.js, rendering the table client-side.
If using Turbo Drive, ensure your layout includes:
{% block stylesheets %}
{{ parent() }}
{{ importmap('app') }} {# Auto-includes Grid.js #}
{% endblock %}
Define Grid Logic in Controllers/Services
Use GridPresenter to encapsulate table logic (columns, sorting, filtering):
$grid = (new GridPresenter())
->setColumns([
['name' => 'created_at', 'title' => 'Date', 'type' => 'date'],
['name' => 'status', 'title' => 'Status', 'filter' => true],
])
->setData($repository->findAll())
->setSearch(true)
->setSort(true);
Reuse Presenters Create a service to centralize grid configurations:
// src/Service/UserGridService.php
class UserGridService {
public function getGrid(): GridPresenter {
return (new GridPresenter())
->setColumns([...])
->setData($this->userRepository->findAll());
}
}
Pass to Twig Inject the service into your controller and render:
public function index(UserGridService $gridService): Response {
return $this->render('user/index.html.twig', [
'grid' => $gridService->getGrid(),
]);
}
Dynamic Data Loading Use Stimulus actions to fetch data via API:
// grid_controller.js (extend the default)
connect() {
this.grid = gridjs.Grid(this.element, this.gridConfig);
// Add custom fetch logic
this.element.addEventListener('grid:refresh', () => {
fetch('/api/users').then(response => {
this.grid.updateData(response.data);
});
});
}
Toolbar Buttons Configure buttons server-side (no Twig overrides):
$grid->setToolbar([
'buttons' => [
['text' => 'Add', 'action' => 'addUser'],
['text' => 'Export', 'action' => 'exportCsv'],
],
]);
Handle actions via Stimulus:
// grid_controller.js
addUser() {
Turbo.visit('/users/new');
}
Pagination
Use setPagination(true) and configure server-side:
$grid->setPagination([
'server' => true,
'pageSize' => 10,
]);
Handle pagination via API endpoints (e.g., /api/users?page=2).
Custom CSS/JS: Override Grid.js styles by adding your own importmap entry:
php bin/console importmap:require custom-grid-styles
Then include it in your Twig template:
{{ importmap('custom-grid-styles') }}
Lazy Loading: Defer Grid.js initialization until the table is visible:
// grid_controller.js
connect() {
if (this.hasElementVisible()) {
this.grid = gridjs.Grid(this.element, this.gridConfig);
} else {
this.element.addEventListener('show', () => this.initGrid());
}
}
AssetMapper Cache
After running importmap:install, clear your cache:
php bin/console cache:clear
If Grid.js fails to load, verify the importmap entry exists in public/build/importmap.json.
Stimulus Controller Naming
Ensure grid_controller.js is registered in config/packages/stimulus.yaml:
stimulus:
controllers:
grid: 'Alexain\UXGridJs\Asset\grid_controller.js'
Twig Template Overrides
Avoid overriding gridjs_render in Twig. Instead, extend the Stimulus controller for custom behavior.
Turbo Drive Conflicts
If using Turbo, ensure your grid container has data-turbo-permanent to prevent reinitialization:
<div data-controller="grid" data-turbo-permanent>
{{ gridjs_render(grid) }}
</div>
Check Console Errors
Open browser dev tools (F12) to verify Grid.js and Stimulus are loaded:
Uncaught ReferenceError: gridjs is not defined
→ Run importmap:install again.
Inspect Stimulus Controller
Add debug logs to grid_controller.js:
connect() {
console.log('Grid config:', this.gridConfig); // Debug output
this.grid = gridjs.Grid(this.element, this.gridConfig);
}
Data Binding Issues
If data doesn’t render, ensure setData() returns an array of arrays:
// Correct:
$grid->setData([['id' => 1, 'name' => 'Alice'], ['id' => 2, 'name' => 'Bob']]);
// Incorrect (will fail silently):
$grid->setData($yourEntityObjects); // Use ->toArray() first!
Custom Column Types Extend Grid.js column types in your Stimulus controller:
// grid_controller.js
connect() {
this.grid = gridjs.Grid(this.element, {
columns: [
{ name: 'status', title: 'Status', formatter: (cell) => {
return cell === 'active' ? '<span class="text-green">Active</span>' : 'Inactive';
}},
],
});
}
Server-Side Sorting/Filters Proxy client-side events to your API:
// grid_controller.js
connect() {
this.grid = gridjs.Grid(this.element, this.gridConfig);
this.grid.on('sort', (event) => {
Turbo.visit(`/users?sort=${event.column}&dir=${event.direction}`);
});
}
Plugin Integration
Use Grid.js plugins (e.g., gridjs-plugin-filter) by extending the controller:
import { GridJsPluginFilter } from 'gridjs-plugin-filter';
connect() {
this.grid = gridjs.Grid(this.element, this.gridConfig);
GridJsPluginFilter(this.grid);
}
Ensure the plugin is imported via AssetMapper.
Default Pagination
If setPagination(true) is called without options, it defaults to:
{
server: false,
pageSize: 10,
navigate: true,
}
Toolbar Button Actions
Button actions are passed as strings (e.g., 'action' => 'export'). Handle them in Stimulus:
// grid_controller.js
exportCsv() {
window.open('/api/users/export');
}
Turbo Drive + GridJs Turbo Drive may re-render the page, causing Grid.js to reinitialize. Mitigate with:
// grid_controller.js
connect() {
if (this.grid) return; // Skip if already initialized
this.grid = gridjs.Grid(this.element, this.gridConfig);
}
How can I help you explore Laravel packages today?