Заготовка. Свой компонент Битрикс D7 и ajax с BX.ajax.runComponentAction

Напишем заготовку для создания своего компонента. Объяснений будет по минимуму, многое очень подробно написано в этих ссылках:


Структура компонента Битрикс

Напомним себе лишний раз структуру компонента и оставим под рукой.

Создаем папку со своим namespace в папке local/components/ У меня это будет папка kochnev

Далее внутри создаем папку с названием компонента, у меня это будет draft

Теперь добавляем файлы:

Файл .description.php

Содержит описание компонента для отображения в визуальном редакторе Битрикс.

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
    die();
}

$arComponentDescription = [
    'NAME' => GetMessage('K4v_DRAFT_COMPONENT_NAME'),
    'DESCRIPTION' => GetMessage('K4v_DRAFT_COMPONENT_DESCRIPTION'),
    'ICON' => '/images/search_page.gif',
    'CACHE_PATH' => 'Y',
    'PATH' => [
        'ID' => 'kochnev',
        'NAME' => GetMessage('K4v_DRAFT_COMPONENT_CATEGORY')
    ],
];

Lang файл .description.php local/components/kochnev/draft/lang/ru/.description.php

<?php
$MESS ['K4v_DRAFT_COMPONENT_NAME'] = "Заготовка кастомного компонента";
$MESS ['K4v_DRAFT_COMPONENT_DESCRIPTION'] = "Описание работы кастомного компонента.";
$MESS ['K4v_DRAFT_COMPONENT_CHILD_NAME'] = "Подкатегория кастомного компонента";
$MESS ['K4v_DRAFT_COMPONENT_CATEGORY'] = "Kochnev";

Если компонент не появился в списке, нужно нажать кнопку "Обновить"

Файл .parameters.php

Как формировать этот файл подробно описано в этой статье

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}

$arComponentParameters = [
    'GROUPS' => [
        'GROUP_SETTINGS' => [
            'NAME' => GetMessage('K4v_DRAFT_COMPONENT_SETTINGS'),
        ],
    ],
    'PARAMETERS' => [
        'AJAX_MODE' => [],
        'CACHE_TIME' => ['DEFAULT' => 3600],
        'DRAFT_PARAMETER_STRING' => [
            'PARENT' => 'GROUP_SETTINGS',
            'NAME' => GetMessage('K4v_DRAFT_COMPONENT_PARAMETER_STRING'),
            'TYPE' => 'STRING',
            'DEFAULT' => 'Значение по умолчанию',
        ],
        'DRAFT_PARAMETER_LIST' => [
            'PARENT' => 'GROUP_SETTINGS',
            'NAME' => GetMessage('K4v_DRAFT_COMPONENT_PARAMETER_CHECKBOX'),
            'TYPE' => 'CHECKBOX',
            'DEFAULT' => 'N',
        ],
        'DRAFT_PARAMETER_CHECKBOX' => [
            'PARENT' => 'GROUP_SETTINGS',
            'NAME' => GetMessage('K4v_DRAFT_COMPONENT_PARAMETER_LIST'),
            'TYPE' => 'LIST',
            "VALUES" => [
                1 => GetMessage('K4v_DRAFT_COMPONENT_PARAMETER_LIST_VAL_1'),
                2 => GetMessage('K4v_DRAFT_COMPONENT_PARAMETER_LIST_VAL_2'),
                3 => GetMessage('K4v_DRAFT_COMPONENT_PARAMETER_LIST_VAL_3'),
            ],
            "MULTIPLE" => "N",
        ],
    ],
];

Lang файл .description.php local/components/kochnev/draft/lang/ru/.parameters.php

<?php
$MESS ['K4v_DRAFT_COMPONENT_SETTINGS'] = "Группа настроек";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_STRING'] = "Параметр строка";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_CHECKBOX'] = "Параметр чекбокс";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_LIST'] = "Параметр список";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_LIST_VAL_1'] = "Элемент списка 1";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_LIST_VAL_2'] = "Элемент списка 2";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_LIST_VAL_3'] = "Элемент списка 3";
$MESS ['K4v_DRAFT_COMPONENT_PARAMETER_DEF_STRING'] = "Значение по умолчанию";

После настроек всех параметров, вызов компонента выглядит так

<?$APPLICATION->IncludeComponent(
    "kochnev:draft",
    "",
    Array(
        "AJAX_MODE" => "N",
        "AJAX_OPTION_ADDITIONAL" => "",
        "AJAX_OPTION_HISTORY" => "N",
        "AJAX_OPTION_JUMP" => "N",
        "AJAX_OPTION_STYLE" => "Y",
        "CACHE_TIME" => "3600",
        "CACHE_TYPE" => "A",
        "DRAFT_PARAMETER_CHECKBOX" => "Y",
        "DRAFT_PARAMETER_LIST" => "2",
        "DRAFT_PARAMETER_STRING" => "Значение по умолчанию"
    )
);?>

