Разметка вёрстки
Разметка — основа всей работы с компонентом. Вы берёте готовую вёрстку и навешиваете на нужные элементы атрибуты data-mpc-*: помечаете, где секция, где редактируемое поле, где список, где вызов сниппета. Переписывать вёрстку не нужно — только пометить. По этой разметке нарезка раскладывает шаблон на файлы-секции и чанки, на месте полей ставит плейсхолдеры Fenom, а контент переносит в админку.
Подсказка
Скопируйте в проект файл assets/components/migxpageconfigurator/css/mpc.css — тогда IDE будет подсказывать доступные атрибуты прямо во время разметки.
Осторожно
У всех атрибутов префикс data-mpc-.
Прежде чем размечать секции, готовят каркас, общий для всех страниц шаблона: индексный файл (он отдаёт готовую страницу на фронт) и секцию-обёртку (wrapper), внутрь которой выводятся все остальные секции. Порядок такой:
- индексный файл шаблона —
pages/index.tpl; - базовая секция-обёртка —
wrapper; - заголовок шаблона;
- разметка секций.
Индексный файл шаблона
Каждый MODX-шаблон, который создаёт компонент, в теле (поле «Содержимое шаблона») просто подключает один общий файл:
{include 'file:pages/index.tpl'}Этот файл — pages/index.tpl — единый «движок вывода» для всех страниц. Готовый образец поставляется в пакете: core/components/migxpageconfigurator/examples/pages/index.tpl. Его копируют в проект — в папку pages/ (путь резолвится pdoTools как @FILE относительно pdotools_elements_path). Образец минимальный:
{set $path = '!getParsedConfigPath' | snippet:[]}
{if $path}
{include $path}
{/if}Как это работает:
- сниппет
getParsedConfigPathвозвращает путь к запечённой странице — файлуparsed/<id ресурса>.tpl. В нём все секции собраны в один файл, но запечена прежде всего структура: статичные значения подставлены, а динамика остаётся «живой» и вычисляется уже при отдаче страницы — переводимые значения ({'…' | lexicon}), статические секции (черезgetStaticSection), некэшируемые сниппеты и Fenom-логика. Поэтому, например, переключение языка не требует перенарезки. Если такого файла ещё нет, страница рендерится на лету и сохраняется (см. Рендеринг); - если путь получен — страница подключается из готового файла, без парсинга конфига на каждый запрос;
- при желании в файл добавляют ветку
{else}с выводом{$_modx->resource.content}— запасной вариант для ресурсов, к которым компонент не применён (так сделано в демо-проекте).
Подсказка
Образец почти не зависит от проекта — обычно его копируют один раз и больше не трогают. Главное помнить: тело каждого шаблона = {include 'file:pages/index.tpl'}, а всю реальную вёрстку задают обёртка и секции.
Базовая секция-обёртка (wrapper)
Wrapper — это «рама» страницы: <!DOCTYPE>, <head> с подключением стилей и скриптов, шапка, подвал — всё, что общее для всех страниц. Внутри неё есть зона контента, куда по очереди выводятся остальные секции конкретной страницы.
Помечается как обычная секция, но с именем из системной настройки mpc_wrapper_name (по умолчанию wrapper):
<div data-mpc-section="wrapper" class="wrapper" data-mpc-unwrap="1">
<!-- ...шапка, общие блоки... -->
<main class="wrap">
<!--CONTENT-->
{if $sections}
{foreach $sections as $section}
{$section}
{/foreach}
{else}
{$content}
{/if}
<!--CONTENT-->
</main>
<!-- ...подвал... -->
</div>Что здесь важно:
data-mpc-section="wrapper"— имя должно совпадать сmpc_wrapper_name. В эту секцию «заворачиваются» все остальные: каждый шаблон ссылается на файл обёртки (@FILE sections/wrapper.tpl), а уже она внутри выводит секции страницы.data-mpc-unwrap="1"— опционально. Если поставить, сам тег-обёртка (<div class="wrapper">) в результат не попадёт — останется только его содержимое. Удобно, когда лишняя обёртка в разметке не нужна.- Вывод секций делает Fenom-цикл
{foreach $sections as $section}: если у страницы есть секции ($sections— массив, собранный компонентом), они выводятся по порядку; если секций нет — показывается обычное полеcontentресурса (запасной вариант). Именно этот цикл и выполняет всю работу. - Парные
<!--CONTENT-->— это обычные HTML-комментарии: код их не обрабатывает и от них ничего не зависит. Они стоят лишь как визуальная пометка контентной зоны для того, кто читает шаблон, — можно убрать без последствий.
Этот блок цикла пишется один раз в обёртке и дальше не меняется — отдельные секции его не касаются.
Что доступно в обёртке
Обёртка обрабатывается как Fenom-чанк, в который передаются все данные ресурса. Поэтому в ней доступны плейсхолдеры:
$sections— массив секций страницы (что это за массив — см. врезку ниже);- поля ресурса —
$id,$pagetitle,$longtitle,$description,$introtext,$content,$templateи прочие. Контентные поля наследуются от «типа страницы»: если у ресурса поле пустое, подставляется значение типа. Какие поля участвуют — задаёт системная настройкаmpc_editable_resource_fields(по умолчаниюlongtitle,description,introtext,content,menutitle); структурные и защищённые поля (id,alias,uri,template,parent,pagetitle…) каскадом не затрагиваются и всегда берутся у самого ресурса; $tvs— TV ресурса. Каскад от «типа страницы» работает так же: TV типа подставляется там, где у ресурса собственного значения нет;$contacts— данные контактов;$sbp_id— id ресурса со статичными блоками,$cp_id— id ресурса с контактами;$gallery— галерея товара, если установлен miniShop2 и у ресурса есть файлы.
Как добавить условие в рендер
Здесь важный нюанс: $sections — это массив уже готовых HTML-строк секций, отсортированных по позиции. В цикле {$section} — это отрендеренная строка, у неё нет метаданных (имени, типа). Поэтому условие «обернуть конкретную секцию по её имени» прямо в обёртке написать нельзя — по имени там не за что зацепиться (доступен только числовой индекс, а он меняется при перестановке секций).
Правильные способы добавить условную вёрстку:
- В разметке самой секции — если обёртка/условие относятся к конкретной секции, их задают прямо в её вёрстке (атрибутом
data-mpc-ifна элементе, обычным тегом-обёрткой). Это основной путь: вёрстка остаётся при секции и переживает любой порядок. - Через событие
mpcOnGetSectionHtml— когда нужно вмешаться в готовый HTML конкретной секции по её имени (например, обернуть только секциюfeatures). Событие срабатывает на каждую секцию до сборки страницы и даёт её имя, данные и HTML.
Пример плагина, который оборачивает одну секцию по имени:
// Плагин на событие mpcOnGetSectionHtml.
// Параметры события ($section, $html) доступны в плагине напрямую.
if (($section['section_name'] ?? '') === 'features') {
// вернуть изменённый HTML обратно в рендер
$modx->event->returnedValues['html'] = '<div class="features-wrap">' . $html . '</div>';
}Доступны и более ранние события — mpcOnBeforeParseConfig (вмешаться в набор секций до рендера) и mpcOnBeforeRender (поправить весь resourceData). Полный список с параметрами — в разделе Системные события.
Подсказка
Если условие касается одной секции и его можно выразить версткой — делайте это в секции (data-mpc-if или тег-обёртка). К событиям прибегайте, когда логику нельзя зашить в шаблон: она зависит от данных ресурса, внешних условий и т.п.
Заголовок шаблона
Файл шаблона должен начинаться со специального комментария — из него компонент создаёт MODX-шаблон и ресурс-«тип страницы»:
<!--##{"templatename":"Главная","wrapper":"wrapper","icon":"icon-home","include":"file:pages/index.tpl"}##-->Доступные ключи (содержимое — валидный JSON):
| Ключ | Обяз. | Назначение | По умолчанию |
|---|---|---|---|
templatename | да | имя создаваемого MODX-шаблона. Без него заголовок игнорируется | — |
wrapper | нет | имя секции-обёртки; в «Описание» шаблона пишется ссылка @FILE sections/<wrapper>.tpl | wrapper |
icon | нет | CSS-класс иконки шаблона | icon-gears |
include | нет | путь к файлу, подключаемому в теле шаблона ({include '…'}) | file:pages/index.tpl |
template_var_ids | нет | id TV через запятую для привязки к шаблону (в дополнение к настройке mpc_tmplvar_ids) | — |
Тело шаблона (content) и «Описание» (description) компонент формирует сам — из include и wrapper соответственно; задавать их в заголовке не нужно. Имя ресурса-типа собирается как «Шаблон <templatename>».
Не очищайте поле «Описание» у MPC-шаблонов
В «Описании» сгенерированного шаблона лежит служебная ссылка на файл обёртки (@FILE sections/<wrapper>.tpl) — по ней рендер находит wrapper. Это не свободный текст. Если очистить или изменить поле, рендер не найдёт обёртку и соберёт страницу из «голых» секций встык — без <head>, подключения CSS/JS, шапки и подвала (а если секций нет — выведет поле content ресурса). Причём этот сломанный результат закэшируется в parsed/. Чинится возвратом значения @FILE sections/<wrapper>.tpl или повторной нарезкой.
Осторожно
Формат <!--## … ##--> менять нельзя — он распознаётся регулярным выражением, а содержимое внутри должно быть валидным JSON. Значения include и wrapper подставляются в шаблон, поэтому компонент их санирует (защита от инъекции Fenom и path traversal) — не используйте в них кавычки, .. и спецсимволы.
Секция
Секция — это смысловой блок страницы, который можно двигать и редактировать. Помечается двумя атрибутами:
<section data-mpc-section="hero" data-mpc-name="Главный экран" id="{$id}">
...
</section>data-mpc-section— машинное имя секции (ключ конфигурации). По нему секции матчатся при переиспользовании — наследование от «типа страницы», копирование, мерж при сохранении. Важное следствие: если на одной странице окажутся две секции с одинаковымdata-mpc-section, при сохранении вторая перепишет первую (они схлопнутся в одну). Чтобы вывести одну и ту же секцию дважды с разным контентом, пометьте вторуюdata-mpc-copyи задайте ей свойdata-mpc-lexicon(см. Копирование секций);data-mpc-name— человекочитаемое имя, которое увидит менеджер в админке;id="{$id}"— плейсхолдер идентификатора секции. Ставьте его, если секция может встретиться на странице несколько раз, чтобы у блоков были уникальныеid.
У секции есть несколько вариантов разметки: обычная, статическая (data-mpc-static — одинаковая на всех страницах, см. Статические секции) и копия (data-mpc-copy — та же вёрстка со своим отдельным контентом, см. Копирование секций). Полный разбор всех вариантов с примерами «вход → выход» — в этих разделах.
Дополнительные атрибуты секции
Кроме базовых, на сам элемент секции можно навесить:
data-mpc-lexicon— префикс лексикона секции: под ним группируются переводы её полей. По умолчанию равен имени секции. Задают его явно прежде всего при копировании (data-mpc-copy): у оригинала и копии одинаковое имя секции, поэтому без разных префиксов их переводы слились бы в один набор. Указав копии свойdata-mpc-lexicon, вы разводите переводы оригинала и копии (см. Лексиконы):html<section data-mpc-section="features" data-mpc-lexicon="features" ...> <!-- оригинал --> <section data-mpc-section="features" data-mpc-copy="..." data-mpc-lexicon="features_demo" ...> <!-- копия -->data-mpc-field="bg_img"— фоновое изображение.bg_img— зарезервированное имя поля: компонент берёт URL изbackground[-image]вstyleэлемента, скачивает и обрезает картинку, делает поле редактируемым и подключает ленивую загрузку фона (на рендере —data-lazy="bg:…"). Это работает на любом элементе с таким полем (секция,divи т.д.), а не только на секции:htmlВажно: фон срабатывает именно от имени<section data-mpc-section="howto" data-mpc-field="bg_img" style="background-image: url('img/hero-bg.jpg')">bg_img. Под любым другим именем элемент обработается как обычное поле (возьмётся его внутреннее содержимое), аbackgroundвstyleбудет проигнорирован.data-mpc-if— условный вывод всей секции. В значении — условие на Fenom (пустое значение = «когда поле непустое»). Секция целиком обернётся в{if …}…{/if}:html<section data-mpc-section="promo" data-mpc-if="$show_promo">…</section>data-mpc-unwrap— вывести только содержимое секции, без её тега-обёртки. Так, например, размечают базовую обёртку, когда лишний<div class="wrapper">в результате не нужен.
Поля конфигурации
Поле — это редактируемое значение внутри секции: заголовок, абзац, ссылка, картинка, список карточек. Все поля секции хранятся в одном TV mpc_config (JSON, грид MIGX в админке), а в файле секции на месте поля встаёт плейсхолдер Fenom. Значение из вёрстки становится значением по умолчанию — тем, что менеджер увидит и сможет поменять.
Ниже — по нарастанию сложности: простые поля (одно значение), медиа-поля (картинки/видео), списки (повторяющиеся блоки).
Примеры приведены для режима без лексиконов
Все примеры «выход» ниже показывают вывод при выключенных лексиконах (mpc_use_lexicons = Нет) — на месте поля встаёт простой плейсхолдер {$title}.
Если режим лексиконов включён, переводимые значения выносятся в lexicon-файлы, а в файле секции встаёт отложенный плейсхолдер с ##:
##'{$title}' | lexicon}## здесь не опечатка: он откладывает перевод на финальный проход рендера, когда лексикон ресурса уже загружен. На этом проходе ## превращается в {, а {$title} — в конкретный ключ, и в запечённой странице (parsed/) получается нормальный Fenom-тег {'<префикс>_title' | lexicon} (префикс — имя секции или data-mpc-lexicon). Менять ## вручную нельзя. Логика разметки и имена полей при этом те же — отличается только форма плейсхолдера. Подробнее — в разделе Лексиконы и мультиязычность.
Простые поля
Самый частый случай — одно значение. Помечается атрибутом data-mpc-field="<имя>". Откуда взять значение, компонент определяет по элементу:
- ссылка или любой элемент с атрибутом
href→ берётсяhref; - элемент с дочерними тегами → берётся их HTML;
- иначе → внутренний текст.
В файле секции на месте поля встаёт {$<имя>}:
<!-- вход -->
<h1 data-mpc-field="title">Заголовок страницы</h1>
<a data-mpc-field="link" href="/contacts">Связаться</a><!-- выход -->
<h1>{$title}</h1>
<a href="{$link}">Связаться</a>Значения («Заголовок страницы», /contacts) уезжают в mpc_config как дефолты полей title и link.
Редактируемый текст ссылки
У ссылки из примера выше редактируется только URL, а текст «Связаться» остаётся в вёрстке. Ссылка — особый случай: на <a data-mpc-field> значением берётся href, поэтому для текста нужно отдельное поле. Вложите внутрь ещё одно поле с data-mpc-unwrap: внешний <a> заберёт href, вложенный элемент отдаст свой текст отдельным полем, а unwrap уберёт лишний тег-обёртку:
<!-- вход -->
<a data-mpc-field="link" href="/contacts">
<span data-mpc-field="link_text" data-mpc-unwrap="1">Связаться</span>
</a><!-- выход -->
<a href="{$link}">{$link_text}</a>Теперь в админке два поля — link (URL) и link_text (подпись).
Внимание
Это приём именно для ссылки (href + текст). Так нельзя собирать произвольную вложенность полей: повторяющиеся блоки (карточки, списки) размечаются не вложением data-mpc-field в data-mpc-field, а через прокладку data-mpc-item — см. Списки.
Тип поля
data-mpc-field создаёт поле, а data-mpc-ftype задаёт, как оно редактируется в админке (если тип не указать — будет обычный текст). На вывод тип почти не влияет: плейсхолдер всё равно {$<имя>} — меняется только виджет ввода в гриде MIGX. Доступные типы и примеры разметки:
ftype | Поле в админке | Пример разметки (вход) |
|---|---|---|
text (по умолчанию) | однострочный текст | <span data-mpc-field="name">Имя</span> |
textarea | многострочный текст | <p data-mpc-field="note" data-mpc-ftype="textarea">…</p> |
richtext | визуальный редактор | <div data-mpc-field="body" data-mpc-ftype="richtext">…</div> |
number | число | <span data-mpc-field="qty" data-mpc-ftype="number">12</span> |
date | датапикер | <span data-mpc-field="day" data-mpc-ftype="date">2026-06-09</span> |
email | <span data-mpc-field="mail" data-mpc-ftype="email">a@b.ru</span> | |
url | URL | <span data-mpc-field="site" data-mpc-ftype="url">https://…</span> |
file | файловый браузер источника | <span data-mpc-field="doc" data-mpc-ftype="file">/files/x.pdf</span> |
tag / autotag | теги | <span data-mpc-field="tags" data-mpc-ftype="tag">a,b,c</span> |
option | радио-кнопки (один из) | + data-mpc-values (см. ниже) |
checkbox | флажки (несколько) | + data-mpc-values (см. ниже) |
listbox | выпадающий список (один) | + data-mpc-values (см. ниже) |
listbox-multiple | список с мультивыбором | + data-mpc-values (см. ниже) |
У всех текстовых типов выход одинаковый — отличается только редактор в админке. Например для richtext:
<!-- вход -->
<div data-mpc-field="body" data-mpc-ftype="richtext">Текст статьи</div>
<!-- выход -->
<div>{$body}</div>Подсказка
ftype передаётся в MIGX как есть, белого списка нет — можно указать и любой другой поддерживаемый MIGX тип ввода. Перечисленные типы просто получают готовую русскую подпись.
Формат не проверяется
ftype задаёт только виджет ввода (для date — датапикер, для file — файловый браузер источника). Валидации формата нет: если вписать значение руками, в date/email/url/file сохранится любой текст, и на фронт он выйдет как есть. Нужна проверка — настраивайте validation в определении поля (MIGX).
Поля с выбором: data-mpc-values
Типы listbox, option, checkbox, listbox-multiple показывают набор вариантов. Список вариантов задаёт data-mpc-values. Хранится в поле ключ выбранного варианта, а подпись уходит в лексикон. Три формата записи:
1. Подпись и ключ через == (когда значение в БД должно отличаться от подписи):
<!-- вход -->
<span data-mpc-field="theme" data-mpc-ftype="listbox"
data-mpc-values="Светлая==light||Тёмная==dark">light</span>
<!-- выход -->
{$theme}В админке — выпадайка «Светлая / Тёмная», в конфиг пишется ключ light.
2. Просто список (ключ = нормализованное значение, транслит + нижний регистр):
<!-- вход -->
<span data-mpc-field="size" data-mpc-ftype="listbox"
data-mpc-values="Маленький||Средний||Большой">Средний</span>
<!-- выход -->
{$size}3. Динамические опции из БД — @SELECT (значение, начинающееся с @, идёт в MIGX как есть):
<!-- вход -->
<span data-mpc-field="rel" data-mpc-ftype="listbox"
data-mpc-values="@SELECT `pagetitle`, `id` FROM modx_site_content WHERE published=1">5</span>
<!-- выход -->
{$rel}Мультивыбор (listbox-multiple, checkbox) — выбранные ключи хранятся одной строкой через ||. На рендере их перебирают:
<!-- вход -->
<span data-mpc-field="tags" data-mpc-ftype="listbox-multiple"
data-mpc-values="Новинка==new||Хит==hot||Скидка==sale">new||hot</span>
<!-- выход (пример обхода значений) -->
{foreach $tags | split:'||' as $t}{$t} {/foreach}Внимание
Опции берутся из data-mpc-values при нарезке. Поменяли варианты в вёрстке — перенарежьте секцию, иначе в админке останется старый набор. Это часть структуры конфига, поэтому опции обновляются при любой нарезке — ключ обновления контента (--upd) для этого не нужен (он влияет только на перезапись значений полей).
Откуда берутся типы — mpc_base
Готовые прототипы полей (определения типов с их настройками) хранятся в служебной MIGX-конфигурации mpc_base (её имя задаёт настройка mpc_base_section_name) — это и есть «список типов». data-mpc-ftype ссылается на прототип по имени, и компонент клонирует его под ваше поле. Полный перечень типов с описанием вложенных полей каждого — в Справочнике типов полей.
Тип поля определяется по приоритету:
- Имя поля совпадает с полем в
mpc_base→ берётся его определение как есть (data-mpc-ftypeпри этом игнорируется — имя приоритетнее). Так подключаются предопределённые поля. - Иначе — явный
data-mpc-ftype: если вmpc_baseесть прототип с таким именем, клонируется он; если нет — компонент создаёт определение простого поля «на лету», подставив указанный тип ввода (text/textarea/number/listbox…) в стандартный каркас MIGX. «Простое» — это одиночное значение без вложенных полей (в отличие от медиа и списков). - Иначе — автоопределение по тегу:
<img>/<picture>→ изображение,<video>/<audio>→ медиа, всё остальное →text.
То есть если тип не указать, обычный элемент станет полем text, а медиа-тег — медиа-полем. Ничего не ломается — это разумный дефолт.
Подпись и описание поля в гриде задают data-mpc-fcap (подпись) и data-mpc-fdesc (описание):
<span data-mpc-field="qty" data-mpc-ftype="number"
data-mpc-fcap="Количество" data-mpc-fdesc="Сколько штук на складе">10</span>Полный разбор типов и их настроек — в разделе Создание и обновление элементов.
Другие атрибуты поля
На data-mpc-field навешиваются дополнительные атрибуты-модификаторы. Каждый — со своим эффектом.
Условный вывод — data-mpc-if. Поле попадёт в результат только при выполнении условия. Пустое значение if = «когда поле непустое»:
<!-- вход -->
<a data-mpc-field="link" data-mpc-if="$link" href="/contacts">Связаться</a>
<!-- выход -->
{if $link}<a href="{$link}">Связаться</a>{/if}Условие можно задать и явно: data-mpc-if="$price > 0". Полезно для необязательных кнопок/баннеров — не заполнил менеджер, блок не выводится. Тот же атрибут работает на секциях и вызовах сниппетов.
Условие и лексиконы — момент вычисления
По умолчанию условие вычисляется на раннем проходе (при нарезке/сохранении). Простое data-mpc-if="$title" (проверка непустоты) так и работает. Но если условие зависит от переведённого значения, на раннем проходе лексикон ещё не загружен — нужно отложить условие на фронт атрибутом data-mpc-symbol="##". Плюс сам перевод в условии оборачивается как ('{$поле}' | lexicon) — кавычки вокруг {$поле} нужны, чтобы на раннем проходе подставился ключ, а перевод применился уже на фронте.
Рабочий пример — вывести заголовок, только если перевод начинается с «Вёрстка»:
<h1 data-mpc-field="title" data-mpc-symbol="##"
data-mpc-if="('{$title}' | lexicon) | match: 'Вёрстка*'">Вёрстка → нарезка → редактируемая страница</h1>Без data-mpc-symbol="##" такое условие посчиталось бы до загрузки лексикона и сработало бы неверно.
Служебное значение — data-mpc-remove. Значение собирается в поле, но сам элемент из вёрстки убирается. Классика — блок инлайновых стилей секции:
<!-- вход -->
<div data-mpc-field="inline_styles" data-mpc-remove>padding: 80px 0; --color:#fff;</div>
<!-- выход: значение ушло в поле inline_styles, а сам <div> в результат не попал -->Удобно, когда значение надо собрать и использовать в другом месте, а носитель в разметке не нужен. Собранное inline_styles обычно подставляют в <style> секции, привязав к её id ({$id} — плейсхолдер идентификатора секции):
<section data-mpc-section="hero" id="{$id}">
<style>
#{$id} { {$inline_styles} }
</style>
…
</section>Так менеджер правит отступы/цвета секции прямо в админке (поле inline_styles), а стили применяются точечно к нужному блоку по его id.
Несколько тегов в одно поле — data-mpc-unwrap. В значение попадает внутренний HTML без тега-обёртки:
<!-- вход -->
<div data-mpc-field="advantages" data-mpc-unwrap>
<h3>Почему мы</h3>
<ul><li>Быстро</li><li>Надёжно</li></ul>
</div>
<!-- выход -->
{$advantages}В поле advantages окажутся оба тега (<h3> и <ul>), а временный <div> в вёрстку не войдёт. data-mpc-unwrap также вспомогательно используется в редактируемом тексте ссылки — там он снимает обёртку у вложенного поля. Работает data-mpc-unwrap и с вызовом сниппета/чанком.
Медиа-поля
Если поле — медиа-элемент, компонент собирает все его атрибуты в один объект и обращается к ним через индекс [0]:
<!-- вход -->
<img data-mpc-field="img" src="assets/img/hero.png" width="1200" height="500" alt="Главный экран"><!-- выход -->
<img src="{$img[0].src}" width="{$img[0].width}" height="{$img[0].height}" alt="{$img[0].alt}">Тип медиа определяется по тегу:
<img>→src,alt,title,width,height;<picture>→<img>(основное изображение) + набор<source>(соsrcset,media, размерами). Внутри<picture>ставьте<img>первым — нарезка берёт основное изображение именно из первого<img>(полный разбор — в Справочнике типов полей);<video>/<audio>→src,posterи пр.
Если URL в src внешний (http…), файл скачивается в источник; при заданных width/height подключается обрезка (если задан сниппет-обрезчик) и ленивая загрузка. Медийные модификаторы (data-mpc-nothumb — без обрезки, data-mpc-nolazy — без ленивой загрузки, data-mpc-thumb — свои параметры обрезки) и фон (bg_img) подробно разобраны в разделе Работа с медиа.
Несколько картинок
Если нужно несколько изображений, не заводите специальных «медиа-списков» — проще дать полям обычные имена image_1, image_2 и т.д. (фиксированный набор) или использовать список через data-mpc-item (динамическое число элементов).
Списки
Когда блок повторяется (карточки, слайды, пункты меню) — это список. В отличие от простого поля, число элементов не фиксировано: менеджер добавляет строки в админке. В вёрстке это размечается так: контейнер помечается полем data-mpc-field, каждый повтор — атрибутом data-mpc-item, а поля внутри повтора получают индекс уровня — data-mpc-field-1.
Простой список
<!-- вход -->
<div class="cards" data-mpc-field="cards">
<div class="card" data-mpc-item>
<img data-mpc-field-1="img" src="assets/img/1.png" alt="Первая карточка">
<h3 data-mpc-field-1="title">Первая карточка</h3>
<p data-mpc-field-1="text">Текст первой карточки</p>
</div>
<div class="card" data-mpc-item>
<img data-mpc-field-1="img" src="assets/img/2.png" alt="Вторая карточка">
<h3 data-mpc-field-1="title">Вторая карточка</h3>
<p data-mpc-field-1="text">Текст второй карточки</p>
</div>
</div><!-- выход -->
{foreach $cards as $item1 index=$i1 last=$l1}
<div class="card">
<img src="{$item1.img[0].src}" alt="{$item1.img[0].alt}">
<h3>{$item1.title}</h3>
<p>{$item1.text}</p>
</div>
{/foreach}Что здесь важно:
- вёрстка берётся только из первого
data-mpc-item, а контент — из всех (в админку попадут обе карточки); - поля внутри списка адресуются через переменную элемента —
$item1для первого уровня ({$item1.title},{$item1.text}); - компонент сам оборачивает блок в
{foreach}— цикл писать руками не нужно; index=$i1 last=$l1компонент добавляет сам: это номер текущей итерации и флаг последней (нужны для условий по позиции — см.data-mpc-cond);- в админке контейнер превращается в MIGX-грид — одна строка = одна карточка.
Список в списке (двухуровневый)
Список можно вложить в список: внутри элемента (data-mpc-item) размечаем ещё один контейнер-поле, его повторы помечаем data-mpc-item-1, а поля в них — data-mpc-field-2. Индекс везде растёт на единицу с уровнем вложенности:
<!-- вход -->
<div data-mpc-field="blocks">
<div data-mpc-item>
<h3 data-mpc-field-1="title">Блок</h3>
<ul data-mpc-field-1="items">
<li data-mpc-item-1>
<span data-mpc-field-2="label">Пункт</span>
</li>
</ul>
</div>
</div><!-- выход -->
{foreach $blocks as $item1 index=$i1 last=$l1}
<h3>{$item1.title}</h3>
<ul>
{foreach $item1.items as $item2 index=$i2 last=$l2}
<li><span>{$item2.label}</span></li>
{/foreach}
</ul>
{/foreach}Поле верхнего уровня — {$item1.title}, вложенный список — {$item1.items}, его элементы — $item2 ({$item2.label}). В админке это вложенные MIGX-гриды: грид блоков, внутри каждого блока — грид пунктов.
Типы и атрибуты внутри списка
Внутри списка работает всё то же, что и у простых полей — типы (data-mpc-ftype), значения (data-mpc-values), медиа, модификаторы (if/remove/unwrap). Разница одна: к полю добавляется префикс элемента $itemN. Например медиа-поле в списке остаётся объектом с индексом [0]:
<!-- вход -->
<img data-mpc-field-1="photo" data-mpc-ftype="image" src="a.jpg" alt="Фото">
<!-- выход -->
<img src="{$item1.photo[0].src}" alt="{$item1.photo[0].alt}">Внимание
alt берётся из самой картинки ({$item1.photo[0].alt}), а не из соседнего поля — не подставляйте в alt чужой плейсхолдер вроде {$item1.title}.
Несколько атрибутов относятся именно к спискам.
data-mpc-max— ставится на контейнер списка и ограничивает число строк, которые можно добавить в гриде (MIGXmaxRecords):htmlВ админке нельзя будет добавить больше 5 слайдов.<div data-mpc-field="slides" data-mpc-max="5"> <div data-mpc-item>…</div> </div>data-mpc-limиdata-mpc-off— ставятся на контейнер списка и задают параметры вывода{foreach}:lim— сколько элементов показать (limit),off— сколько пропустить с начала (offset). Компонент подставит подходящий вариант цикла:htmlЗдесь на странице выведутся 3 элемента, начиная со второго (первый пропущен). В отличие от<div data-mpc-field="news" data-mpc-lim="3" data-mpc-off="1"> <div data-mpc-item>…</div> </div>data-mpc-max(лимит в админке),lim/offограничивают вывод на фронте.data-mpc-cond— выводить элемент списка по условию итерации. В значении — условие с переменными$i(номер итерации) и$l(номер последней); знаки сравнения кодируютсяurlencode()(ограничение парсера). Ставится наdata-mpc-item:html<div data-mpc-item data-mpc-cond="$i %3C 3">…</div> <!-- %3C = «<» : первые две итерации -->
Глубина вложенности
Технически вложенность не ограничена (список в списке в списке…): индекс растёт — data-mpc-item-2, data-mpc-field-3, переменные $item2, $item3. Но на практике дальше второго уровня мы не проверяли и не рекомендуем: и поддерживать такую разметку тяжело, и есть риск необкатанных краевых случаев. Если данные просятся глубже двух уровней — обычно стоит пересмотреть структуру секции.
Сниппеты и чанки
Это самый «продвинутый» уровень разметки — динамика и переиспользуемые куски вёрстки.
Вызов сниппета — data-mpc-snippet="ИмяСниппета|пресет". Элемент заменяется вызовом сниппета, параметры берутся из пресета по ключу (см. Работа со сниппетами):
<ul data-mpc-snippet="pdoMenu|main_menu">...</ul>После нарезки на месте элемента будет вызов сниппета с параметрами из пресета main_menu.
К вызову сниппета применимы те же модификаторы, что и к полям:
data-mpc-if— вызывать сниппет только при условии (вызов обернётся в{if …}…{/if}):html<ul data-mpc-snippet="pdoMenu|main_menu" data-mpc-if="$show_menu">...</ul>data-mpc-unwrap— подставить результат сниппета без тега-обёртки (в вывод уйдёт только сам вызов, без оборачивающего элемента).
Вырезание в чанк — data-mpc-chunk="путь/к/chunk.tpl". Содержимое элемента вырезается в отдельный чанк. Работает и само по себе, и внутри вызова сниппета (так размечают шаблон строки или обёртки списка):
<li data-mpc-chunk="chunks/menuitem.tpl">
<a href="{$uri}">{$menutitle}</a>
</li>Как подключить вырезанный чанк обратно, задают парные атрибуты:
data-mpc-include→{include "file:chunks/promo.tpl"}:html<div data-mpc-chunk="chunks/promo.tpl" data-mpc-include> <h2>Промо информация</h2> </div>data-mpc-parse→ вызовparseChunkс параметрами из значения атрибута:htmlпревратится в<div data-mpc-chunk="chunks/promo_2.tpl" data-mpc-parse="[$id => 1]"> <h2>Промо информация из ресурса ID={$id}</h2> </div>{$_modx->parseChunk("@FILE chunks/promo.tpl", [$id => 1])}.
Повторное использование чанка
Один и тот же чанк нередко подключают в нескольких местах (шапка меню, карточка товара). Чтобы не нарезать его файл повторно, помечайте такие места data-mpc-copy — файл чанка тогда не пишется, только ставится подключение (include/parse). Файл нарезается лишь из «оригинала» — элемента без data-mpc-copy.
<!-- оригинал: и нарезается в файл, и подключается -->
<div data-mpc-chunk="promo.tpl" data-mpc-include>
<h2>Промо</h2>
</div>
<!-- копия: только подключает promo.tpl, файл не трогает -->
<div data-mpc-chunk="promo.tpl" data-mpc-copy data-mpc-include></div>В значении data-mpc-copy можно (необязательно) указать имя файла оригинала — как подсказку, где он лежит; на подключение это не влияет. Если значение не указано — элемент всё равно считается копией, файл не нарезается.
Внимание
data-mpc-copy без data-mpc-include или data-mpc-parse — бессмысленен: файл не нарезается и подключение не ставится, элемент просто остаётся в вёрстке. Это, скорее всего, ошибка разметки.
Есть и неявный механизм: если на нескольких элементах указан один и тот же data-mpc-chunk без data-mpc-copy, файл нарезается из первого вхождения, остальные одноимённые пропускаются (в пределах одного запуска нарезки). Он работает, но зависит от порядка/режима нарезки — поэтому для повторного использования лучше явно помечать копии data-mpc-copy.
Момент выполнения сниппета/парса регулирует data-mpc-symbol ({ — выполнить при нарезке/сохранении, ## — отложить на фронт; по умолчанию ##). Некэшируемые сниппеты (AjaxForm, mFilter2) и плейсхолдеры, доступные только на фронте, оставляйте с ##. Подробно — в разделе Работа с чанками.
TV-поля
data-mpc-tv — записать значение в TV (Template Variable) ресурса. Если такого TV ещё нет, компонент создаст и синхронизирует его по разметке. На рендере значение берётся из TV ресурса:
<!-- вход -->
<span data-mpc-tv="subtitle">Подзаголовок</span>
<!-- выход -->
<span>{$resource.tvs.subtitle}</span>В отличие от полей конфигурации (которые лежат в общем mpc_config), TV — это отдельная нативная переменная ресурса. Используйте data-mpc-tv, когда значение нужно как самостоятельный TV (например для выборок pdoTools по нему).
Комбинации с TV
data-mpc-tv поддерживает те же модификаторы типа и значений, что и поля конфигурации, — они задают, каким будет создаваемый TV:
data-mpc-ftype— тип создаваемого TV. Поддерживаются нативные типы MODX TV:text,textarea,richtext,number,date,email,url,listbox,listbox-multiple,option,checkbox,file,tag/autotag,image. Если не указать — тип определяется по тегу (<img>/<picture>→image, иначеtext).html<span data-mpc-tv="published_at" data-mpc-ftype="date">2026-06-09</span>Обратите внимание: у TV нет составных медиа-типов (
picture/video/audio) — любое медиа становится одной картинкой (image). Это отличие от полей конфигурации, где медиа собирает все атрибуты в объект.data-mpc-ftype+data-mpc-values— TV-список с опциями (опции уходят вelementsTV):html<span data-mpc-tv="badge" data-mpc-ftype="listbox" data-mpc-values="Новинка==new||Хит==hot">new</span>data-mpc-if— условный вывод TV (оборачивается в{if …}), как и у обычных полей:html<span data-mpc-tv="subtitle" data-mpc-if="$resource.tvs.subtitle">…</span>data-mpc-fcap/data-mpc-fdesc— подпись (caption) и описание (description) создаваемого TV:html<span data-mpc-tv="published_at" data-mpc-ftype="date" data-mpc-fcap="Дата публикации" data-mpc-fdesc="Когда показать на сайте">2026-06-09</span>Если
data-mpc-fcapне указан, подписью становится имя TV. У уже настроенного TV подпись/описание перезаписываются, только когда атрибуты заданы в разметке — пустые значения существующие настройки не затирают.
Вывод TV другого ресурса
Маркеры внутри блока с data-mpc-res относятся к чужому ресурсу (по его id) — такой TV компонент не создаёт и не синхронизирует, только выводит. Это используется, когда на странице нужно показать значение TV другого ресурса.
Внимание
TV синхронизируется из шаблона (тип/опции), только если он в категории пакета mpc. Чужой TV (созданный вне mpc) компонент не трогает по структуре — лишь привязывает к шаблону и пишет значение. Так правки чужих TV из разметки не ломают их настройки.
Как компонент создаёт и обновляет TV из разметки — в разделе Создание и обновление элементов.
Поля ресурса
data-mpc-rfield — записать значение в нативное поле ресурса (longtitle, description, introtext, content, menutitle). На рендере — {$resource.<поле>}:
<!-- вход -->
<h2 data-mpc-rfield="longtitle">Расширенный заголовок</h2>
<!-- выход -->
<h2>{$resource.longtitle}</h2>Какие поля разрешено писать, задаёт системная настройка mpc_editable_resource_fields (по умолчанию longtitle,description,introtext,content,menutitle); структурные и защищённые поля (id, alias, uri, template, parent, pagetitle и др.) от записи защищены (mpc_protected_resource_fields). Удобно, когда расширенный заголовок/описание/контент страницы редактируются прямо в вёрстке, но должны лечь в стандартные поля ресурса, а не в конфиг.
Подсказка
pagetitle по умолчанию защищён — data-mpc-rfield="pagetitle" запись не выполнит. Если заголовок страницы нужно писать из вёрстки, перенесите pagetitle из mpc_protected_resource_fields в mpc_editable_resource_fields.
Дополнительные атрибуты
data-mpc-if— условный вывод поля (как у обычных полей): оборачивает в{if …}.html<div data-mpc-rfield="introtext" data-mpc-if="$resource.introtext">…</div>
Как поле ресурса хранится при включённых лексиконах
Если режим лексиконов включён, поле ресурса переводимое (его content-type в mpc_translated_content, обычно text) и не исключено — значение не пишется в колонку как есть. Вместо этого:
- значение уходит в словарь ресурса под ключом
mpc_resource_<поле>(для текущего языка); - в саму колонку ресурса пишется этот ключ (
mpc_resource_<поле>); - на рендере значение резолвится через
| lexicon— поэтому показывается перевод для активного языка.
При первом сохранении нового значения ключ синхронизируется во все языки (в остальных языках появляется плейсхолдер) и попадает в список непереведённых (.pending). Ввод перевода на другом языке снимает ключ из этого списка. Это та же логика, что работает на нарезке (общий механизм синхронизации) — поэтому правки из редактора и перенарезка дают согласованный результат.
Без лексиконов (или для непереводимого/исключённого поля) значение пишется в колонку как обычно.
Редактирование полей другого ресурса (cross-resource)
Иногда на странице выводятся данные другого ресурса — например в листинге (карточка статьи, превью), который собирает сниппет вроде pdoResources. Чтобы такие поля можно было править прямо на странице, блок оборачивают атрибутом data-mpc-res="<id>" (id целевого ресурса):
<!-- чанк строки листинга: $id, $longtitle, $uri — поля ресурса из выборки -->
{set $lexicons = $id | lexiconsarr: $template}
<li data-mpc-res="{$id}">
<a href="{$uri}">
<span data-mpc-rfield="longtitle">{$lexicons[$longtitle] ?: $longtitle}</span>
</a>
</li>Здесь lexiconsarr возвращает массив лексиконов конкретного ресурса (по его id и шаблону). Так как при лексиконах в колонке поля лежит ключ, $longtitle — это и есть ключ; перевод берём из массива $lexicons[$longtitle], а ?: $longtitle — запасной вариант, если перевода нет.
Подсказка
Для перевода полей чужого ресурса в листинге используйте lexiconsarr (массив лексиконов нужного ресурса) + обращение по ключу — это надёжнее, чем | lexicon: модификатор lexicon резолвит из общего контекста, и в цикле по разным ресурсам их переводы перетёрли бы друг друга.
Что здесь важно:
- render-выражение внутри пишет автор — нарезка контент в
data-mpc-resне трогает (только сохраняет маркеры). Автор сам выводит поле чужого ресурса (как в примере:lexiconsarr+ обращение по ключу); - редактор (mpcVisualEditor) по ближайшему
data-mpc-resопределяет, в какой ресурс писать правку: значение поля и/или лексикон-ключ уходят в тот ресурс (id изdata-mpc-res), а не в текущую страницу; - то есть один и тот же набор полей листинга редактируется «по месту», но сохраняется каждый в свой ресурс.
Нужен mpcVisualEditor
Редактирование полей прямо на странице (в том числе cross-resource через data-mpc-res) выполняет пакет mpcVisualEditor — без него правки с фронта недоступны. Сама нарезка и вывод значений (data-mpc-rfield) работают и без редактора; data-mpc-res без mpcVE — просто пометка, на рендер не влияет.
Контакты
Контакты (телефоны, почты, адреса, соцсети) хранятся отдельно от секций — в едином ресурсе-хранилище — и выводятся сниппетом на любой странице. Блок контакта помечается data-mpc-contact, поля внутри — data-mpc-cfield:
<div data-mpc-contact="phone|header" data-mpc-key="mainphone">
<a data-mpc-cfield="value" href="tel:+79000000000">+7 900 000-00-00</a>
<span data-mpc-cfield="caption">Телефон</span>
</div>data-mpc-contact — тип и плейсмент
Значение — <тип>|<плейсмент> (плейсмент необязателен, по умолчанию default):
- тип —
phone,email,address, имя соцсети и т.п. Влияет на обработку: дляphoneзначение нормализуется (только цифры), а форматированный вид (fvalue) генерируется по настройкамmpc_phone_regexp/mpc_phone_format. - плейсмент — где контакт выводится (
header,footer…). Один и тот же контакт можно показать в нескольких местах с разным оформлением. Поэтому подпись и атрибуты привязаны к плейсменту, а само значение — общее (см. перевод ниже).
data-mpc-cfield — роли полей
cfield | Что это |
|---|---|
value | само значение — телефон/e-mail/URL. У ссылки берётся href, иначе содержимое |
caption | подпись (зависит от плейсмента: «Телефон», «Звоните» и т.п.) |
attributes | доп. данные/иконка (берётся src или содержимое) |
fvalue | форматированное значение. Указывать не нужно — для phone генерируется само из value; задают вручную лишь в особых случаях |
data-mpc-key — ключ контакта
data-mpc-key задаёт устойчивый идентификатор контакта (mainphone). Один и тот же key + тип — это один контакт, переиспользуемый в разных плейсментах.
Задавайте ключ явно
Если data-mpc-key не указан, ключ вычисляется как md5 от значения. Это хрупко: поменяете телефон — ключ изменится, и контакт «потеряется» (перевод/привязки не подхватятся). Поэтому для контактов, которые выводятся и переводятся, всегда задавайте data-mpc-key.
Перевод контактов
Какие под-поля переводятся, задаёт настройка mpc_contact_lexicon_fields (по умолчанию caption); на конкретном блоке её переопределяет data-mpc-translate (через запятую, например data-mpc-translate="caption,value"). Логика ключей:
value/fvalue— данные самого контакта → один перевод на все плейсменты (ключ без плейсмента);caption/attributes— оформление → перевод на каждый плейсмент свой.
Полный разбор (типы контактов, форматирование телефонов, вывод сниппетом) — в разделе Работа с контактами.
Системные настройки
data-mpc-info — записать значение в системную или контекстную настройку MODX. Значение из вёрстки уходит в настройку, а на рендере на месте элемента подставляется её текущее значение {$_modx->config['<ключ>']}:
<!-- вход -->
<span data-mpc-info="site_name">Мой сайт</span>
<!-- выход -->
<span>{$_modx->config['site_name']}</span>Удобно, когда «глобальные» значения (название сайта, телефон в шапке, реквизиты) правятся прямо в вёрстке, но должны жить в настройках MODX, а не в конфиге секции.
Откуда берётся значение
Как и у полей, источник определяется по элементу:
- ссылка (
href) → берётсяhref; - картинка (
src) → берётсяsrc; - иначе → текст элемента (теги Fenom
{и MODX[[экранируются, чтобы значение из контента не исполнялось при выводе настройки).
Системная или контекстная — data-mpc-ctx
- без
data-mpc-ctx→ ищется системная настройка (modSystemSetting), а если её нет — настройка ClientConfig; - с
data-mpc-ctx→ ищется контекстная настройка (modContextSetting) указанного контекста, затем системная, затем ClientConfig (для контекста). Значение атрибута — ключ контекста (web); пустой атрибут = текущий контекст рендера:html<span data-mpc-info="site_name" data-mpc-ctx="web">Мой сайт</span>
Поддерживаются и нативные настройки MODX, и ClientConfig — в обоих случаях значение пишется в уже существующую настройку (ClientConfig подключается как запасной вариант, если нативной с таким ключом нет).
Настройка должна существовать до нарезки
data-mpc-info обновляет существующую настройку, но не создаёт новую: если настройки с таким ключом нет (ни нативной, ни ClientConfig), значение просто не запишется. Создайте нужные настройки заранее — например консольной командой settings apply (см. Консольные команды (CLI)), а уже потом размечайте и нарезайте шаблон.
Условный вывод
Поддерживается data-mpc-if — вывести значение настройки только по условию (оборачивается в {if …}).
Защита критичных настроек
Запись через контент заблокирована для настроек безопасности — ключи, содержащие allow_manager, manager_, session, password, forgot, filemanager, proxy, http_host, mail_, cache_, register_enabled и т.п., проигнорируются (защита от подмены критичных параметров через вёрстку). Для них data-mpc-info работать не будет — это намеренно.
После того как шаблон размечен, остаётся запустить нарезку — и контент окажется в админке. Как её запустить, какие есть варианты и что при этом происходит — в разделе Нарезка.
