Перейти до основного вмісту

Точки рендеру вітрини (render points)

Точка рендеру — це іменоване місце в темі вітрини, куди будь-який модуль може додати свій HTML. Тема позначає де ({{ render_point('product_buy', …) }}), модуль каже що (слухач події, який віддає розмітку), а ядро (App\Libraries\RenderPoint) їх склеює. Сторони нічого не знають одна про одну, тому той самий модуль з'являється в кожній темі, яка стріляє точкою, і зміна теми його мовчки не губить.

Навіщо вони

В ECMS9 модуль ніколи не пише у файли теми. До точок рендеру фронтовий віджет модуля (кнопка «Купити в 1 клік», віджет доставки) з'являвся лише там, де активна тема явно кликала module('x'). Тема без такого виклику губила модуль без жодної помилки. Точки рендеру розвертають залежність: тема публікує слоти розширення, модулі їх заповнюють.

Як це працює

  1. Шаблон теми кличе Twig-функцію render_point('<назва>', { …контекст… }). Функція зареєстрована в обох рендерерах вітрини (TwigStorefrontRenderer для класичних тем і Storefront\ThemeRenderer), тож працює в будь-якій темі.
  2. RenderPoint::render() кидає подію CI4 storefront_render_point:<назва> з об'єктом-колектором.
  3. Кожен слухач отримує колектор, читає контекст теми з $collector->ctx і кличе $collector->add($html). Порожній вивід ігнорується.
  4. Фрагменти склеюються в порядку слухачів (пріоритет події CI4 — третій аргумент Events::on()) і повертаються як безпечний HTML.

Кожен слухач працює всередині try/catch: один зламаний модуль пише помилку в лог і нічого не додає — сторінку він не кладе. Точка, яку не обробляє жоден модуль, віддає порожній рядок, тому теми можуть ставити точки вільно.

Назва точки — [a-z0-9_]+; інше не рендериться.

Точки в ядрі та вбудованих темах

ТочкаЗвідки стріляєКонтекстХто відповідає
product_buyсторінка товару, біля «Купити»productbuy_one_click
product_actionsсторінка товару, ряд іконокproductвбудованого модуля немає — вільна для store/сторонніх модулів
product_shareсторінка товаруproductshare
product_relatedсторінка товару, блок схожихproductrelated_products
product_seoсторінка товару (тема fauna)productseo_snippets
cart_delivery_widgetкошик, для кожного способу доставки окремоdeliverynova_poshta, ukrposhta, rozetka
checkout_extraшаблони оформленняsystem_bonus (поле списання балів)
body_endкожна сторінка вітрини перед </body> (стріляє BaseFront, не тема)ai_chat
admin_order_delivery_widgetадмінка, створення/редагування замовлення (стріляє Orders модуля Shop)mode, ordernova_poshta
admin_order_customer_infoадмінка, редагування замовлення, права колонкаorderbuy_one_click

Дві останні використовують той самий механізм у формі замовлення адмінки, тож модуль доставки може додати туди свій віджет, не правлячи в'юхи Shop.

Слухач cart_delivery_widget мусить перевіряти, чи переданий спосіб доставки ($collector->ctx['delivery']) — його власний, і інакше виходити: точку ділять кілька перевізників.

Реєстрація слухача в модулі

Слухачі живуть у власному Config/Events.php модуля, який підхоплює auto-discovery CI4 (див. Структура модуля). Ніколи не реєструйте їх в app/Config/Events.php.

<?php
// app/Modules/MyModule/Config/Events.php
namespace App\Modules\MyModule\Config;

use CodeIgniter\Events\Events;

Events::on('storefront_render_point:product_buy', static function ($collector): void {
try {
helper('modules');
if (! module_enabled('my_module')) {
return;
}
$product = $collector->ctx['product'] ?? null;
if (! is_object($product) || ! method_exists($product, 'getId')) {
return;
}
$collector->add((new \App\Modules\MyModule\Libraries\Widget())->render($product->getId()));
} catch (\Throwable $e) {
log_message('error', 'my_module render point: ' . $e->getMessage());
}
});

Правила для кожного слухача: гард module_enabled(), try/catch, перевірка контексту перед використанням (різні теми передають трохи різні об'єкти), мовчазний вихід, коли точка не стосується модуля.

Третім аргументом Events::on() задається порядок, коли одну точку обробляють кілька модулів (менший пріоритет — раніше).

Як тема виставляє точку

{# themes/<theme>/shop/includes/product/product_purchase.twig #}
{{ render_point('product_buy', {'product': model}) }}

{# кошик: усередині циклу по способах доставки #}
{{ render_point('cart_delivery_widget', {'delivery': delivery}) }}

Тема може додавати власні точки з новими назвами — модулю потрібна лише назва і ключі контексту.

Скрипти і стилі віджета

tpl_register_asset() у поточній збірці — заглушка, тож точка рендеру дає лише розмітку. JS/CSS віджета має підключати тема (тег <script> на /templates/<theme>/<module>/js/…), а ассети мусять лежати у двох копіях: app4/themes/<theme> і публічна templates/<theme>. Не покладайтесь на порядок <script defer> — jQuery може стояти нижче в документі; чекайте window.jQuery або події load.

Точка рендеру повертає HTML-віджет. Дата-аксесори (метод, що повертає число чи масив, який тема форматує сама), AJAX-віджети і сесійні механізми лишаються прямими викликами — не тягніть їх у точку.

Перевірка покриття теми

php spark shop:theme-coverage # активна тема
php spark shop:theme-coverage goodlook # конкретна тема

Команда сканує .twig/.tpl теми і показує, які встановлені й увімкнені фронтові модулі тема показує (виклик module('x') або html-level модуль, що вставляється в head/body), а які ні, плюс перелік точок рендеру, які тема стріляє. Нічого не змінює. Проганяйте перед перемиканням сайту на іншу тему, щоб побачити, що зникне.

Після правок .twig чистьте Twig-кеш вітрини (writable/cache/twig_storefront/).