Файл class.php

Это сердце нашего компонента. Здесь мы:

  1. Подготавливаем параметры.

  2. Получаем данные.

  3. Подключаем шаблон.

 

Наследуемся от CBitrixComponent и наш класс подключится автоматически.

<?php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) {
    die();
}

// Класс для загрузки необходимых файлов, классов, модулей
use Bitrix\Main\Loader;

// Класс для работы с языковыми файлами
use Bitrix\Main\Localization\Loc;

// Класс для всех исключений в системе
use Bitrix\Main\SystemException;

// Класс для вспомогательных методов в системе
use Bitrix\Iblock\Component\Tools;

// Наш собственный класс из папки lib
use Draft\Demo;

class CustomCBitrixComponent extends CBitrixComponent
{
    // Выполняет основной код компонента, аналог конструктора (метод подключается автоматически)
    public function executeComponent(): void
    {
        // Если у нас много логики и требуется разносить ее по отдельным классам.
        // Регистрируем автозагрузку для нашего пространства имен
        Loader::registerAutoLoadClasses(
            null,
            [
                // ключ - имя класса с пространством имен, значение - путь относительно корня сайта к файлу.
                Demo::class => '/local/components/kochnev/draft/lib/Demo/Demo.php',
            ]
        );

        dump(Demo::test());

        try {
            // Подключаем метод проверки подключения модуля «Информационные блоки»
            $this->checkModules();
            // Подключаем метод подготовки массива $arResult
            $this->getResult();
        } catch (SystemException $e) {
            ShowError($e->getMessage());
        }
    }

    // Подключение языковых файлов (метод подключается автоматически)
    public function onIncludeComponentLang(): void
    {
        Loc::loadMessages(__FILE__);
    }

    // Проверяем установку модуля «Информационные блоки» (метод подключается внутри класса try...catch)
    protected function checkModules(): void
    {
        // Если модуль не подключен
        if (!Loader::includeModule('iblock')) // Выводим сообщение в catch
        {
            throw new SystemException(Loc::getMessage('K4v_DRAFT_COMPONENT_IBLOCK_MODULE_NOT_INSTALLED'));
        }
    }

    // Обработка массива $arParams (метод подключается автоматически)
    public function onPrepareComponentParams($arParams)
    {
        // Время кеширования
        if (!isset($arParams['CACHE_TIME'])) {
            $arParams['CACHE_TIME'] = 3600;
        } else {
            $arParams['CACHE_TIME'] = (int) $arParams['CACHE_TIME'];
        }

        // Покажем исключение о том, что какого-то параметра не хватает
        /*if (!isset($arParams['IBLOCK_ID'])) {
            throw new SystemException(Loc::getMessage('K4v_DRAFT_COMPONENT_NO_IBLOCK_ID'));
        }*/

        // Пример с показом ошибка, вместо исключения
        if (empty($arParams['DRAFT_PARAMETER_STRING'])) {
            ShowError(Loc::getMessage('K4v_DRAFT_COMPONENT_PARAMETER_STRING_EMPTY'));
            // Или
            // throw new SystemException(Loc::getMessage('K4v_DRAFT_COMPONENT_PARAMETER_STRING_EMPTY'));
        }

        // Возвращаем в метод новый массив $arParams
        return $arParams;
    }

    // подготовка массива $arResult (метод подключается внутри класса try...catch)
    protected function getResult()
    {
        // если нет валидного кеша, получаем данные из БД
        if ($this->startResultCache()) {
            // Здесь получаем необходимые данные (через GetList и др.)
            // ...

            // Формируем массив arResult
            $this->arResult[] = [
                'NAME' => 'Мой кастомный компонент',
                'ITEMS' => [
                    1 => 'Item 1',
                    2 => 'Item 2',
                    3 => 'Item 3',
                    4 => 'Item 4',
                ]
            ];

            // Кеш не затронет весь код ниже, он будут выполняться на каждом хите,
            // здесь работаем с другим $arResult, будут доступны только те ключи массива, которые перечислены
            // в вызове SetResultCacheKeys()
            if (isset($this->arResult)) {
                // Ключи $arResult перечисленные при вызове этого метода, будут доступны
                // в component_epilog.php и ниже по коду, обратите внимание там будет другой $arResult
                $this->SetResultCacheKeys(
                    []
                );
                // Подключаем шаблон и сохраняем кеш
                $this->IncludeComponentTemplate();
            } else {
                // Если выяснилось, что кешировать данные не требуется, прерываем кеширование
                // и выдаем сообщение «Страница не найдена»
                $this->AbortResultCache();
                Tools::process404(Loc::getMessage('PAGE_NOT_FOUND'),true,true);
            }
        }
    }

