Skip to content
  1. Компоненты
  2. MiniShop3
  3. Интерфейс админки
  4. Утилиты
  5. Колонки гридов

Утилиты: Колонки гридов ​

Настройка колонок в таблицах админки MiniShop3.

Назначение ​

Утилита «Колонки гридов»

Cookbook

Пошаговые примеры badge-колонки в заказах и редактирование прямо в таблице: Cookbook колонок грида.

  • Включать и отключать колонки
  • Изменять порядок колонок
  • Настраивать сортировку и фильтрацию
  • Задавать ширину колонок
  • Добавлять свои колонки
  • Настраивать редактирование прямо в таблице (для грида category-products)

Начиная с версии 1.7.0

Системная настройка ms3_category_grid_fields удалена. Настройка колонок таблицы товаров в категории выполняется только через этот интерфейс.

Доступные гриды ​

ГридОписание
customersСписок покупателей
ordersСписок заказов
category-productsСписок товаров в категории
order_productsТовары в заказе
vendorsСписок производителей

Есть и шестой грид из поставки — deliveries. В выпадающем списке утилиты его нет, настраивается он только через GET и PUT /api/mgr/grid-config/deliveries.

Интерфейс ​

Выбор грида ​

Вверху страницы выберите грид из списка.

Действия ​

  • Перетаскивание — изменение порядка колонок
  • Редактирование — клик на строку открывает диалог
  • Добавление — кнопка для создания новой колонки

Параметры колонки ​

Основные ​

ПараметрОписание
Имя поляИмя поля из модели или алиас
НазваниеЗаголовок колонки
ВидимостьОтображать колонку
СортировкаРазрешить сортировку по клику
ФильтрацияПоказать поле фильтра
ЗамороженаФиксировать при прокрутке

Размеры ​

ПараметрОписание
ШиринаШирина в пикселях или %
Мин. ширинаМинимальная ширина при изменении размера

Тип колонки ​

ТипОписание
modelПоле из модели данных
templateШаблонная колонка (HTML)
relationДанные из связанной таблицы
badgeЦветная метка (source_field, color_field)
optionЗначение опции товара (грид category-products)
computedВычисляемое значение
imageОтображение изображения
booleanФлаг да/нет
priceДенежное значение (displayConfig: валюта, decimals)
weightВес (displayConfig: unit, decimals)
datetimeДата и время (displayConfig: format)
actionsКолонка действий

Типы колонок ​

Поле модели (model) ​

Стандартная колонка: значение поля.

text
Тип: model
Имя поля: email
Название: Email

Шаблон (template) ​

Колонка с HTML-шаблоном.

text
Тип: template
Шаблон: <a href="mailto:{email}">{email}</a>

Доступные переменные — поля текущей записи в фигурных скобках.

Цветная метка (badge) ​

Цветная метка по тексту и HEX-цвету из других полей строки. В гриде orders колонка order_status берёт подпись из status_name и цвет из status_color.

text
Тип: badge
source_field: status_name
color_field: status_color

Шаблон безопасен по данным

Подстановка ловит только {имя_поля} из букв, цифр и подчёркиваний, а значение прогоняется через HTML-экранирование. Разметку задаёт сам шаблон: HTML, попавший в данные записи, выведется текстом и не исполнится (#920). Пустое значение и null дают пустую строку.

Опция товара (option) ​

Колонка опции в гриде category-products. В конфиге укажите option.key (ключ опции из msOption).

text
Тип: option
option.key: color

Имя колонки проверяется на коллизию

В гриде category-products имя своей колонки типа option или relation не должно совпадать ни с одной из 24 встроенных колонок товара, ни с зарезервированным словом SQL. Опция color в примере выше — именно такой случай: встроенная колонка color уже есть.

Сервер отклонит такое имя и подскажет альтернативу — option_color для опции, vendor_address для связи. Сам option.key при этом остаётся прежним, меняется только имя колонки в гриде.

Связь (relation) ​

Данные из связанной таблицы.

Параметры связи:

ПараметрОписание
ТаблицаКороткое имя модели MiniShop3 (msOrder), полное имя класса или имя таблицы без префикса
Внешний ключПоле для связи
Отображаемое полеКакое поле показывать
АгрегацияCOUNT, SUM, AVG, MIN, MAX. В гриде category-products агрегация не поддерживается — такая колонка не сохранится

Пример — количество заказов покупателя:

text
Тип: relation
Таблица: msOrder
Внешний ключ: customer_id
Агрегация: COUNT

Вычисляемое значение (computed) ​

Значение вычисляется на сервере. В JSON config обязателен ключ computed.className (класс реализует ComputedFieldInterface):

json
{
  "type": "computed",
  "computed": {
    "className": "MyComponent\\Columns\\TotalSpentColumn"
  }
}

Изображение (image) ​

Миниатюра изображения.

text
Тип: image
Имя поля: image

Флаг (boolean) ​

Флаг с иконкой.

text
Тип: boolean
Имя поля: active

Цена (price) ​

Числовое поле как цена. Параметры в JSON displayConfig:

КлючОписание
decimalsЗнаков после запятой
currencyСимвол валюты
currency_positionbefore или after
thousands_separatorРазделитель тысяч
decimal_separatorРазделитель дробной части
text
Тип: price
Имя поля: price
displayConfig: {"decimals":2,"currency":"₽","currency_position":"after","thousands_separator":" "}

Значения по умолчанию зашиты в код: decimals 2, разделитель тысяч — пробел, дробной части — запятая, валюта пустая, позиция after. Системных настроек цены грид не читает. У веса дефолты свои — 2 знака и «кг».

Вес (weight) ​

Вес отображается с единицей измерения:

text
Тип: weight
Имя поля: weight
displayConfig: {"decimals":2,"unit":"кг","unit_position":"after"}

Дата и время (datetime) ​

text
Тип: datetime
Имя поля: createdon
displayConfig: {"format":"dd.MM.yyyy HH:mm"}

Формат — собственный шаблон MiniShop3, а не PrimeVue. Поддерживаются токены yyyy, yy, MM, dd, HH, mm, ss; каждый заменяется ровно один раз, поэтому повтор токена в шаблоне не отработает.

Действия (actions) ​

Колонка с кнопками действий. Встроенные и свои обработчики.

Конфигурация действий:

json
[
  {
    "name": "edit",
    "handler": "edit",
    "icon": "pi-pencil",
    "label": "Редактировать",
    "severity": null
  },
  {
    "name": "delete",
    "handler": "delete",
    "icon": "pi-trash",
    "label": "Удалить",
    "severity": "danger",
    "confirm": true,
    "confirmMessage": "Вы уверены, что хотите удалить запись?"
  }
]

Параметры inline-редактирования колонки:

ПараметрОписание
editableРазрешить правку значения прямо в строке
editor_typeРедактор: text, number, select, combo
editor_optionsВарианты для select
editor_referenceСвязанная модель для combo
editor_combo_endpointПуть, откуда combo берёт варианты. Принимается только из списка разрешённых

Сквозной пример — в Cookbook настройки гридов.

Параметры действия:

ПараметрТипОписание
namestringУникальное имя действия
handlerstringИмя обработчика из реестра
iconstringПолный класс PrimeIcons, например pi-pencil
labelstringТекст подсказки / ключ лексикона
severitystringСтиль кнопки: danger, success, secondary, info, warn
confirmbooleanТребовать подтверждение
confirmMessagestringТекст подтверждения
visiblebooleanВидимость кнопки
disabledboolean/functionОтключение кнопки
disabledFieldstringПоле записи для проверки disabled
confirmTitlestringЗаголовок окна подтверждения, ключ лексикона
confirmAcceptstringПодпись кнопки согласия
confirmRejectstringПодпись кнопки отказа
permissionstringПраво MODX: без него кнопка не рисуется
toggleFieldstringПоле записи, по которому кнопка переключается. Штатный механизм кнопки публикации
iconOffstringИконка, когда toggleField истинно
labelOffstringПодпись, когда toggleField истинно

В тексте подтверждения работает подстановка {поле} — значения берутся из строки записи.

Встроенные обработчики:

ОбработчикОписание
editОткрыть запись на редактирование
deleteУдалить запись
viewПросмотр записи
addressesУправление адресами (для покупателей)
publishПереключить публикацию, работает с toggleField
duplicateОткрыть окно дублирования ресурса MODX
refreshОбновить грид

publish и duplicate поставляются в конфигурации грида товаров категории.

Примеры настройки ​

Добавить колонку «Сумма заказов» ​

  1. Нажмите «Добавить колонку»
  2. Заполните:
    • Имя: total_spent
    • Название: Сумма заказов
    • Тип: relation
    • Таблица: msOrder
    • Внешний ключ: customer_id
    • Отображаемое поле: cost
    • Агрегация: SUM
  3. Сохраните

Ссылка в колонке email ​

  1. Найдите колонку email.
  2. Смените тип на template.
  3. Шаблон: <a href="mailto:{email}">{email}</a>.
  4. Сохраните.

API-эндпоинты ​

Получение конфигурации грида ​

http
GET /api/mgr/grid-config/{grid_name}

Ответ:

json
{
  "success": true,
  "data": {
    "columns": [
      {
        "name": "id",
        "label": "ID",
        "visible": true,
        "sortable": true,
        "filterable": false,
        "frozen": true,
        "width": 60,
        "minWidth": "80px",
        "isSystem": true,
        "type": "model"
      }
    ],
    "direct_filter_keys": ["query", "status_id"]
  }
}

Ключи minWidth и isSystem приходят в camelCase; в базе колонка называется min_width, и в теле запроса на сохранение тоже используется minWidth. Поверх них разворачивается JSON из config.

Ключ editor_references присутствует только у grid_key=category-products. У остальных гридов его в ответе нет вовсе — не пустой массив, а отсутствующий ключ.

Работа с отдельной колонкой ​

Кроме сохранения всей конфигурации есть два точечных роута:

МетодРоутОписание
POST/api/mgr/grid-config/{grid_key}/fieldДобавить колонку. В теле field_name, label, type, config и прочие параметры колонки
PUT/api/mgr/grid-config/{grid_key}/field/{field_name}Изменить одну колонку. Системные колонки этим путём не правятся

Точечные роуты не трогают остальные колонки — в отличие от полного сохранения ниже.

Сохранение конфигурации ​

http
PUT /api/mgr/grid-config/{grid_key}

Тело запроса:

json
{
  "fields": [
    {
      "name": "id",
      "label": "ID",
      "visible": true,
      "sortable": true,
      "filterable": false,
      "frozen": true,
      "width": 60,
      "type": "model"
    }
  ]
}

Тело запроса — полный список колонок

Сохранение сверяет присланный fields с тем, что лежит в базе, и удаляет все несистемные колонки, которых в массиве нет. Частичный payload с одной изменённой колонкой снесёт все остальные свои колонки этого грида.

Правильный порядок: прочитать конфигурацию через GET, изменить нужное в полученном массиве и отправить его целиком.

Права

GET /api/mgr/grid-config/{grid_key} требует view_document. Запись (PUT, POST, DELETE колонки) — mssetting_save.

Удаление колонки ​

http
DELETE /api/mgr/grid-config/{grid_key}/{field_name}

Системные колонки (is_system) удалить нельзя.

Системные колонки ​

Системные колонки ограничены:

  • Нельзя удалить
  • Нельзя править точечно через диалог колонки: PUT /api/mgr/grid-config/{grid}/field/{name} отвечает «Cannot modify system field»
  • Через общее сохранение конфигурации (PUT /api/mgr/grid-config/{grid_key} со всем массивом fields) меняются подпись, видимость, сортировка, фильтрация, закрепление, ширина и порядок

Обычно это id и колонки действий.

Свои действия ​

Глобальный реестр MS3ActionRegistry добавляет свои кнопки в колонку действий.

Реестр действий ​

Реестр доступен как window.MS3ActionRegistry:

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

API реестра ​

register(name, handler, options) ​

Регистрирует обработчик действия.

Параметры:

ПараметрТипОписание
namestringИмя действия
handlerfunctionФункция-обработчик (data, context) => void
options.overridebooleanРазрешить перезапись существующего обработчика

Параметры handler:

  • data — объект данных строки грида
  • context — контекст выполнения:
    • gridId — идентификатор грида
    • emit(event, data) — отправка события
    • refresh() — обновление грида
    • toast — сервис уведомлений PrimeVue
    • confirm — сервис подтверждений PrimeVue
    • _(key) — функция локализации

registerBeforeHook(actionName, hook) ​

Регистрирует хук, выполняемый перед действием.

javascript
MS3ActionRegistry.registerBeforeHook('delete', (data, context) => {
  // Вернуть false для отмены действия
  if (data.is_system) {
    context.toast.add({
      severity: 'warn',
      summary: 'Запрещено',
      detail: 'Нельзя удалить системную запись'
    })
    return false
  }
  return true
})

registerAfterHook(actionName, hook) ​

Регистрирует хук, выполняемый после действия.

javascript
MS3ActionRegistry.registerAfterHook('delete', (data, context, result) => {
  console.log('Запись удалена:', data.id)
  // Можно отправить аналитику, логировать и т.д.
})

Другие методы ​

МетодОписание
has(name)Проверить наличие обработчика
get(name)Получить обработчик
unregister(name)Удалить обработчик. Не снимаются только edit, delete, view и refresh; addresses, publish и duplicate снять можно
getRegisteredActions()Получить список всех зарегистрированных действий
execute(name, data, context)Выполнить действие программно
getActionInfo(name)Метаданные обработчика: ключ подписи, иконка. По ним редактор действий строит выпадающий список

Примеры своих действий ​

Пример 1: Блокировка покупателя ​

Шаг 1. Регистрация обработчика (в плагине MODX или своём JS):

javascript
// Файл: assets/components/mycomponent/js/customer-actions.js

document.addEventListener('DOMContentLoaded', () => {
  // Ждём загрузки реестра
  if (!window.MS3ActionRegistry) {
    console.error('MS3ActionRegistry not available')
    return
  }

  // Регистрируем действие "Заблокировать"
  MS3ActionRegistry.register('blockCustomer', async (data, context) => {
    try {
      const response = await fetch('/assets/components/minishop3/connector.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          action: 'MyComponent\\Processors\\Customer\\Block',
          id: data.id,
          HTTP_MODAUTH: MODx.siteId
        })
      })

      const result = await response.json()

      if (result.success) {
        context.toast.add({
          severity: 'success',
          summary: 'Успех',
          detail: `Покупатель ${data.email} заблокирован`,
          life: 3000
        })
        context.refresh() // Обновить грид
      } else {
        throw new Error(result.message)
      }
    } catch (error) {
      context.toast.add({
        severity: 'error',
        summary: 'Ошибка',
        detail: error.message,
        life: 5000
      })
    }
  })

  // Регистрируем действие "Разблокировать"
  MS3ActionRegistry.register('unblockCustomer', async (data, context) => {
    const response = await fetch('/assets/components/minishop3/connector.php', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        action: 'MyComponent\\Processors\\Customer\\Unblock',
        id: data.id,
        HTTP_MODAUTH: MODx.siteId
      })
    })

    const result = await response.json()
    if (result.success) {
      context.toast.add({
        severity: 'success',
        summary: 'Покупатель разблокирован',
        life: 3000
      })
      context.refresh()
    }
  })
})

