Hello,
I show you how to make an interactive data table, in a Symfony project, using Tabulator.js.
What is Tabulator.js?
Tabulator.js is a Javascript library for interactively managing datas tables.
Data table creation
For the example, we need to create an entity allowing us to store different data in the database, which we have previously created and configured with Doctrine.
We name it Member
, it contain, for the example, the following fields:
- Id (auto-increment),
- Username (string(50) not null),
- Firstname (string(255) not null),
- Lastname (string(255) not null),
- Age (integer null).
The easiest way is to use the Maker component, using the command line :
php bin/console maker:entity
It will create Entity and Repository objects in our project. Then create them in the database using a Doctrine migration.
We need to modify the Repository, adding method that manages the data, with a pagination system:
// ...
class MemberRepository extends ServiceEntityRepository
{
// ...
public function getDatas(int $limit, int $offset, array $sorters = [], bool $count = false)
{
$q = $this->createQueryBuilder('m');
if ($count)
{
$q->select('count(m.id)');
return $q->getQuery()->getSingleScalarResult();
}
if (0 !== count($sorters))
{
foreach($sorters as $sorter)
{
$q->orderBy($sorter['field'], $sorter['dir']);
}
}
$q->setMaxResults($limit);
$q->setFirstResult($offset);
return $q->getQuery()->getResult();
}
// ...
}
Let's go to create the Controller:
php bin/console make:controller MemberController
We adapt it to our needs:
// ...
#Route['/members', name: 'app_members_']
class MemberController extends AbstractController
{
#[Route('/index', name: 'index')]
public function index(Request $request)
{
return $this->render('member/index.html.twig', []);
}
#[Route('/list', name: 'list')]
public function list(Request $request, MemberRepository $repo): JsonResponse
{
$page = $request->get('page', 1);
$limit = $request->get('size', null);
$offset = ($page * $limit) - $limit;
$sorters = $request->get('sort', []);
$members = $repo->getDatas($limit, $offset, $sorters);
$membersTotal = $repo->getDatas($limit, $offset, $sorters, true);
$datas = [];
$response = new JsonResponse();
foreach ($members as $member)
{
$datas[] = [
'm.id' => $member->getId(),
'm.username' => $member->getUsername(),
'm.firstname' => $member->getFirstname(),
'm.lastname' => $member->getLastname(),
'm.age' => $member->getAge()
];
}
$final['data'] = $datas;
$final['last_page'] = ceil($membersTotal / $limit);
$final['nb_row'] = $membersTotal;
$final['last_row'] = $membersTotal;
$final['size'] = sizeof($datas);
$response->setContent(json_encode($final));
return $response;
}
}
We create the templates, starting with the layout:
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('assets/css/tabulator.min.css') }}">
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
<script src="{{ asset('assets/js/tabulator.min.js') }}"></script>
{% endblock %}
</body>
</html>
We create the index page:
{% extends 'layout.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<style type="text/css">
</style>
{% endblock %}
{% block body %}
<div class="wrapper">
<div class="content">
<h1>Tabulator intégration</h1>
<hr />
<div id="tabulator">
</div>
<div id="pagination"></div>
<div id="page-count"></div>
</div>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
var table = new Tabulator("#tabulator", {
layout: "fitDataFill",
renderHorizontal: "virtual",
maxHeight: "85vh",
sortMode: "remote",
movableColumns: true,
columnsHeaderVertAlign: "middle",
ajaxUrl: "{{ path('app_members_list') }}",
initialSort: {
{ column: "m.username", dir: "asc" },
},
columnsDefaults: {
headerWordWrap: true,
vertAlign: "middle",
resizable: true,
},
pagination: true,
paginationSize: 15,
paginationMode: "remote",
paginationCounter: "rows",
paginationCounterElement: "#page-count",
columns: {
{ title: "ID", field: "m.id", sorter: "number", visible: true },
{ title: "Username", field: "m.username", sorter: "string", visible: true},
{ title: "Firstname", field: "m.firstname", sorter: "string", visible:true },
{ title: "Lastname", field: "m.lastname", sorter: "string", visible:true },
{ title: "Age", field: "m.age", sorter: "number", visible:true },
}
});
</script>
{% endblock %}