Концепция :meta эндпоинтов
Задача: В Административной панели требуется, чтобы администратор мог табличные списки гибко настраивать, выводить только интересующие столбцы, фильтры, менять сортировку. При этом добавление новых полей в сущность не должно требовать доработок табличного списка на фронте.
Для решения этой задачи реализуется следующее:
- На каждый табличный список бэк предоставляет эндпоинт
:meta
, который отдаёт информацию о каждом поле сущности, с детализацией как по этому полю фильтровать, доступна ли сортировка и т.д. - Фронт использует эту мета-информацию о сущности, чтобы построить страницу. Для получения самих данных необходимо использовать эндпоинт
:search
Таким образом можно настроить редактирование пользователем выводимых полей, а при добавлении нового поля, оно просто дополнительно регистрируется в :meta
Структура ответа эндпоинта :meta
{
"data": {
"fields": [ // Массив с описанием полей сущности
{
"code": "id", // Символьный код поля
"name": "ID", // Название поля
"list": true, // Доступно ли поле для вывода в таблицу
"type": "string", // Тип поля. Список актуальных доступных значений смотрите в документации админки, FieldTypeEnum
"enum_info": { // Заполняется, если поле типа `enum`. Доступно 2 варианта, либо статический список перечислений, либо догрузка списка через отдельный эндпоинт
"endpoint": "string", // Заполняется для динамических перечислений, тут будет относительный путь до эндпоинта от корня сервиса
"values": [ // Заполняется для статических перечислений
{
"id": "int/string/...",
"title": "string"
}
]
},
"sort": true, // Доступно ли поле для сортировки
"sort_key": "id", // Заполняется, если доступна сортировка. Этот ключ необходимо передавать в :search метод для сортировки по данному полю
"filter": "default", // Тип фильтра по данному полю. Список актуальных доступных значений смотрите в документации админки, FieldFilterTypeEnum
"filter_range_key_from": "id_from", // Если тип фильтра по диапазону - ключ, под которым необходимо передавать в :search значение ОТ
"filter_range_key_to": "id_to", // Если тип фильтра по диапазону - ключ, под которым необходимо передавать в :search значение ДО
"filter_key": "id" // Если фильтр обычный - ключ, под которым необходимо передавать в :search значение
}
],
"detail_link": "number", // Код поля, которое нужно использовать для вывода ссылки на детальную страницу. Такое поле пользователь не может скрыть
"default_sort": "id", // Код поля, по которому необходимо проводить сортировку по-умолчанию
"default_list": [ // Коды полей, которые по-умолчанию выводятся в таблицу
"id",
"created_at"
],
"default_filter": [ // Коды полей, которые по-умолчанию выводятся в фильтр
"id",
"created_at"
]
}
}
Эндпоинт для перечислений
Обычно структура этого эндпоинта одинаковая:
- Фильтрация по 2 полям: id (для получения значений по id, например для вывода значения в таблицу) или query (для автодополнения, при введении в фильтр).
- Массив объектов в ответе, каждый объект содержит id (int/string/...) + title (string)
Реализация на бэке
Со стороны бэкенда подготовлены хелперы для формирования списка полей:
new \App\Http\ApiV1\Support\Resources\ModelMetaResource([...])
- ресурс для формирования ответа\App\Domain\Common\Data\Meta\Field::{type}()
- получение объекта для поля нужного типа\App\Domain\Common\Data\Meta\Fields\AbstractField
- базовый класс для работы описанием поля, содержит методы для конфигурации описания\App\Domain\Common\Data\Meta\Enum\AbstractEnumInfo
- базовый класс для заполнения информации о Enum. Либо указываем name эндпоинта, либо загружаем статическое перечисление
В итоге стандартный :meta
эндпоинт выглядит примерно так:
public function propertiesMeta(AsyncLoadAction $action, PropertyTypeEnumInfo $types): ModelMetaResource
{
$action->execute([$types]);
return new ModelMetaResource([
Field::id()->listDefault()->filterDefault()->detailLink(),
Field::text('name', 'Рабочее название')->listDefault()->filterDefault()->resetSort(),
Field::text('display_name', 'Публичное название')->listDefault()->filterDefault()->resetSort(),
Field::text('code', 'Код')->listDefault()->filterDefault()->resetSort(),
Field::enum('type', 'Тип данных', $types)->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_active', 'Активный')->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_public', 'Выводить на витрине')->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_required', 'Обязательность')->listDefault()->filterDefault()->resetSort(),
Field::boolean('is_gluing', 'Параметр склеивания')->listDefault()->filterDefault()->resetSort(),
Field::datetime('created_at', 'Дата создания')->listDefault()->filterDefault(),
Field::datetime('updated_at', 'Дата обновления')->listDefault()->filterDefault(),
Field::boolean('is_filterable', 'Фильтр на витрине')->resetSort(),
Field::boolean('is_multiple', 'Множественный')->resetSort(),
Field::boolean('has_directory', 'Справочник')->resetSort(),
]);
}