Шаг 2. Подключение скрипта через плагин MODX:

php
<?php
// Плагин: MyCustomerActions
// События: OnManagerPageBeforeRender

if ($modx->event->name !== 'OnManagerPageBeforeRender') return;

// Только на странице покупателей
$controller = $modx->controller ?? null;
if (!$controller || strpos(get_class($controller), 'Customers') === false) return;

$modx->regClientStartupScript(
    MODX_ASSETS_URL . 'components/mycomponent/js/customer-actions.js'
);

Шаг 3. Настройка колонки действий.

Через диалог колонка actions не сохраняется

Во всех шести гридах из поставки колонка actions помечена is_system = 1. Иконка карандаша рядом с ней не заблокирована, диалог откроется и даже даст добавить действие, но сохранение шлёт PUT /api/mgr/grid-config/{grid}/field/actions и получает «Cannot modify system field».

Рабочий путь — общее сохранение конфигурации: PUT /api/mgr/grid-config/customers с полным массивом fields, где у колонки actions подменён ключ actions. Системность там не проверяется.

Новое действие в массиве actions колонки:

json
{
  "name": "blockCustomer",
  "handler": "blockCustomer",
  "icon": "pi-ban",
  "severity": "danger",
  "confirm": true,
  "confirmMessage": "Заблокировать покупателя {email}?"
}

