276 lines
8.2 KiB
Markdown
276 lines
8.2 KiB
Markdown
# DataTable Components
|
||
|
||
Переиспользуемые компоненты для отображения интерактивных таблиц с AJAX-загрузкой, сортировкой и поиском.
|
||
|
||
## Структура компонентов
|
||
|
||
```
|
||
public/
|
||
├── js/
|
||
│ └── modules/
|
||
│ └── DataTable.js # JS-модуль для инициализации таблиц
|
||
└── css/
|
||
└── components/
|
||
└── data-table.css # Стили для интерактивных таблиц
|
||
|
||
app/Views/components/table/
|
||
├── table.twig # Основной компонент таблицы
|
||
├── table_header.twig # Переиспользуемый заголовок
|
||
└── pagination.twig # Компонент пагинации
|
||
```
|
||
|
||
## Быстрый старт
|
||
|
||
### 1. Подключение стилей и скриптов
|
||
|
||
В вашем шаблоне добавьте:
|
||
|
||
```twig
|
||
{% block stylesheets %}
|
||
{{ parent() }}
|
||
<link rel="stylesheet" href="/css/components/data-table.css">
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
{{ parent() }}
|
||
<script src="/js/modules/DataTable.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
new DataTable('your-table-id', {
|
||
url: '/your-module/table',
|
||
perPage: 10
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|
||
```
|
||
|
||
### 2. Использование компонента таблицы
|
||
|
||
```twig
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'products-table',
|
||
url: '/products/table',
|
||
perPage: 25,
|
||
sort: sort|default(''),
|
||
order: order|default('asc'),
|
||
filters: filters|default({}),
|
||
columns: [
|
||
{ name: 'name', label: 'Название', width: '35%' },
|
||
{ name: 'sku', label: 'Артикул', width: '15%' },
|
||
{ name: 'price', label: 'Цена', width: '15%' },
|
||
{ name: 'stock', label: 'Остаток', width: '15%' }
|
||
],
|
||
actions: { label: 'Действия', width: '20%' },
|
||
emptyMessage: 'Товары не найдены'
|
||
}) }}
|
||
```
|
||
|
||
## Конфигурация столбцов
|
||
|
||
Каждый столбец поддерживает следующие параметры:
|
||
|
||
| Параметр | Тип | Описание |
|
||
|----------|-----|----------|
|
||
| `name` | string | Идентификатор столбца (используется для сортировки и фильтрации) |
|
||
| `label` | string | Отображаемое название столбца |
|
||
| `width` | string | Ширина столбца (например, '35%', '200px') |
|
||
| `placeholder` | string | Текст-подсказка в поле поиска |
|
||
| `searchTitle` | string | Title для иконки поиска |
|
||
| `align` | string | CSS-класс выравнивания |
|
||
|
||
## Конфигурация пагинации
|
||
|
||
Компонент автоматически получает данные из объекта `pager`:
|
||
|
||
```php
|
||
// В контроллере
|
||
$pagination = [
|
||
'currentPage' => $pager->getCurrentPage(),
|
||
'totalPages' => $pager->getPageCount(),
|
||
'totalRecords' => $pager->getTotal(),
|
||
'perPage' => $perPage,
|
||
'from' => (($pager->getCurrentPage() - 1) * $perPage + 1),
|
||
'to' => min($pager->getCurrentPage() * $perPage, $pager->getTotal())
|
||
];
|
||
```
|
||
|
||
## Пример контроллера
|
||
|
||
```php
|
||
public function table()
|
||
{
|
||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||
$perPage = (int) ($this->request->getGet('perPage') ?? 10);
|
||
$sort = $this->request->getGet('sort') ?? '';
|
||
$order = $this->request->getGet('order') ?? 'asc';
|
||
|
||
// Фильтры
|
||
$filters = [
|
||
'name' => $this->request->getGet('filters[name]') ?? '',
|
||
];
|
||
|
||
// Построение запроса
|
||
$builder = $this->model->builder();
|
||
|
||
// Применяем фильтры
|
||
if (!empty($filters['name'])) {
|
||
$builder->like('name', $filters['name']);
|
||
}
|
||
|
||
// Сортировка
|
||
if (!empty($sort)) {
|
||
$builder->orderBy($sort, $order);
|
||
}
|
||
|
||
// Пагинация
|
||
$items = $builder->paginate($perPage, 'default', $page);
|
||
|
||
$data = [
|
||
'items' => $items,
|
||
'pager' => $this->model->pager,
|
||
'perPage' => $perPage,
|
||
'sort' => $sort,
|
||
'order' => $order,
|
||
'filters' => $filters,
|
||
];
|
||
|
||
return $this->renderTwig('path/to/your/_table', $data);
|
||
}
|
||
```
|
||
|
||
## AJAX-ответ
|
||
|
||
Для AJAX-запросов контроллер должен возвращать только `tbody` и `tfoot`:
|
||
|
||
```twig
|
||
{# _table.twig для модуля #}
|
||
{% set isAjax = app.request.headers.get('X-Requested-With') == 'XMLHttpRequest' %}
|
||
|
||
{% if isAjax %}
|
||
{# AJAX: только tbody #}
|
||
<tbody>
|
||
{% for item in items %}
|
||
<tr>
|
||
<td>{{ item.name }}</td>
|
||
<td>{{ item.price }}</td>
|
||
<td class="text-end">
|
||
<a href="...">Редактировать</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
|
||
{% if items is not empty and pager %}
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="3">
|
||
{{ include('@components/table/pagination.twig', {
|
||
pagination: paginationData,
|
||
id: 'your-table-id'
|
||
}) }}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
{% endif %}
|
||
{% else %}
|
||
{# Обычный запрос: полная таблица #}
|
||
<div class="table-responsive">
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'your-table-id',
|
||
url: '/your-module/table',
|
||
perPage: perPage,
|
||
columns: columns,
|
||
pagination: paginationData,
|
||
actions: { label: 'Действия' },
|
||
emptyMessage: 'Нет данных'
|
||
}) }}
|
||
</div>
|
||
{% endif %}
|
||
```
|
||
|
||
## API DataTable
|
||
|
||
### Опции при инициализации
|
||
|
||
```javascript
|
||
new DataTable('container-id', {
|
||
url: '/api/endpoint', // URL для AJAX-загрузки
|
||
perPage: 10, // Записей на странице по умолчанию
|
||
debounceTime: 300, // Задержка поиска в мс
|
||
preserveSearchOnSort: true // Сохранять видимость поиска при сортировке
|
||
});
|
||
```
|
||
|
||
### Методы
|
||
|
||
```javascript
|
||
const table = new DataTable('my-table', options);
|
||
|
||
// Установка фильтра
|
||
table.setFilter('columnName', 'value');
|
||
|
||
// Установка количества записей
|
||
table.setPerPage(25);
|
||
|
||
// Переход на страницу
|
||
table.goToPage(3);
|
||
```
|
||
|
||
## Доступные CSS-классы
|
||
|
||
| Класс | Описание |
|
||
|-------|----------|
|
||
| `.data-table` | Основной контейнер таблицы |
|
||
| `.header-content` | Контейнер для элементов заголовка |
|
||
| `.header-text` | Текст заголовка столбца |
|
||
| `.search-trigger` | Иконка поиска |
|
||
| `.sort-icon` | Иконка сортировки |
|
||
| `.header-search-input` | Поле ввода поиска |
|
||
| `.sort-icon.active` | Активная сортировка |
|
||
| `.pagination-wrapper` | Обёртка пагинации |
|
||
|
||
## Расширение функциональности
|
||
|
||
### Добавление кастомных действий
|
||
|
||
Для добавления кнопок действий в строки:
|
||
|
||
```twig
|
||
{% for client in clients %}
|
||
<tr>
|
||
<td>{{ client.name }}</td>
|
||
<td>{{ client.email }}</td>
|
||
<td class="actions-cell text-end">
|
||
<a href="{{ editUrl }}" class="btn btn-sm btn-outline-primary">
|
||
<i class="fa-solid fa-pen"></i>
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
```
|
||
|
||
### Кастомные строки
|
||
|
||
Компонент поддерживает произвольное содержимое ячеек через параметр `rows`:
|
||
|
||
```twig
|
||
{% set rows = [] %}
|
||
{% for product in products %}
|
||
{% set rows = rows|merge([{
|
||
cells: [
|
||
{ content: '<strong>' ~ product.name ~ '</strong>', class: '' },
|
||
{ content: product.price ~ ' ₽', class: 'text-end' }
|
||
],
|
||
actions: '<a href="...">Редактировать</a>'
|
||
}]) %}
|
||
{% endfor %}
|
||
|
||
{{ include('@components/table/table.twig', {
|
||
id: 'products-table',
|
||
rows: rows,
|
||
columns: columns,
|
||
...
|
||
}) }}
|
||
```
|