Skip to content

[Feature] тип поля повторитель #299

@rasxod

Description

@rasxod

Описание функции

Добавить в MiniShop3 тип поля «повторитель» (repeater) — встроенный аналог MIGX для структурированных списков записей с фиксированным набором подполей.

Повторитель должен:

  • редактироваться в менеджере как грид / таблица строк (добавить, удалить);
  • поддерживать drag-and-drop сортировку строкmust-have для MVP (без этого повторитель теряет смысл по сравнению с textarea + JSON);
  • храниться в БД в колонке JSON (одна колонка на поле, массив объектов);
  • при сохранении автоматически проставлять индекс/rank в каждой строке (как MIGX после перетаскивания) — чтобы порядок был явным в данных, а не только «по позиции в массиве»;
  • быть доступен через существующую утилиту «Дополнительные поля» (ms3_extra_fields) и формы товара/заказа, где уже подключаются extra fields;
  • отдавать данные в API и сниппетах как массив структур в заданном порядке, а не как «сырой» JSON-строкой.

Пример доменной модели: у товара поле specifications — список { "name": "...", "value": "...", "rank": 0 }, у заказа — список позиций доп. атрибутов и т.п.

См. также: #300 — тип поля key=value (ms3-key-value) — UI «ключ → значение» для плоского ассоциативного массива (JSON object), без табличной схемы колонок. Проще repeater'а; удобен для КБЖУ, произвольных meta-пар и настроек.

Проблема, которую решает

Сейчас для повторяющихся структур в MiniShop3 остаются обходные пути:

  1. MODX TV + MIGX / MIGXdb — отдельная экосистема, не интегрированная с extra fields, гридами каталога и REST API компонента «из коробки»; но у MIGX есть ожидаемый UX: drag-and-drop + авто-rank при save — это нужно повторить.
  2. Плоское extra field dbtype=json — колонку можно создать в утилите дополнительных полей, но нет UI повторителя: редактор не знает схему подполей, нет валидации строк, нет сортировки строк, нет удобного грида в карточке товара.
  3. Встроенные JSON-массивы (tags, color, size у msProductData) — это простые списки значений, а не произвольная табличная структура с разными типами колонок.
  4. TV + join — для выборки и сортировки по вложенным данным нужны тяжёлые join к modx_site_tmplvar_*; при хранении в JSON-колонке модели MS3 (MySQL 5.7+ / MariaDB 10.3+) доступны фильтрация и ORDER BY по JSON-path без TV.

В итоге для типовых кейсов («характеристики», «комплектация», «галерея метаданных», «несколько адресов доставки») приходится тянуть сторонние дополнения или писать кастомные формы/плагины.

Предлагаемое решение

1. Новый xtype / тип виджета: repeater (или ms3-repeater)

В ms3_extra_fields (и связанной Vue-админке) — отдельный тип поля поверх существующих xtype, dbtype, phptype:

Уровень Значение Смысл
dbtype json Колонка MySQL/MariaDB типа JSON; в БД хранится JSON-массив [{...}, {...}]
phptype json Стандарт MS3/xPDO 3 для JSON-колонок: в PHP после загрузки — array, не строка (в ExtraFieldsManager подписано «json (массив)»)
xtype ms3-repeater (рабочее имя) Виджет грида строк в менеджере

Контракт значения (важно для API и процессоров):

Слой Тип Пример
БД JSON array [{"name":"Материал","value":"Хлопок","rank":0}, …]
PHP / $product->get('specifications') array (индексированный список assoc-массивов) [['name'=>'…', 'value'=>'…', 'rank'=>0], …]
REST API / Vue array тот же список объектов, без JSON-строки
Процессор Data array 'specifications' => [['name'=>'…', 'value'=>'…'], …] — как в #297

Feedback @bs110060 (24.05.2026, phptype): «а массив не?» — да: в коде и API repeater — это массив. Отдельного phptype: array в extra fields MS3 сейчас нет; для JSON-колонок используется phptype: json, который в xPDO 3 автоматически decode/encode в PHP array (см. msProductData::getArraysValues(), лексикон ms3_vue_phptype_json = «json (массив)»). Для repeater в документации и контракте API явно писать array of rows, а phptype: json — техническая метадата колонки, как у tags / color / size.

Дополнительно — схема колонок повторителя (конфиг в ms3_extra_fields, отдельная колонка или JSON):

{
  "columns": [
    { "key": "name", "label": "Название", "xtype": "textfield", "required": true },
    { "key": "value", "label": "Значение", "xtype": "textfield" }
  ],
  "minRows": 0,
  "maxRows": null,
  "sortable": true,
  "rankField": "rank"
}