Своего обработчика не будет в списке

Поле «Обработчик» в редакторе действий — выпадающий список без свободного ввода, и он показывает только то, что уже зарегистрировано в реестре на текущей странице. Плагин из шага 2 подключает скрипт лишь на странице покупателей, поэтому на странице утилит blockCustomer в списке не появится. Либо снимите в плагине фильтр по контроллеру, либо задавайте handler правкой JSON через API.

Пример 2: Копирование товара ​

Свой обработчик здесь нужен не всегда: duplicate входит в семь встроенных действий реестра, и в гриде товаров категории он уже открывает окно modx-window-resource-duplicate и перезагружает таблицу. Зарегистрировать своё действие под именем duplicate без options.override реестр не даст.

Пример ниже пригодится для другого грида или если нужно своё поведение. Штатного процессора дублирования в MiniShop3 нет, поэтому вызывается ядерный MODX resource/duplicate (msProduct наследует ресурс):

javascript
MS3ActionRegistry.register('duplicateProduct', async (data, context) => {
  const response = await fetch('/assets/components/minishop3/connector.php', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      action: 'resource/duplicate',
      id: data.id,
      HTTP_MODAUTH: MODx.siteId
    })
  })

  const result = await response.json()

  if (result.success) {
    context.toast.add({
      severity: 'success',
      summary: 'Товар скопирован',
      detail: `Создан товар ID: ${result.object.id}`,
      life: 3000
    })
    context.refresh()
  }
})