    /**
     * Тут могут быть другие методы для получения и формирование данных.
     * Но лучше не плодить тут простыню и выносить логику в отдельные классы (папка lib).
     */
}

Проверим вывод всех данных и убедимся, что все подключено и работает.

Распечатаем Demo::test() из class.php и $arParams, $arResult из template.php

Структура папки

Файл ajax.php

Тут будем принимать и обрабатывать все наши ajax-запросы.

Подробно как это работает можно почитать здесь:

Здесь же будет только заготовка.

<?php

use Bitrix\Main\Engine\ActionFilter\Authentication;
use Bitrix\Main\Engine\ActionFilter\HttpMethod;
use Bitrix\Main\Engine\ActionFilter\Csrf;
use Bitrix\Main\Engine\Controller;

if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) {
    die();
}

class CustomAjaxController extends Controller {

    public function configureActions():array 
    {
        return [
            /**
             * Пример с включенными фильтрами.
             *
             * Подробно тут: https://dev.1c-bitrix.ru/api_d7/bitrix/main/engine/actionfilter/index.php
             */
            // Название метода без 'Action'
            'test' => [
                'prefilters' => [
                    // Проверяет аутентифицирован ли пользователь
                    // и блокирует выполнение действия, если проверка не прошла, установив http status 401.
                    // Может выполнить редирект на страницу авторизации при необходимости.
                    new Authentication(),

                    // Проверяет, по какому http методу запускается действие
                    new HttpMethod([
                        HttpMethod::METHOD_GET, // Позволяем GET
                        HttpMethod::METHOD_POST // Позволяем POST
                    ]),

                    // Проверяет наличие и корректность csrf-токена
                    new Csrf()
                ],
                /**
                 * Пример с выключенными фильтрами.
                 *
                 * Чтобы удалить фильтры из дефолтных, нужно добавить знак минус "-prefilters"
                 */
                /*'test' => [
                    'prefilters' => [],
                    'postfilters' => []
                ]*/
            ]
        ];
    }

    /**
     * Суффикс Action обязателен.
     */
    public function testAction(string $personName):array
    {
        if (empty($personName)) {
            return [
                'success' => false,
                'error' => 'Тут пишем ошибку для разработчика, напр. Ошибка в методе testAction()',
                'message' => 'Тут сообщение для пользователя, напр. Пожалуйста, введите имя.'
            ];
        }

        return [
            'success' => true,
            'message' => "Привет, $personName! Ваше имя успешно получено."
        ];
    }
}

Файл шаблона template.php

<?php if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    die();
}
/** @var array $arParams */
/** @var array $arResult */
/** @global CMain $APPLICATION */
/** @global CUser $USER */
/** @global CDatabase $DB */
/** @var CBitrixComponentTemplate $this */
/** @var string $templateName */
/** @var string $templateFile */
/** @var string $templateFolder */
/** @var string $componentPath */

/** @var CBitrixComponent $component */

/*dump('$arParams', $arParams);
dump('$arResult', $arResult);*/
?>
<div class="form-wrapper">
    <h2>Простая форма с AJAX</h2>
    <br>
    <form id="simpleForm">
        <input type="text" name="personName" placeholder="Введите ваше имя">
        <button type="submit">Отправить</button>
    </form>
    <br>
    <div id="result"></div>
</div>

Файл шаблона script.js

document.addEventListener('DOMContentLoaded', () => {
    testAjax();

    function testAjax() {
        const form = document.getElementById('simpleForm');
        const resultDiv = document.getElementById('result');

        if (!form || !resultDiv) {
            return;
        }

        form.addEventListener('submit', function (e) {
            e.preventDefault();

            const personName = form.querySelector('[name="personName"]').value;

            BX.ajax.runComponentAction('kochnev:draft', 'test', {
                mode: 'ajax',
                data: {
                    personName: personName
                },
            }).then(function (response) {
                // console.log(response);
                if (response.data.success === true) {
                    resultDiv.innerHTML = response.data.message;
                } else {
                    resultDiv.innerHTML = response.data.message;
                    console.error(response.data.error);
                }
            }, function (response) {
                //сюда будут приходить все ответы, у которых status !== 'success'
                console.error(response);
            });
        })
    }
});

В шаблоне лежит форма, проверяющая работу отправки запросов. При вводе текста, получаем ответ от сервера.