#компоненты

Советы с тегом «компоненты»

2 совета

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 — ассеты подгрузятся до резолва промиса.

PropertyFeature вместо ручного списка свойств каталога

PropertyFeature вместо ручного списка свойств каталога

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

Контент-менеджер отмечает в карточке свойства галки «Показывать в списке» и «Показывать на детальной», а разработчик в своём компоненте дублирует те же коды в параметре PROPERTY_CODE. Списки расходятся при первом же изменении настроек. Эти галки хранятся не в свойстве, а в отдельной таблице b_iblock_property_feature, и у неё есть публичное API — Bitrix\Iblock\Model\PropertyFeature.

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

Движок фич включается опцией «Использовать параметры свойств в компонентах и формах» property_features_enabled модуля iblock (значение по умолчанию — Y). Проверка — PropertyFeature::isEnabledFeatures(): bool.

        <?php
declare(strict_types=1);

use Bitrix\Iblock\Model\PropertyFeature;
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

if (PropertyFeature::isEnabledFeatures())
{
    // ['COLOR', 'MATERIAL'] или null
    $listCodes = PropertyFeature::getListPageShowPropertyCodes($iblockId, ['CODE' => 'Y']);
    $detailCodes = PropertyFeature::getDetailPageShowPropertyCodes($iblockId, ['CODE' => 'Y']);
}


    

Детали, которые важны на практике:

  • сигнатура — getListPageShowPropertyCodes($iblockId, array $parameters = []): ?array; второй аргумент понимает единственный ключ CODE в верхнем регистре;
  • без ['CODE' => 'Y'] возвращаются ID свойств (строками), а не символьные коды. С CODE => 'Y' вернётся CODE, а при пустом CODE — ID;
  • возвращается null, если движок выключен, $iblockId <= 0 или подходящих свойств нет. Пустого массива не будет — обрабатывайте null явно;
  • выборка фильтрует PROPERTY.ACTIVE = 'Y' и IS_ENABLED = 'Y', сортировка — по PROPERTY.SORT, затем PROPERTY.ID.

Именно так делают штатные компоненты. bitrix:catalog.section наследует Bitrix\Iblock\Component\ElementList: метод initIblockPropertyFeatures() вызывает loadDisplayPropertyCodes(), тот берёт getListPageShowPropertyCodes($iblockId, ['CODE' => 'Y']) для основного инфоблока и для инфоблока торговых предложений и кладёт результат в storage['IBLOCK_PARAMS'][$iblockId]['PROPERTY_CODE'] и OFFERS_PROPERTY_CODE. Точка входа — Base::processResultData(). bitrix:catalog.element через Bitrix\Iblock\Component\Element делает то же самое, но вызывает устаревший алиас getDetailPageShowProperties().

Хранилище — ORM-класс Bitrix\Iblock\PropertyFeatureTable (таблица b_iblock_property_feature, поля PROPERTY_ID, MODULE_ID, FEATURE_ID, IS_ENABLED). Писать напрямую не нужно: есть addFeatures(), updateFeatures(), setFeatures(), все возвращают Bitrix\Main\ORM\Data\Result. CIBlockProperty::Update() сам вызывает setFeatures(), если передан ключ FEATURES.

Свой тип фичи регистрируется обработчиком события модуля iblock с полным именем Bitrix\Iblock\Model\PropertyFeature::OnPropertyFeatureBuildList. Обработчик получает параметры property и description и возвращает EventResult(EventResult::SUCCESS, [...]) с элементами MODULE_ID, FEATURE_ID, FEATURE_NAME. Эталон — Bitrix\Catalog\Product\PropertyCatalogFeature::handlerPropertyFeatureBuildList(), добавляющий IN_BASKET и OFFER_TREE.

Итог

Набор отображаемых свойств задаётся в карточке свойства и лежит в b_iblock_property_feature. Читайте его через PropertyFeature::getListPageShowPropertyCodes() / getDetailPageShowPropertyCodes() с ['CODE' => 'Y'] и проверкой на null — тогда кастомный компонент покажет ровно то же, что catalog.section.

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

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

на связи

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

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

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

Войти