Конфигурация действия:

json
{
  "name": "duplicate",
  "handler": "duplicateProduct",
  "icon": "pi-copy",
  "label": "Копировать",
  "severity": "secondary",
  "confirm": false
}

Пример 3: Отправка уведомления ​

Штатного процессора отправки уведомления нет — REST-роуты /api/mgr/notifications отдают только CRUD конфигураций. Для кнопки «Отправить» реализуйте свой эндпоинт (внутри — NotificationManager или StatusChangedNotification) и вызовите его из обработчика:

javascript
MS3ActionRegistry.register('sendNotification', async (data, context) => {
  const response = await fetch('/assets/components/mycomponent/notify.php', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ customer_id: data.id })
  })

  const result = await response.json()

  if (result.success) {
    context.toast.add({
      severity: 'success',
      summary: 'Уведомление отправлено',
      detail: `Email: ${data.email}`,
      life: 3000
    })
  }
})

Пример 4: Условная видимость кнопки ​

Кнопку отключают полем записи disabledField:

javascript
// В конфигурации колонки
{
  "name": "unblock",
  "handler": "unblockCustomer",
  "icon": "pi-unlock",
  "label": "Разблокировать",
  "severity": "success",
  // Кнопка неактивна, если покупатель не заблокирован
  "disabledField": "active"  // disabled когда active = true
}

Хуки встроенных действий ​

Встроенные edit и delete тоже идут через реестр, поэтому на них вешаются те же хуки.

Запись удалений в журнал аудита:

javascript
MS3ActionRegistry.registerAfterHook('delete', async (data, context, result) => {
  // Отправить в систему аудита
  await fetch('/api/audit/log', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      action: 'delete',
      entity: context.gridId,
      entityId: data.id,
      user: MODx.user?.id,
      timestamp: new Date().toISOString()
    })
  })
})

Запрет удаления:

javascript
MS3ActionRegistry.registerBeforeHook('delete', (data, context) => {
  // Не давать удалять оплаченные заказы
  if (context.gridId === 'orders' && data.status === 2) {
    context.toast.add({
      severity: 'error',
      summary: 'Запрещено',
      detail: 'Оплаченный заказ удалить нельзя',
      life: 5000
    })
    return false // Отменить действие
  }
  return true // Продолжить
})

Доступные иконки ​

Иконки — PrimeIcons. В выпадающем списке редактора действий их семнадцать: pi-pencil, pi-trash, pi-eye, pi-check, pi-times, pi-ban, pi-lock, pi-unlock, pi-user, pi-cog, pi-copy, pi-download, pi-external-link, pi-list, pi-refresh, pi-send, pi-print.

Произвольный класс PrimeIcons задаётся только правкой JSON конфигурации колонки через API.