Brilliancy of quality
Документация

Практический синтаксис Mirage Templates для управления версткой

Sapphire I.C.D.S.

Граница шаблонного слоя

Mirage предназначен для HTML и текстового представления. Через него можно переставлять секции, менять карточки и заголовки, добавлять декоративные контейнеры, управлять классами, выводить подготовленные списки и локализованные подписи. Шаблон не должен выполнять SQL, определять права, менять данные или заменять модульную проверку маршрута. Если нужного поля нет во входном контексте, одной правкой верстки его достоверно создать нельзя.

Публичные шаблоны находятся в skins/<Layout>/<Skin>/<Module>/, административные — в adminskins/..., модераторские — в moderatorskins/.... Перед изменением определите активные Layout и Skin. Одинаковое имя файла может встречаться в нескольких деревьях, а правка неактивной копии не изменит страницу.

Экранированный вывод

Основная и безопасная форма вывода — двойные фигурные скобки:

<h1>{{ Context.Table("page").Data("title") }}</h1>
<p>{{ Context.Table("page").Data("summary") }}</p>

Текст экранируется для HTML, поэтому символы из данных не превращаются в теги или атрибуты. Используйте эту форму для заголовков, имён, URL в атрибутах, подписей, поисковых строк и любых значений, которые мог ввести пользователь.

Сырой вывод обозначается восклицательным знаком:

{{! Former.BuildHTML(Context.Table("form").Data("submit_ir")) }}

Он допустим только для источника, который уже отвечает за безопасный HTML: например, элемента, собранного Former, зоны Echelon или заранее сериализованного JSON-LD. Не заменяйте обычный вывод на raw ради того, чтобы «заработал тег» из пользовательского поля. Это превращает данные в активную разметку.

Именованные таблицы Context

Для новых шаблонов используйте Context.Table. Одна страница может получать таблицы meta, page, assets и модульные таблицы с префиксом __. Основные вызовы:

  • Context.HasTable("items") — проверить наличие таблицы;
  • Context.Table("items").Data("title") — прочитать поле текущей строки;
  • RowCount() — получить число строк;
  • Rewind() — вернуть курсор в начало;
  • NextRow() — перейти к следующей строке.

Совместимый синтаксис Table.Data(...) остаётся в старых однотабличных шаблонах, но его не следует смешивать с именованным контекстом без причины.

Условия, значения и комментарии

{% if Context.Table("page").Data("status") == "published" %}
  <span class="badge badge--live">{{ Boardwords("status_published") | default("Опубликовано") }}</span>
{% elif Context.Table("page").Data("status") == "draft" %}
  <span class="badge badge--draft">{{ Boardwords("status_draft") | default("Черновик") }}</span>
{% else %}
  <span class="badge">{{ Boardwords("status_unknown") | default("Неизвестно") }}</span>
{% endif %}

Флаги часто приходят строками "1" и "0", поэтому сравнивайте их явно. Для временной переменной применяется {% set name = value %}, а комментарий записывается как {# пояснение #} и не попадает в HTML.

Для запасного значения подходят две формы:

{{ Context.Table("meta").Data("title") ?? "Без заголовка" }}
{{ Boardwords("catalog_title") | default("Каталог") }}

Тернарное выражение удобно для короткого значения класса, но большие блоки лучше оформлять обычным if:

{{ Context.Table("page").Data("compact") == "1" ? "card--compact" : "card--wide" }}

Безопасный цикл по строкам

Именованная таблица не превращается автоматически в массив объектов. Используйте полный курсорный шаблон:

{% if Context.Table("products").RowCount() > 0 %}
  {% set __ = Context.Table("products").Rewind() %}
  <div class="product-grid">
    {% for _ in range(0, Context.Table("products").RowCount(), 1) %}
      {% if Context.Table("products").NextRow() %}
        <article class="product-card{% if loop.even %} product-card--even{% endif %}">
          <h3>{{ Context.Table("products").Data("title") }}</h3>
          <p>{{ Context.Table("products").Data("summary") }}</p>
        </article>
      {% endif %}
    {% endfor %}
  </div>
{% else %}
  <p>{{ Boardwords("products_empty") | default("Пока ничего нет") }}</p>
{% endif %}

Порядок обязателен: проверить строки, выполнить Rewind(), пройти диапазон и вызвать NextRow() до чтения Data(). В цикле доступны loop.index, loop.first, loop.last, loop.odd и loop.even.

Локализация, формы и зоны

Статические пользовательские подписи выводите через Boardwords с понятным fallback. Это сохраняет переводимость при копировании дизайна между skin trees. Если поле формы приходит как *_ir, не собирайте input вручную:

<label>{{ Boardwords("profile_name") | default("Имя профиля") }}</label>
{{! Former.BuildHTML(Context.Table("profile").Data("name_ir"), "class", "profile-form__input") }}

Mirage управляет контейнером, сеткой и классом, а Former — HTML-контролом. Контент Echelon Zone также выводится raw только потому, что зона является доверенным производителем фрагмента; произвольное поле таблицы таким статусом не обладает.

Проверка изменения

  1. Сделайте узкую правку в активном шаблоне, не копируя весь shell.
  2. Проверьте страницу с данными, пустым списком и отсутствующими необязательными полями.
  3. Проверьте специальные символы в тексте и атрибутах.
  4. Проверьте все доступные языки и fallback Boardwords.
  5. Проверьте desktop и mobile, а для административной формы — ошибки валидации.
  6. Убедитесь, что raw используется только для доверенных производителей HTML.

Если шаблону потребовались новые права, SQL или бизнес-правило, остановитесь: задача вышла за границу Mirage. Правильное расширение сначала готовит данные в модуле, а шаблон остаётся предсказуемым слоем представления.

Композиция страницы и динамические фрагменты

Общий shell, меню и содержимое прикладного модуля редактируйте раздельно. Не переносите весь каркас в шаблон одной карточки: это создаёт дублирование и ломает смену skin. Для фрагмента, который обновляется фильтром, pager или вкладкой, используйте принятый AJAX-контур с Mirage. Канонический публичный текст, важный для первого отображения и SEO, оставляйте в обычном рендере страницы.

Если шаблон использует дополнительный namespace callback-пакета, например форматирование даты, убедитесь, что пакет присутствует в целевом runtime. Такой callback является зависимостью конкретного шаблона. Предусмотрите понятный fallback там, где это допускает дизайн, и не пытайтесь имитировать отсутствующий callback сложными строковыми выражениями.

Практика безопасного review

Сравнивайте итоговый HTML до и после изменения, а не только исходник `.mirage`. Проверьте баланс тегов, уникальность id, доступность label, валидность ссылок и отсутствие вложенных интерактивных элементов. Для списков протестируйте одну и несколько строк: ошибки курсора часто не видны на единственной записи. При переносе шаблона между skin trees повторно проверьте имена таблиц и полей, поскольку визуально похожие модули не обязаны получать одинаковый контекст.