Битрикс: красивая страница админки

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

Рассматривается сферическая страница в вакууме. Нет никакого модуля или компонента. Только один файл (скрипт) с формой и кодом, который эту форму обрабатывает. Если создавать страницу админки в своём модуле, то файлы-скрипты задействованы будут другие, но приёмы оформления (именно на них сделан акцент статьи) остаются те же самые.

Добавление своей страницы в админке

Для добавления своей страницы нужно сделать три вещи: 1) технический скрипт, запускающий саму страницу; 2) скрипт с кодом страницы; 3) ссылка на страницу в самой админке битрикса.

Добавляем технический скрипт. Внутри в нём только отсылка к скрипту, в котором расположена вся логика страницы. Мой пример: /bitrix/admin/captcha_settings.php с содержимым:

<?php
require $_SERVER['DOCUMENT_ROOT'] . '/local/php_interface/include/captcha_settings/index.php';

Далее создаём скрипт /local/php_interface/include/captcha_settings/index.php. Пустая минимальная заготовка:

<?php

/**
 * Самописная страница админки битрикса
 */

declare(strict_types=1);

require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin.php';

/**
 * @var CMain $APPLICATION
 */

$APPLICATION->SetTitle('Красивая админка');
?><div>Заготовка страницы админки</div>
<?php
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_admin.php';

Третий компонент для нашей страницы админки - добавить ссылку на эту страницу в меню внутри битриксовой админки. Меню ссылается именно на технический скрипт - тот, в котором нет логики, а только подключение другого скрипта. Для этого нужно подписаться на встроенное событие построения меню админки. Внутри события в массив добавляем свой пункт меню. Пример без сторонних классов и методов (хотя, конечно, лучше всё красиво распределять по классам для порядка в коде) нужно разместить, например, в /bitrix/php_interface/init.php. Примеры подписывания на события, когда есть классы и методы, реализующие обработку события.

// Пункт в админке в разделе "Сервисы"
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
    'main',
    'OnBuildGlobalMenu',
    function (&$aGlobalMenu, &$aModuleMenu) use ($APPLICATION) {
        if ($APPLICATION->GetFileAccessPermission('/bitrix/admin/captcha_settings.php') > 'D') {
            // Проверили наличие прав у пользователя на данную страницу
            $aModuleMenu[] = [
                'parent_menu' => 'global_menu_services',
                'sort'        => 150,
                'text'        => 'Настройка каптч',
                'title'       => 'Настройка каптч',
                'url'         => 'captcha_settings.php',
                'more_url'    => [],
                'icon'        => 'sonet_menu_icon',
                'page_icon'   => '',
            ];
        }
    }
);

Тут используется древнее битриксовое событие OnBuildGlobalMenu. Сами себе придумываем название для пункта меню, порядок сортировки, и можем позаимствовать иконку из недр битрикса, если свою пока делать некогда. sonet_menu_icon - это один из блоков из картинки /bitrix/panel/main/images/icons-sprite-14.png, который объявлен в /bitrix/panel/main/admin.css

Итогом этих трёх шагов становится добавленный пункт меню и сама страница-заготовка:

За кадром остаётся вопрос назначения прав на эту страницу админки. Если коротко, то это делается через [Контент / Структура сайта / Файлы и папки]. Переходим в каталог /bitrix/admin. Находим свою страницу (captcha_settings). Рядом с файлом кнопка-бутерброд и там "Права на доступ продукта". Нужным группам пользователей разрешаем. А тем, кому это не нужно видеть - запрещаем.

Стилизованное наполнение страницы

Теперь некрасивую заготовку превратим в современный дизайн административных страниц. Я опишу лишь несколько элементов с отсылками к их описаниям официальной документации. В этой статье не затрагиваем стилизацию таблиц. Только форма с полями и какие-то тексты-пояснения на странице.

Первое, что нужно сделать - сделать красивый фон-подложку. Описываю свой вариант, который нашёл методом научного тыка. Будет полезно, если в комментариях к этой статье кто-то поделится более универсальным вариантом, если такой существует.

Заменим тело страницы на такой код:

<?php
$request = \Bitrix\Main\Context::getCurrent()->getRequest();

\Bitrix\Main\UI\Extension::load([
    'ui.forms',
    'ui.layout-form',
]);
?>
<form method="post" name="post_form" action="<?php echo $request->getRequestUri();?>">
    <div class="ui-form ui-form-section">
        <div>Заготовка страницы админки</div>
    </div>
</form>

Вся эта страница админки - это будет одна большая форма. Сразу пропишем параметры формы. Обработчиком формы будет этот же скрипт. Тут мы подключили пару расширений из битриксовой UI-библиотеки. Это позволило получить белый фон в форме.

Далее создадим красивый заголовок и под ним в две колонки: подзаголовок и инпут checkbox. Мне удалось найти красивый заголовок только в расширении ui.sidepanel-content. Наверняка, есть и другие, - помогите их найти, посоветуйте в комментариях. Разметка по строкам и колонкам доступны благодаря расширению ui.layout-form, которое подключили ранее. Внешний вид для чекбокса берётся из стилей расширения ui.forms. Код примет такой вид:

<?php
$request = \Bitrix\Main\Context::getCurrent()->getRequest();

\Bitrix\Main\UI\Extension::load([
    'ui.forms',
    'ui.layout-form',
    'ui.sidepanel-content',
]);
?>
<form method="post" name="post_form" action="<?php echo $request->getRequestUri();?>">
    <div class="ui-form ui-form-section">
        <div class="ui-form-row"><div class="ui-slider-heading-3">Какие запросы проверять</div></div>
        <div class="ui-form-row">
            <div class="ui-form-label"><div class="ui-ctl-label-text">Активность</div></div>
            <div class="ui-form-content">
                <div class="ui-form-row">
                    <label class="ui-ctl ui-ctl-checkbox">
                        <input type="checkbox" class="ui-ctl-element" name="captcha_all_requests">
                        <div class="ui-ctl-label-text">Проверять каждый запрос до тех пор, пока не появится признак успешного прохождения проверки</div>
                    </label>
                </div>
            </div>
        </div>
    </div>
</form>

Ещё расширим форму. Добавим второй заголовок, так же инпут checkbox, но уже с дополнительной ссылкой и ещё пару текстовых инпутов со своими подписями. Все нужные стили уже есть благодаря трём подключенным ранее расширениям битрикса. HTML-разметка формы:

<form method="post" name="post_form" action="<?php echo $request->getRequestUri();?>">
    <div class="ui-form ui-form-section">
        <div class="ui-form-row"><div class="ui-slider-heading-3">Какие запросы проверять</div></div>
        <div class="ui-form-row">
            <div class="ui-form-label"><div class="ui-ctl-label-text">Активность</div></div>
            <div class="ui-form-content">
                <div class="ui-form-row">
                    <label class="ui-ctl ui-ctl-checkbox">
                        <input type="checkbox" class="ui-ctl-element" name="captcha_all_requests">
                        <div class="ui-ctl-label-text">Проверять каждый запрос до тех пор, пока не появится признак успешного прохождения проверки</div>
                    </label>
                </div>
            </div>
        </div>
        <div class="ui-form-row"><div class="ui-slider-heading-3">Google Recaptcha v3</div></div>
        <div class="ui-form-row">
            <div class="ui-form-label">
                <div class="ui-ctl-label-text">Активность</div>
            </div>
            <div class="ui-form-content">
                <div class="ui-form-row">
                    <label class="ui-ctl ui-ctl-checkbox">
                        <input
                            type="checkbox"
                            class="ui-ctl-element"
                            name="google_active"
                        >
                        <div class="ui-ctl-label-text">
                            Активация будет использовать бесплатный месячный лимит запросов
                        </div>
                    </label>
                    <a
                        href="https://www.google.com/recaptcha/admin/site/12345678"
                        class="ui-form-link"
                        target="_blank"
                    >личный кабинет сервиса</a>
                </div>
            </div>
        </div>
        <div class="ui-form-row">
            <div class="ui-form-label"><div class="ui-ctl-label-text">Доступы</div></div>
            <div class="ui-form-content">
                <div class="ui-form-row">
                    <div class="ui-form-label"><div class="ui-ctl-label-text">Ключ сайта</div></div>
                    <div class="ui-ctl ui-ctl-textbox ui-ctl-w50">
                        <input type="text" class="ui-ctl-element" name="google_site_key" value="">
                    </div>
                </div>
                <div class="ui-form-row">
                    <div class="ui-form-label"><div class="ui-ctl-label-text">Секретный ключ</div></div>
                    <div class="ui-ctl ui-ctl-textbox ui-ctl-w50">
                        <input type="text" class="ui-ctl-element" name="google_secret_key" value="">
                    </div>
                </div>
            </div>
        </div>
    </div>
</form>

Форма стала выглядеть так:

Добавим ещё пару элементов из расширения ui.alerts: сообщение с иконкой. Представим, что есть переменная, которая определяет успешный вид сообщения или тревожный вид сообщения. Разметка сообщения получится такой:

<div class="ui-form-row">
    <div class="ui-alert <?php echo $isGoogleFreePeriod ? 'ui-alert-success ui-alert-icon-info' : 'ui-alert-warning ui-alert-icon-warning';?>">
        <span class="ui-alert-message">
            <?php echo $isGoogleFreePeriod
                ? 'Ещё не исчерпан бесплатный месячный лимит запросов в Google Recaptcha v3'
                : 'Бесплатные запросы на этот месяц закончились';?>
        </span>
    </div>
</div>

Какие иконки тут могут быть?! Можно догадаться только заглянув в исходные стили: ui-alert-icon-warning, ui-alert-icon-danger, ui-alert-icon-info и ui-alert-icon-forbidden.

Успешный вид сообщения на странице выглядит так:

Есть и другого типа иконка-подсказка из расширения ui.hint. Добавим одну такую после подписи Активность. Разметка:

<div class="ui-form-label">
    <div class="ui-ctl-label-text">Активность</div>
    <a href="https://developers.google.com/recaptcha/docs/v3?hl=ru" target="_blank">
        <span class="ui-hint-icon"></span>
    </a>
</div>

Перечисленных элементов вполне достаточно для начала, чтобы сделать кастомную страницу админки в битриксе. И при этом задействовать все встроенные в битрикс стили. Во всём примере не было написано ни единого собственного стиля - всё из поставки битрикса. По ссылкам в статье можно попасть в документацию по этим расширениям и там найти ещё уйму других.

Интерактив

Мы создали кастомную страницу с формой. Но пока не сделали сохранения. Приведу пример, как можно добавить JavaScript, отправить форму на этот же скрипт, обработать результат и отобразить пользователю сообщение о результате.
Внутри ui-form-section добавляем последним узлом кнопку со стандартными классами для оформления и одним своим классом, чтобы на него навесить потом обработчик:

<div class="ui-btn-container">
    <button type="submit" class="ui-btn ui-btn-success js-btn-save">Сохранить</button>
</div>

Чтобы стилизовать кнопку, подключим расширение ui.buttons. И сразу ещё подключим расширение для показа всплывающего сообщения - ui.dialogs.messagebox (в этом расширении есть не только стили, но и JavaScript функции). В разметке всё готово. Перед закрывающим тегом <form> (хотя можно и в другое место) добавляем тег <script> с кодом, котрый:

  • Описывает некий глобальный объект, внутри которого находятся все полезные функции
  • Функция init() из набора выполняет начальную инициализацию всего нужного на странице: в нашем случае только навешивание обработчика клика на кнопку
  • Функция initClickSubmitButton() выполняет код, который добавляет обработчик обытия клика на кнопку
  • Функция submitFormHandler() выполняет сбор данных с формы и отправку запроса на сервер. Показал полностью ручой сбор данных для каждого из четырёх инпутов, чтобы корректно обработать чекбосы. Внутри есть пример показа битриксового диалогового окна - в примере оно отобразит результат обработки, который придёт с сервера.
  • Битриксовая функция  BX.ready(function () {} выполнится один раз при старте страницы и запустит нашу функию инициализации всего необходимого.

Как видно тут чистый JavaScript (без jQuery и каких-либо ещё библиотек) и несколько функций от битрикса:

<script>
    window.captchaSettings = {
        init: function () {
            "use strict";

            window.captchaSettings.initClickSubmitButton();
        },
        initClickSubmitButton: function () {
            "use strict";

            const $buttons = document.querySelectorAll(".js-btn-save");
            Array.prototype.forEach.call($buttons, function ($button) {
                $button.addEventListener("click", window.captchaSettings.submitFormHandler);             });         },         submitFormHandler: function (event) {             "use strict";             if (!!event) {                 event.preventDefault();                 const $form = document.querySelector("[name=\"post_form\"]");                 let url = "";                 if (!!$form && $form.getAttribute("action")) {                     url = $form.getAttribute("action");                 }                 let request = new XMLHttpRequest();                 let formData = new FormData();                 let isCaptchaAllRequests = "N";                 const captchaAllRequestsInput = document.querySelector("[name=\"captcha_all_requests\"]");                 if (!!captchaAllRequestsInput) {                     isCaptchaAllRequests = captchaAllRequestsInput.checked ? "Y" : "N";                 }                 let isGoogleActive = "N";                 const activeGoogleInput = document.querySelector("[name=\"google_active\"]");                 if (!!activeGoogleInput) {                     isGoogleActive = activeGoogleInput.checked ? "Y" : "N";                 }                 let googleSiteKey = "";                 const googleSiteKeyInput = document.querySelector("[name=\"google_site_key\"]");                 if (!!googleSiteKeyInput) {                     googleSiteKey = googleSiteKeyInput.value;                 }                 let googleSecretKey = "";                 const googleSecretKeyInput = document.querySelector("[name=\"google_secret_key\"]");                 if (!!googleSecretKeyInput) {                     googleSecretKey = googleSecretKeyInput.value;                 }                 formData.append("captcha_all_requests", isCaptchaAllRequests);                 formData.append("google_active", isGoogleActive);                 formData.append("google_site_key", googleSiteKey);                 formData.append("google_secret_key", googleSecretKey);                 request.open("POST", url, true);                 request.responseType = "json";                 request.send(formData);                 request.onload = function () {                     if (request.readyState === 4 && request.status === 200) {                         const jsonResponse = request.response;                         if (jsonResponse.hasOwnProperty("success") && jsonResponse.success) {                             let message = "";                             if (jsonResponse.hasOwnProperty("messages") && jsonResponse.messages) {                                 message = jsonResponse.messages.join("; ");                             }                             BX.UI.Dialogs.MessageBox.show(                                 {                                     message: "<div class=\"ui-alert ui-alert-success\">" +                                         "<span class=\"ui-alert-message\">" + message + "</span>" +                                         "</div>",                                     title: "Сохранение",                                     modal: true,                                     buttons: BX.UI.Dialogs.MessageBoxButtons.OK,                                     onOk: function (messageBox) {                                         messageBox.close();                                     }                                 }                             );                         } else {                             // Запрос выполнился, но там какие-то ошибки                             BX.UI.Dialogs.MessageBox.show(                                 {                                     message: "<div class=\"ui-alert ui-alert-danger\">" +                                         "<span class=\"ui-alert-message\">В результате запроса получены ошибки</span>" +                                         "</div>",                                     title: "Сохранение",                                     modal: true,                                     buttons: BX.UI.Dialogs.MessageBoxButtons.OK,                                     onOk: function (messageBox) {                                         messageBox.close();                                     }                                 }                             );                         }                     } else {                         // Запрос не выполнился                         BX.UI.Dialogs.MessageBox.show(                             {                                 message: "<div class=\"ui-alert ui-alert-danger\">" +                                     "<span class=\"ui-alert-message\">Запрос не выполнился. " + request.status +                                     ": " + request.statusText + "</span>" + "</div>",                                 title: "Сохранение",                                 modal: true,                                 buttons: BX.UI.Dialogs.MessageBoxButtons.OK,                                 onOk: function (messageBox) {                                     messageBox.close();                                 }                             }                         );                     }                 }             }         }     };     BX.ready(function () {         "use strict";         window.captchaSettings.init();     }); </script>

Сам по себе JavaScript не отработает полностью без соотвествующей обработки на бэкенде. Поэтому в этот же скрипт добавим обработку на PHP:


// Сохраняем настройки
if ($request->isPost()) {
    $arResponse = ['success' => false, 'messages' => []];
    $isError = false;

    if (
        ($requestCaptchaAllRequests = $request->getPost('captcha_all_requests'))
        && ('Y' === $requestCaptchaAllRequests || 'N' === $requestCaptchaAllRequests)
    ) {
        try {
            \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_all_requests', $requestCaptchaAllRequests);
        } catch (\Exception $exception) {
            $isError = true;
            $arResponse['messages'][] = 'Fail set "settings_captcha_all_requests": ' . $exception->getMessage();
        }
    }

    if (
        ($requestGoogleActive = $request->getPost('google_active'))
        && ('Y' === $requestGoogleActive || 'N' === $requestGoogleActive)
    ) {
        try {
            \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_google_active', $requestGoogleActive);
        } catch (\Exception $exception) {
            $isError = true;
            $arResponse['messages'][] = 'Fail set "settings_captcha_google_active": ' . $exception->getMessage();
        }
    }

    $requestGoogleSiteKey = $request->getPost('google_site_key') ?? '';
    try {
        \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_google_site_key', $requestGoogleSiteKey);
    } catch (\Exception $exception) {
        $isError = true;
        $arResponse['messages'][] = 'Fail set "settings_captcha_google_site_key": ' . $exception->getMessage();
    }

    $requestGoogleSecretKey = $request->getPost('google_secret_key') ?? '';
    try {
        \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_google_secret_key', $requestGoogleSecretKey);
    } catch (\Exception $exception) {
        $isError = true;
        $arResponse['messages'][] = 'Fail set "settings_captcha_google_secret_key": ' . $exception->getMessage();
    }

    if (!$isError) {
        $arResponse['success'] = true;
        $arResponse['messages'][] = 'Настройки сохранены';
    }

    try {
        $strResponse = \Bitrix\Main\Web\Json::encode($arResponse);
    } catch (\Exception $exception) {
        AddMessage2Log('Не удалось получить Json: ' . $exception->getMessage());
        $strResponse = '{}';
    }

    global $APPLICATION;
    $APPLICATION->restartBuffer();

    header('Content-Type: application/json; charset=UTF-8');
    \CMain::finalActions($strResponse);
}

Тут мы просто ловим входящие параметры и сохраняем их в базу данных (в данном случае через битриксовый механизм сохранения настроек модуля). Своего модуля у нас нет, поэтому сохраняем всё в выдуманный модуль custom_main, - да так тоже работает в битриксе. На выходе возвращаем в браузер ответ в виде JSON.

Теперь форма стала рабочей: она сохраняется!

Но ещё нужно при открытии страницы в форму подставлять те значения, которые уже есть в настройках в базе данных.

В итоге вся страница (PHP + HTML + JavaScript) этого примера примет такой вид:


<?php

/**
 * Самописная страница админки битрикса
 */

declare(strict_types=1);

require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin.php';

/**
 * @var CMain $APPLICATION
 */

$APPLICATION->SetTitle('Красивая админка');

$request = \Bitrix\Main\Context::getCurrent()->getRequest();

\Bitrix\Main\UI\Extension::load([
    'ui.forms',
    'ui.layout-form',
    'ui.sidepanel-content',
    'ui.alerts',
    'ui.hint',
    'ui.buttons',
    'ui.dialogs.messagebox',
]);

$isGoogleFreePeriod = true;

// Сохраняем настройки
if ($request->isPost()) {
    $arResponse = ['success' => false, 'messages' => []];
    $isError = false;

    if (
        ($requestCaptchaAllRequests = $request->getPost('captcha_all_requests'))
        && ('Y' === $requestCaptchaAllRequests || 'N' === $requestCaptchaAllRequests)
    ) {
        try {
            \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_all_requests', $requestCaptchaAllRequests);
        } catch (\Exception $exception) {
            $isError = true;
            $arResponse['messages'][] = 'Fail set "settings_captcha_all_requests": ' . $exception->getMessage();
        }
    }

    if (
        ($requestGoogleActive = $request->getPost('google_active'))
        && ('Y' === $requestGoogleActive || 'N' === $requestGoogleActive)
    ) {
        try {
            \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_google_active', $requestGoogleActive);
        } catch (\Exception $exception) {
            $isError = true;
            $arResponse['messages'][] = 'Fail set "settings_captcha_google_active": ' . $exception->getMessage();
        }     }     $requestGoogleSiteKey = $request->getPost('google_site_key') ?? '';     try {         \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_google_site_key', $requestGoogleSiteKey);     } catch (\Exception $exception) {         $isError = true;         $arResponse['messages'][] = 'Fail set "settings_captcha_google_site_key": ' . $exception->getMessage();     }     $requestGoogleSecretKey = $request->getPost('google_secret_key') ?? '';     try {         \Bitrix\Main\Config\Option::set('custom_main', 'settings_captcha_google_secret_key', $requestGoogleSecretKey);     } catch (\Exception $exception) {         $isError = true;         $arResponse['messages'][] = 'Fail set "settings_captcha_google_secret_key": ' . $exception->getMessage();     }     if (!$isError) {         $arResponse['success'] = true;         $arResponse['messages'][] = 'Настройки сохранены';     }     try {         $strResponse = \Bitrix\Main\Web\Json::encode($arResponse);     } catch (\Exception $exception) {         AddMessage2Log('Не удалось получить Json: ' . $exception->getMessage());         $strResponse = '{}';     }     global $APPLICATION;     $APPLICATION->restartBuffer();     header('Content-Type: application/json; charset=UTF-8');     \CMain::finalActions($strResponse); } ?> <form method="post" name="post_form" action="<?php echo $request->getRequestUri();?>">     <div class="ui-form ui-form-section">         <div class="ui-form-row"><div class="ui-slider-heading-3">Какие запросы проверять</div></div>         <div class="ui-form-row">             <div class="ui-form-label"><div class="ui-ctl-label-text">Активность</div></div>             <div class="ui-form-content">                 <div class="ui-form-row">                     <label class="ui-ctl ui-ctl-checkbox">                         <input                             type="checkbox"                             class="ui-ctl-element"                             name="captcha_all_requests"                             <?php echo 'Y' === \Bitrix\Main\Config\Option::get('custom_main', 'settings_captcha_all_requests') ? ' checked' : '';?>                         >                         <div class="ui-ctl-label-text">Проверять каждый запрос до тех пор, пока не появится признак успешного прохождения проверки</div>                     </label>                 </div>             </div>         </div>         <div class="ui-form-row"><div class="ui-slider-heading-3">Google Recaptcha v3</div></div>         <div class="ui-form-row">             <div class="ui-form-label">                 <div class="ui-ctl-label-text">Активность</div>                 <a href="https://developers.google.com/recaptcha/docs/v3?hl=ru" target="_blank">                     <span class="ui-hint-icon"></span>                 </a>             </div>             <div class="ui-form-content">                 <div class="ui-form-row">                     <div class="ui-alert <?php echo $isGoogleFreePeriod ? 'ui-alert-success ui-alert-icon-info' : 'ui-alert-warning ui-alert-icon-warning';?>">                         <span class="ui-alert-message">                             <?php echo $isGoogleFreePeriod                                 ? 'Ещё не исчерпан бесплатный месячный лимит запросов в Google Recaptcha v3'                                 : 'Бесплатные запросы на этот месяц закончились';?>                         </span>                     </div>                 </div>                 <div class="ui-form-row">                     <label class="ui-ctl ui-ctl-checkbox">                         <input                             type="checkbox"                             class="ui-ctl-element"                             name="google_active"                             <?php echo 'Y' === \Bitrix\Main\Config\Option::get('custom_main', 'settings_captcha_google_active') ? ' checked' : '';?>                         >                        <div class="ui-ctl-label-text">                             Активация будет использовать бесплатный месячный лимит запросов                         </div>                     </label>                     <a                         href="https://www.google.com/recaptcha/admin/site/12345678"                         class="ui-form-link"                         target="_blank"                     >личный кабинет сервиса</a>                 </div>             </div>         </div>         <div class="ui-form-row">             <div class="ui-form-label"><div class="ui-ctl-label-text">Доступы</div></div>             <div class="ui-form-content">                 <div class="ui-form-row">                     <div class="ui-form-label"><div class="ui-ctl-label-text">Ключ сайта</div></div>                     <div class="ui-ctl ui-ctl-textbox ui-ctl-w50">                         <input                             type="text"                             class="ui-ctl-element"                             name="google_site_key"                             value="<?php echo \Bitrix\Main\Config\Option::get('custom_main', 'settings_captcha_google_site_key');?>"                         >                     </div>                 </div>                 <div class="ui-form-row">                     <div class="ui-form-label"><div class="ui-ctl-label-text">Секретный ключ</div></div>                     <div class="ui-ctl ui-ctl-textbox ui-ctl-w50">                         <input                             type="text"                             class="ui-ctl-element"                             name="google_secret_key"                             value="<?php echo \Bitrix\Main\Config\Option::get('custom_main', 'settings_captcha_google_site_key');?>"                         >                     </div>                 </div>             </div>         </div>         <div class="ui-btn-container">             <button type="submit" class="ui-btn ui-btn-success js-btn-save">Сохранить</button>         </div>     </div>     <script>         window.captchaSettings = {             init: function () {                 "use strict";                 window.captchaSettings.initClickSubmitButton();             },             initClickSubmitButton: function () {                 "use strict";                 const $buttons = document.querySelectorAll(".js-btn-save");                 Array.prototype.forEach.call($buttons, function ($button) {                     $button.addEventListener("click", window.captchaSettings.submitFormHandler);                 });             },             submitFormHandler: function (event) {                 "use strict";                 if (!!event) {                     event.preventDefault();                     const $form = document.querySelector("[name=\"post_form\"]");                     let url = "";                     if (!!$form && $form.getAttribute("action")) {                         url = $form.getAttribute("action");                     }                     let request = new XMLHttpRequest();                     let formData = new FormData();                     let isCaptchaAllRequests = "N";                     const captchaAllRequestsInput = document.querySelector("[name=\"captcha_all_requests\"]");                     if (!!captchaAllRequestsInput) {                         isCaptchaAllRequests = captchaAllRequestsInput.checked ? "Y" : "N";                     }                     let isGoogleActive = "N";                     const activeGoogleInput = document.querySelector("[name=\"google_active\"]");                     if (!!activeGoogleInput) {                         isGoogleActive = activeGoogleInput.checked ? "Y" : "N";                     }                     let googleSiteKey = "";                     const googleSiteKeyInput = document.querySelector("[name=\"google_site_key\"]");                     if (!!googleSiteKeyInput) {                         googleSiteKey = googleSiteKeyInput.value;                     }                     let googleSecretKey = "";                     const googleSecretKeyInput = document.querySelector("[name=\"google_secret_key\"]");                     if (!!googleSecretKeyInput) {                         googleSecretKey = googleSecretKeyInput.value;                     }                     formData.append("captcha_all_requests", isCaptchaAllRequests);                     formData.append("google_active", isGoogleActive);                     formData.append("google_site_key", googleSiteKey);                     formData.append("google_secret_key", googleSecretKey);                     request.open("POST", url, true);                     request.responseType = "json";                     request.send(formData);                     request.onload = function () {                         if (request.readyState === 4 && request.status === 200) {                             const jsonResponse = request.response;                             if (jsonResponse.hasOwnProperty("success") && jsonResponse.success) {                                 let message = "";                                 if (jsonResponse.hasOwnProperty("messages") && jsonResponse.messages) {                                     message = jsonResponse.messages.join("; ");                                 }                                 BX.UI.Dialogs.MessageBox.show(                                     {                                         message: "<div class=\"ui-alert ui-alert-success\">" +                                             "<span class=\"ui-alert-message\">" + message + "</span>" +                                             "</div>",                                         title: "Сохранение",                                         modal: true,                                         buttons: BX.UI.Dialogs.MessageBoxButtons.OK,                                         onOk: function (messageBox) {                                             messageBox.close();                                         }                                     }                                 );                             } else {                                 // Запрос выполнился, но там какие-то ошибки                                 BX.UI.Dialogs.MessageBox.show(                                     {                                         message: "<div class=\"ui-alert ui-alert-danger\">" +                                             "<span class=\"ui-alert-message\">В результате запроса получены ошибки</span>" +                                             "</div>",                                         title: "Сохранение",                                         modal: true,                                         buttons: BX.UI.Dialogs.MessageBoxButtons.OK,                                         onOk: function (messageBox) {                                             messageBox.close();                                         }                                     }                                 );                             }                         } else {                             // Запрос не выполнился                             BX.UI.Dialogs.MessageBox.show(                                 {                                     message: "<div class=\"ui-alert ui-alert-danger\">" +                                         "<span class=\"ui-alert-message\">Запрос не выполнился. " + request.status +                                         ": " + request.statusText + "</span>" + "</div>",                                     title: "Сохранение",                                     modal: true,                                     buttons: BX.UI.Dialogs.MessageBoxButtons.OK,                                     onOk: function (messageBox) {                                         messageBox.close();                                     }                                 }                             );                         }                     }                 }             }         };         BX.ready(function () {             "use strict";             window.captchaSettings.init();         });     </script> </form> <?php require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_admin.php';

По итогу этой статьи:

  • Разобрались, какие есть UI-расширения битрикса, зачем они нужны и как их подключать
  • Научились комбинировать блоки разметки, чтобы составить красивую страницу админки
  • Научились работать со страницей из JavaScript'а
  • Нучились сохранять данные после отправки их с фронта
  • Научились пользоваться встроенным битриксовым диалоговым окном

Комментарии

Популярные сообщения из этого блога

Пропорциональное распределение суммы

Битрикс: своя геолокация

Bitrix24 API - разбор демо приложения третьего типа