Всем привет.

Если вы когда-либо работали с универсальными списками в Битрикс24, то, наверное, в курсе, что страница детального просмотра элемента полностью идентична странице редактирования. Единственное отличие — если у пользователя права только на чтение, то на странице не будет кнопок «Сохранить» и «Применить». Согласитесь, не самый приятный интерфейс.



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

ВАЖНОЕ ПРЕДУПРЕЖДЕНИЕ

Создатели Битрикс24 крайне не рекомендуют менять интерфейс корпоративного портала, поскольку в коробке публичная часть приравнивается к ядру, и при обновлении системы есть риск, что все ваши модификации будут стёрты.

Если же вы решите скопировать нужные шаблоны в папку local и издеваться над ними как хотите, то при обновлении они затронуты не будут. Но и новые фичи к ним тоже не будут применены.

Поэтому единственно верный способ в данном случае — это модификация DOM-дерева через Javascript.

По сути нам нужно всего-то подменить ссылку на детальную страницу в таблице списка:



Однако на деле это не так просто реализовать, т.к. нужно лезть в компонент, отвечающий за вывод универсальных списков и править ссылку там.

Поэтому мы пойдём иным путём — через Javascript будем открывать страницу в слайдере, используя битриксовую библиотеку SidePanel.

Сделать это можно двумя способами — в init.php и своём модуле. Также необходимо зарегистрировать свою JS-библиотеку.

И хотя второй способ более удобен, я покажу вам именно первый, а в конце статьи дам ссылку на свой модуль.

Итак, поехали. Все действия нужно выполнять в папке local.

Для начала нужно создать отдельную папку, где будет храниться наша библиотека. Назовём её, к примеру, viewer, и будет она иметь следующую структуру:

/viewer 
-/js
--viewer.js // наша js-библиотека
-include.php // файл, который мы будем подключать в init.php

Здесь немного остановимся. Для php-кода я создал отдельный файл, который потом подключу в init.php, чтобы не засорять последний.

Давайте теперь зарегистрируем нашу библиотеку с помощью метода старого ядра CJSCore::RegisterExt:

// include.php

// т.к. в битриксе есть js-библиотека viewer, то нужно использовать другое название
CJSCore::RegisterExt('elementviewer', [ 
    'js' => '/local/viewer/js/viewer.js', // путь к библиотеке
    'rel' => ['SidePanel'] // слайдер
]);

Осталось только подключить данную библиотеку на странице универсальных списков методом CJSCore::Init, и, казалось бы, дело в шляпе — можно приступать к написанию самой библиотеки.

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

// include.php

$pattern = '/\/lists\/(\d+)\/view\//'; // часть адреса страницы универсального списка, где (\d+) = id списка
$server = Bitrix\Main\Context::getCurrent()->getServer(); // объект Server, потребуется для получения адреса страницы

if(preg_match($pattern, $server->getRequestUri())) {
       CJSCore::Init(['elementviewer']); // подключаем библиотеку
}

Итак, библиотеку подключили, осталось её написать. Для этого создаём файл viewer.js (если ранее не создали) и первым делом объявляем неймспейс с помощью функции BX.namespace:

const ElementViewer = BX.namespace('Viewer');

Теперь все переменные и функции можно объявлять следующим способом:

ElementViewer.init = function() {

}

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

Пе��вым делом нам необходимо найти на странице узел, содержащий ссылку на детальную страницу. Для этого воспользуемся функцией BX.findChildren, которая должна вернуть нам список всех объектов, содержащих ссылки на детальную страницу:

ElementViewer.findCell = function () {
    return BX.findChildren(document, {
        class: 'main-grid-cell-content' // css-класс узла с искомой ссылкой
    }, true);
}

Заодно напишем функцию, которая будет извлекать id списка и элемента из ссылки для дальнейшей работы:

ElementViewer.pattern = '/lists/(\\d+)/element/0/(\\d+)'; // часть адреса страницы детального просмотра элемента универсального списка, где первый шаблон = id списка, а второй = id элемента.

ElementViewer.extractListData = function (url) {
    let match = url.match(this.pattern); // проверяем ссылку регуляркой
    if(match) {
        return {
            list_id: Number(match[1]),
            element_id: Number(match[2])
        };
    }
}

Вернёмся к BX.findChildren. Особенность данной функции в том, что она возвращает список всех объектов с указанным css-классом, и не факт, что это будет ссылка. Поэтому нам нужно выполнить проверку, и уже только после этого отменять событие открытия ссылки и открывать слайдер:

ElementViewer.init = function (sliderUrl) {

    const cell = this.findCell();

    cell.forEach(item => {

        let itemChild = item.children;
        let child = itemChild[0];

        if(child && child.tagName === 'A') {
            const listData = this.extractListData(child.toString()); // получаем id списка и элемента из ссылки

            if(listData !== undefined) {
                child.addEventListener('click', (e) => {
                    e.preventDefault(); // отменяем переход по ссылке
                    this.openSlider(sliderUrl, listData.list_id, listData.element_id); // открываем слайдер
                })
            }
        }
    });

}

Нам осталось написать последнюю функцию, которая будет открывать слайдер. Для этого задействуем библиотеку SidePanel:

ElementViewer.openSlider = function (sliderUri, listId, elementId) {

// в слайдер можно передавать данные методом POST, поэтому воспользуемся этой возможностью для передачи id списка и элемента
    let sliderParams = {
        list_id: listId,
        element_id: elementId
    }

    return BX.SidePanel.Instance.open(sliderUri, {
        allowChangeHistory: false,
        cacheable: false,
        requestMethod: 'POST',
        requestParams: sliderParams
    });
}

Ну что же, библиотека написана, осталось вызвать функцию init после подключения. Для этого вернёмся в include.php, где проверяется адрес страницы:

if(preg_match($pattern, $server->getRequestUri())) {
       CJSCore::Init(['elementviewer']); // подключаем библиотеку
       $asset = Bitrix\Main\Page\Asset::getInstance();
       $script = '<script>BX.ready(function() {
  ElementViewer.init();
})</script>';
      $asset->addString($script);
}

Остался последний штрих — подключить наш код в init.php:

// init.php

$file = $_SERVER['DOCUMENT_ROOT'] . '/local/path/to/viewer/include.php';

if(file_exists($file)) {
   require $file;
}

Если всё сделано правильно, то при нажатии на элемент универсального списка откроется слайдер:





В заключении, как и обещал, ссылка на модуль, реализующий то же самое.

Спасибо за внимание.