Точки рендеру вітрини (render points)
Точка рендеру — це іменоване місце в темі вітрини, куди будь-який модуль може
додати свій HTML. Тема позначає де ({{ render_point('product_buy', …) }}),
модуль каже що (слухач події, який віддає розмітку), а ядро
(App\Libraries\RenderPoint) їх склеює. Сторони нічого не знають одна про
одну, тому той самий модуль з'являється в кожній темі, яка стріляє точкою, і
зміна теми його мовчки не губить.
Навіщо вони
В ECMS9 модуль ніколи не пише у файли теми. До точок рендеру фронтовий віджет
модуля (кнопка «Купити в 1 клік», віджет доставки) з'являвся лише там, де
активна тема явно кликала module('x'). Тема без такого виклику губила модуль
без жодної помилки. Точки рендеру розвертають залежність: тема публікує
слоти розширення, модулі їх заповнюють.
Як це працює
- Шаблон теми кличе Twig-функцію
render_point('<назва>', { …контекст… }). Функція зареєстрована в обох рендерерах вітрини (TwigStorefrontRendererдля класичних тем іStorefront\ThemeRenderer), тож працює в будь-якій темі. RenderPoint::render()кидає подію CI4storefront_render_point:<назва>з об'єктом-колектором.- Кожен слухач отримує колектор, читає контекст теми з
$collector->ctxі кличе$collector->add($html). Порожній вивід ігнорується. - Фрагменти склеюються в порядку слухачів (пріоритет події CI4 — третій
аргумент
Events::on()) і повертаються як безпечний HTML.
Кожен слухач працює всередині try/catch: один зламаний модуль пише помилку
в лог і нічого не додає — сторінку він не кладе. Точка, яку не обробляє жоден
модуль, віддає порожній рядок, тому теми можуть ставити точки вільно.
Назва точки — [a-z0-9_]+; інше не рендериться.
Точки в ядрі та вбудованих темах
| Точка | Звідки стріляє | Контекст | Хто відповідає |
|---|---|---|---|
product_buy | сторінка товару, біля «Купити» | product | buy_one_click |
product_actions | сторінка товару, ряд іконок | product | вбудованого модуля немає — вільна для store/сторонніх модулів |
product_share | сторінка товару | product | share |
product_related | сторінка товару, блок схожих | product | related_products |
product_seo | сторінка товару (тема fauna) | product | seo_snippets |
cart_delivery_widget | кошик, для кожного способу доставки окремо | delivery | nova_poshta, ukrposhta, rozetka |
checkout_extra | шаблони оформлення | — | system_bonus (поле списання балів) |
body_end | кожна сторінка вітрини перед </body> (стріляє BaseFront, не тема) | — | ai_chat |
admin_order_delivery_widget | адмінка, створення/редагування замовлення (стріляє Orders модуля Shop) | mode, order | nova_poshta |
admin_order_customer_info | адмінка, редагування замовлення, права колонка | order | buy_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/).