Поле rankField (по умолчанию rank, аналог MIGX MIGX_id / rank) — служебное: не редактируется вручную в гриде, заполняется автоматически при save по текущему порядку строк после drag-and-drop (0, 1, 2… или 0, 10, 20… — на усмотрение реализации, главное — монотонный порядок).

Feedback @bs110060 (24.05.2026): сортировка строк — must-have. Отдельный индекс в схеме «на всякий случай» избыточен, если пользователь сам его заводит; но при наличии drag-and-drop индекс должен проставляться автоматически при сохранении — как в MIGX, этим активно пользуются в сниппетах и кастомной логике.

2. UI менеджера

  • В ExtraFieldsManager — настройка схемы колонок при создании поля типа «повторитель»; флаг sortable (по умолчанию true).
  • В карточке товара / заказа — PrimeVue DataTable или аналог: строки, inline-edit, drag-and-drop сортировка строк (обязательно в MVP), кнопки «Добавить / Удалить».
  • Порядок строк при save (двойной контракт, как у MIGX):
    1. Порядок элементов в JSON-массиве = порядок после drag-and-drop в редакторе (основной источник истины для {foreach} / API).
    2. Авто-заполнение rankField в каждой строке: rank: 0, rank: 1, … по позиции после DnD — чтобы плагины/шаблоны могли сортировать явно (usort по rank, Fenom @sort:), как привыкли пользователи MIGX.
  • При добавлении новой строки — в конец списка; при save пересчитать rank у всех строк.
  • При удалении строки — пересчитать rank у оставшихся.
  • Сохранение — сериализация в JSON-массив объектов в колонку модели (msProductData, msOrder, …).
  • При чтении API/формы — массив в порядке JSON-массива; дополнительно можно отдавать/нормализовать по rankField ASC, если порядок в payload нарушен.

Не в MVP: сортировка по колонке кликом в заголовке таблицы (UI table sort) — только DnD.

3. Бэкенд

  • Валидация payload по схеме (Rakit / существующие паттерны MS3).
  • Нормализация при save: пересчёт rankField по порядку массива; strip/reject дубликатов rank при необходимости.
  • Нормализация JSON (как для phptype: json → PHP array в ProductDataService::prepareObject).
  • При save: принимать PHP-массив (или JSON-массив в HTTP body — decode до array); отклонять строку с «сырым» JSON, если передана без decode.
  • loadMap() / Object Extension — без изменения контракта: поле остаётся одной JSON-колонкой.
  • API менеджера и публичный слой — отдавать декодированный массив, не строку.

4. Выборка, фильтрация и сортировка (MySQL 5.7+ / MariaDB 10.3+)

Исходная идея автора (@rasxod): хранить структурированные данные в JSON-колонке модели MS3, а не в TV — тогда поиск, фильтрация и сортировка по вложенным полям делаются через JSON-функции SQL без join к modx_site_tmplvar_* / MIGXdb. Это особенно полезно для гридов каталога, REST-фильтров и отчётов.

Фильтрация

-- товары, у которых в specifications есть name = 'Материал'
JSON_CONTAINS(
  ms3_products.specifications,
  JSON_OBJECT('name', 'Материал'),
  '$'
)

Сортировка каталога по значению внутри JSON

-- по полю внутри массива объектов (path к конкретному ключу)
ORDER BY JSON_UNQUOTE(JSON_EXTRACT(ms3_products.specifications, '$[0].value')) ASC

Сортировка строк внутри одного товара (repeater)

  • В UI: drag-and-drop → порядок массива + авто-rank при save.
  • В API / Fenom: итерация по массиву в сохранённом порядке; при необходимости сортировка по rank / rankField до вывода (совместимость с MIGX-мышлением).
  • Через API save: если клиент передал массив без rank — бэкенд проставляет rank по порядку элементов.

Manager API / pdoTools (follow-up)

  • параметры вида sortBy=specifications.0.value / filter[specifications.name]=Материал с маппингом на безопасные JSON-path (whitelist из схемы extra field);
  • generated column + index на часто используемый path — если понадобится производительность на больших каталогах.

На этапе MVP достаточно фильтрации + документированных SQL-примеров; полноценный sort/filter в Grid API — follow-up.

5. Область первой итерации (MVP)

  • Extra fields для классов msProductData, msOrder, msOrderAddress (по аналогии с текущими extra fields).
  • Редактирование в Vue-менеджере.
  • Drag-and-drop сортировка строк + авто-rankField при save (MIGX-паттерн).
  • Чтение в API / Fenom / pdoTools.
  • Документация + пример схемы «характеристики товара».

Вне MVP (follow-up): импорт CSV, сортировка/фильтры грида каталога по JSON-path в Manager API, nested repeaters, версионирование схемы, ручное редактирование rank в UI. Смежный тип key=value — отдельная задача #300.

