#контроллеры

Советы с тегом "контроллеры"

1 совет

HTML компонента из контроллера вместе с CSS и JS

HTML компонента из контроллера вместе с CSS и JS

Проблема/контекст

Экшен отдаёт HTML компонента, а его стили и скрипты остаются на сервере: шаблон вызвал Extension::load() или AddHeadScript(), но в AJAX-ответ это не попало. Ручная сборка списков ассетов на фронте быстро расходится с реальными зависимостями шаблона. В ядре для этого есть Bitrix\Main\Engine\Response\Component, но контракт ответа в документации не описан.

Решение с кодом

Response\Component — final-класс, наследник HtmlContent. Его конструктор: __construct($componentName, $componentTemplate = '', array $componentParams = [], array $additionalResponseParams = [], $dataKeys = []).

        <?php
declare(strict_types=1);

namespace Vendor\Catalog\Infrastructure\Controller;

use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\Response;

final class Slider extends Controller
{
    public function viewAction(int $sectionId): Response\Component
    {
        return $this->renderComponentAjax(
            'vendor:catalog.slider',
            '.default',
            ['SECTION_ID' => $sectionId],
            ['sectionId' => $sectionId], // -> data.additionalParams
            ['ITEMS', 'NAV_STRING'],     // срез arResult -> data.componentResult
        );
    }
}

    

Порядок работы задан в конструкторе HtmlContent: сначала ContentArea\Component::getHtml() выполняет $APPLICATION->IncludeComponent(...) внутри ob_start(), и только потом collectAssetsPathList() снимает списки с Asset::getInstance(). Поэтому в ответ попадает всё, что шаблон зарегистрировал во время рендера.

Итоговый JSON — обычная обёртка AjaxJson:

        {
  "status": "success",
  "data": {
    "html": "...",
    "assets": {"css": [], "js": [], "string": []},
    "additionalParams": {"sectionId": 12},
    "componentResult": {"ITEMS": [], "NAV_STRING": "..."}
  },
  "errors": []
}

    

Ключ ассетов называется string, а не strings. componentResult есть в ответе всегда (ContentArea\Component реализует DataSectionInterface), при пустом $dataKeys в нём []. $dataKeys же управляет шестым аргументом IncludeComponent — returnResult. Массив режет arResult через array_intersect_key, callable получает весь результат компонента. В renderComponentAjax() параметр объявлен как array, поэтому для callable объект создают напрямую:

        return new Response\Component(
    componentName: 'vendor:catalog.slider',
    componentTemplate: '.default',
    componentParams: ['SECTION_ID' => $sectionId],
    dataKeys: static fn(array $result): array => ['count' => count($result['ITEMS'])],
);

    

Признак для фронта — заголовок X-Process-Assets: assets, он ставится в конструкторе HtmlContent. Разбирает его buildAjaxPromiseToRestoreCsrf в js/main/core/core_ajax.js, то есть работает только через BX.ajax.runAction и BX.ajax.runComponentAction. Сценарий: строки вида <script type="extension/settings" сразу уходят в head, затем BX.load(css), BX.loadScript(js), затем остальные inline-строки, и только после этого промис резолвится.

        BX.ajax.runAction('vendor:catalog.api.slider.view', { data: { sectionId: 12 } })
    .then((response) => {
        // CSS и JS уже загружены
        BX.html(document.getElementById('slider'), response.data.html);
        console.log(response.data.componentResult.ITEMS);
    });

    

Уже подключённые файлы BX.load пропускает: isCssLoaded() / isScriptLoaded() сравнивают URL без ?timestamp и .min со списком из DOM и BX.setJSList() / BX.setCSSList(). При объединении CSS/JS сравнивается URL бандла. Компонент из рабочей области попадает в page_<hash>.js, а ответ приносит те же файлы как template_<hash>.js. Хэш совпадает, а URL разный, поэтому script.js шаблона выполнится повторно и должен это выдерживать. Inline-строки и теги extension/settings уходят в head при каждом запросе: дубли отсекаются только внутри одного ответа. Если дёрнуть тот же экшен голым fetch или BX.ajax, заголовок никто не обработает и ассеты придётся подключать вручную.

HtmlContent — более общий класс: он принимает любой ContentAreaInterface, то есть любой источник готового HTML, а секцию данных добавляет только тогда, когда объект дополнительно реализует DataSectionInterface. Так делают, например, в модуле sale — анонимный класс с одним методом getHtml(). Response\Component — частный случай для компонента: он собирает ContentArea\Component и передаёт его в родительский конструктор.

Итог

Response\Component возвращает HTML вместе с фактически использованными CSS, JS и inline-строками и помечает ответ заголовком X-Process-Assets. На фронте достаточно BX.ajax.runAction — ассеты подгрузятся до резолва промиса.

Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти