CSS / JS Кастомизация

Урок 3 из 7

50 мин

Нормально

Немного теории

Есть ряд задач, которые решаются через кастомизацию на css/js. Чаще всего это добавление новых элементов на страницу, но иногда и модификация стандартных возможностей платформы.

Такие кастомизации добавляются на события загрузки страницы (onProlog, onEpilog) посредством использования Bitrix\Main\Page\Asset.

addJs, addCss - принимают аргументом путь, относительно корня сайта, к подключаемому файлу.

addString - принимает аргументом строку, которая содержит произвольный скрипт. Такой подход используется чаще, так как метод позволяет вставить js/css в несколько фиксированных мест страницы, в отличие от addJs/addCss.

Доступные места описаны в классе Bitrix\Main\Page\AssetLocation в виде констант:

  • до/после css;
  • после подключения kernel js;
  • после подключения js, если быть точнее, после подключения шаблона страницы;,
  • в конце страницы.

Кастомизировать страницы посредством js можно произвольными скриптами или через JS-расширения.

Если требуется модифицировать что-то небольшое и настолько специфическое, что вы не представляете, как скрипт мог бы быть переиспользован кем-то, то нет смысла создавать js-расширение для этого.

Расширения нужны для всего остального. Они могут находиться в bitrix/js/<module>/<extension> или bitrix/modules/js/<module>/<extension>, но это устаревший подход или применимый для модулей размещаемых в Маркете. Правильно - в local/js/<vendor>/<module>/<extension>.

Состав расширения:

  • src - исходные файлы;
  • dist - бандлы для браузера;
  • bundle.config.js - конфигурационный файл для сборщика;
  • config.php - конфигурационный файл расширения.

Если установлен @bitrix/cli, то структуру расширения можно создать, выполнив команду bitrix create.

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

Подключить JS-расширение можно в двух местах, в зависимости от того, кто будет его использовать -

php:

  • \Bitrix\Main\UI\Extension::load(‘<extension>’).
  • CJSCore::Init(‘<extension>’).

js:

  • import {class} from '<extension>’.

JS-расширение не обязательно должно содержать в себе полный описанный набор файлов и папок. Это может быть всего один файл, зарегистрированный как расширение. Регистрировать лучше всего в файле модуля include.php, так регистрация будет находиться в пределах вашего модуля и также данный файл автоматически будет вызван при подгрузке модуля, следовательно расширение станет доступно везде, где подключен сам модуль.

  • CJSCore::RegisterExt.
  • \Bitrix\Main\UI\Extension::registerAssets.

Если вы будете дорабатывать портал, используя js-скрипт, то он должен располагаться внутри BX.ready. Это позволит запуститься вашему коду, только когда DOM-структура будет полностью загружена и сформирована.

Следует учитывать, что ошибки при исполнении скрипта могут привести к полной неработоспособности всего JS на странице.

Кроме скриптов, исполняемых сразу при открытии страницы, может потребоваться выполнить что-то в определенный момент времени. Для этого во фреймворке реализованы js-события.

Чтобы подписаться, нужно использовать BX.addCustomEvent. В документации описано, как получить список доступных событий и как подписаться на существующее событие.

(function () {
    BX.ready(function () {
        const sendRemoteBatch = BX.namespace('module.example.sendRemoteBatch.Contact');

        sendRemoteBatch.init = function () {
            BX.addCustomEvent('Grid::ready', BX.proxy(this.insertAction, this));
            BX.addCustomEvent('Grid::updated', BX.proxy(this.insertAction, this));
        }

        sendRemoteBatch.insertAction = function (grid) {
            if (grid.getContainerId() !== 'CRM_CONTACT_LIST_V12') {
                return;
            }

            const actionDropdown = grid.getActionsPanel().getDropdowns()[0];
            if (!actionDropdown) {
                return;
            }

            let items = BX.parseJSON(BX.data(actionDropdown, 'items'));
            if (!BX.type.isArray(items)) {
                return;
            }

            if (items.find(i => i.VALUE === 'send_to_remote_system')) {
                return;
            }

            items.push({
                NAME: 'Send to Remote System',
                VALUE: 'send_to_remote_system',
                ONCHANGE: [
                    {
                        ACTION: 'CALLBACK',
                        DATA: [
                            {JS: 'BX.module.example.sendRemoteBatch.Contact.process()'}
                        ]
                    }
                ]
            });
            actionDropdown.dataset.items = JSON.stringify(items);
        }

Например, подписавшись на события Grid::ready и Grid::updated, можно управлять групповыми действиями грида. В параметрах этих событий можно найти панель ActionsPanel и все ее элементы. На картинке пример добавления своего действия к списку контактов Send to Remote System.

Помимо подписки на существующие события, можно и создавать свои через BX.onCustomEvent.

Для примера выше требуется реализовать функциональность отправки контакта во внешнюю систему. Для этого потребуется написать ajax-контроллер. В документации описано, как создать свой контроллер и как к нему обратиться из js-скрипта. Контроллеры нужны везде, где необходимо взаимодействие с бэкендом.

Мы уже сказали об использовании Asset::addString, как о способе добавить свои скрипты на страницу. Но помимо скриптов, вам может понадобиться вывести компонент или просто верстку чего-то. Это тоже можно реализовать через addString, если использовать буферизацию.

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

Пример:

ob_start();
$APPLICATION->IncludeComponent(
'module:component.name',
'.default',
array(
'TITLE' => 'Some title parameter',
)
);

$content = ob_get_contents();
ob_clean();
Asset::getInstance()->addString($content);

При кастомизации системы можно не только писать свои скрипты, но и использовать JS-расширения платформы. Например, можно реализовать свое всплывающее окно (pop-up). Для этого может пригодиться диалоговое окно или оконная библиотека. Внутри такого окна можно расположить что угодно. К примеру, это может быть форма с полями ввода или статический текст.

Практика

Типовые задачи, решаемые js/css кастомизацией

Изменение дизайна портала на корпоративные цвета, добавление новых элементов управления в различные места, не имеющие подходящих точек расширения (кнопок, ссылок, ...).

Пример: изменение цвета кнопки

Для начала рассмотрим очень простой пример: необходимо изменить цвет кнопки «Создать» на красный.

картинка

Аналогичным способом вы сможете решить различные задачи, связанные с изменением визуальной составляющей шаблона портала, не трогая ядро продукта.

Наш пример проще всего решить с помощью переопределения CSS стилей. Для этого:

  • Определим элемент HTML для модификации.
  • Создадим стиль и присвоим его нужному элементу по id или классу стилей.
  • Зарегистрируем обработчика на событие OnEpilog, который будет вызывать Bitrix\Main\Page\Asset->AddCss() для нужных страниц.

Изменить цвет кнопки

9 мин

Пример посложнее: всплывающее окно со списком элементов

Задача – добавить в левое меню кнопку «Факты компании». При нажатии на кнопку должно открываться всплывающее окно с набором фактов о компании. Сам набор должен задаваться администратором портала в административном разделе портала без участия программистов.

картинка

Для решения задачи уже потребуется больше работы:

  • Определить, к какому HTML-элементу можно привязаться для добавления кастомной кнопки для отображения фактов.
  • Организовать "админку" для управления списком фактов, например, инфоблок.
  • На обработчике события OnEpilog реализовать добавление кнопки, по клику на которую будет вызываться наше js-расширение.
  • Создать js-расширение, которое будет показывать факт в pop-up окне.
  • Создать контроллер, который будет возвращать содержимое для pop-up из компонента.
  • Создать компонент, который будет возвращать факт из инфоблока и его оформление в HTML.
  • Не забыть создать миграцию для переноса доработки на production :).

Пункт в меню и всплывающее окно со списком элементов

29 мин

Список ресурсов

Материалы для выполнения практики

  • Исходный код примера (.zip)