Альтернативы

Вариант Плюсы Минусы
MIGX / MIGXdb (TV) Зрелый UI, DnD + rank, привычен сообществу MODX Вне MS3, дублирование данных, слабая связь с extra fields и Manager API
Плоский json extra field + textarea Уже можно создать колонку Нет UX, нет сортировки, легко сломать JSON
Key=value (#300) Проще для flat map (КБЖУ, meta) Не подходит для однотипных строк с несколькими колонками
Только порядок массива, без rank Меньше полей в JSON Ломает ожидания пользователей MIGX; сложнее отладка и кастомная сортировка
Rank только вручную в схеме Гибко Без DnD + auto-rank — лишняя работа редактору; feedback: излишне
Отдельная таблица ms3_repeater_rows Нормализованная модель, простые SQL-join Сложнее миграции и API; избыточно для MVP
MODX Collections / custom tables Гибко для больших объёмов Вне компонента, выше порог входа

Предлагаемый баланс: JSON-колонка + схема в extra field + UI повторителя с DnD и авто-rank при save — минимальные изменения схемы БД при сохранении удобства MIGX-подобного редактора.

Примеры использования

Характеристики товара (после save с DnD)

[
  { "name": "Материал", "value": "Хлопок 100%", "rank": 0 },
  { "name": "Страна", "value": "Россия", "rank": 1 }
]

Порядок на витрине = порядок в массиве (= rank ASC после нормализации на бэкенде).

Комплектация (несколько SKU в одной карточке)

[
  { "sku": "BOX-XL-01", "qty": 1, "label": "Коробка XL", "rank": 0 },
  { "sku": "RIBBON-RED", "qty": 2, "label": "Лента", "rank": 1 }
]

Передача через процессор / API (после #297)

$response = $modx->runProcessor('MiniShop3\\Processors\\Product\\Create', [
    'pagetitle' => 'Бокс «Весенний каприз» XL',
    'class_key' => \MiniShop3\Model\msProduct::class,
    'parent' => 409,
    'Data' => [
        'price' => 5200,
        'specifications' => [
            ['name' => 'Объём', 'value' => 'XL'],
            ['name' => 'Наполнение', 'value' => 'Ассорти'],
        ],
    ],
]);
// бэкенд при save проставит rank: 0, 1 по порядку массива

Fenom / витрина

{* порядок массива уже после DnD + save *}
{foreach $specifications as $row}
  <dt>{$row.name}</dt><dd>{$row.value}</dd>
{/foreach}

{* или явно по rank, как в MIGX-сниппетах *}
{foreach $specifications|sort:'rank':'asc' as $row}
  ...
{/foreach}

Дополнительный контекст

  • MySQL JSON (5.7+) и MariaDB JSON (10.3+) поддерживают поиск, фильтрацию и сортировку по JSON-path — ключевое преимущество перед TV+MIGX: одна колонка в ms3_products / ms3_orders вместо join к таблицам TV, при этом ORDER BY JSON_EXTRACT(...) и JSON_CONTAINS для выборок в каталоге и отчётах.
  • В MS3 уже есть задел: утилита extra fields (ExtraFields, ExtraFieldsManager.vue), типы dbtype=json / phptype=json, обработка JSON в ProductDataService и msProductData::getArraysValues().
  • Аналоги: MIGX (MODX — DnD + rank при save), Filament Repeater (Laravel), repeater/meta fields в Shopify metafields, WooCommerce custom product attributes.
  • Для плоских key-value пар см. #300 (Filament KeyValue, КБЖУ).

Связанные области кода (ориентиры для реализации):

  • core/components/minishop3/src/Utils/ExtraFields.php
  • vueManager/src/components/ExtraFieldsManager.vue
  • core/components/minishop3/src/Services/Product/ProductDataService.php
  • миграции / модель msExtraField

Критерии приёмки (черновик):

  • В extra fields можно создать поле типа «повторитель» со схемой колонок и sortable: true.
  • В карточке сущности — грид редактирования строк; данные сохраняются в JSON-колонку (массив).
  • Drag-and-drop меняет порядок строк (must-have); порядок массива сохраняется и воспроизводится в API/Fenom.
  • При save автоматически проставляется rankField (по умолчанию rank) в каждой строке по порядку после DnD — MIGX-паттерн.
  • API save без rank в payload — бэкенд нормализует rank по порядку массива.
  • API возвращает массив объектов (array), не JSON-строку; некорректный payload/схема отклоняются с понятной ошибкой.
  • Документация: DnD, auto-rank, save через процессор (Data), вывод Fenom.
  • (Опционально) фильтр и сортировка каталога по JSON-path в Manager API / pdoTools; SQL-примеры ORDER BY JSON_EXTRACT в документации.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions