Просмотр файлов ui.viewer
Расширение ui.viewer открывает файл поверх страницы: изображение, видео, аудио, PDF, текстовый файл или файл с кодом. Разработчик размечает ссылку на файл набором data-атрибутов, а расширение само перехватывает клик по ней, определяет тип файла и показывает подходящий просмотрщик.
Используйте расширение, когда пользователь работает с файлами, которые уже лежат в файловом хранилище: вложения к записи, документы карточки, изображения галереи. Файл не нужно скачивать, чтобы посмотреть его содержимое, а несколько файлов одной записи можно листать стрелками.
Серверную разметку строит класс \Bitrix\Main\UI\Viewer\ItemAttributes модуля main, интерфейс просмотра — JavaScript-расширение ui.viewer модуля ui.
Подключить расширение
Загрузите ui.viewer на той странице, где выводятся ссылки на файлы.
\Bitrix\Main\UI\Extension::load('ui.viewer');
При загрузке расширение подписывается на клик по документу. Обработчик ищет атрибут data-viewer у элемента, по которому кликнул пользователь, и у семи его родителей. Отдельно назначать обработчик каждой ссылке не нужно.
Просмотрщик всегда открывается в верхнем окне. Если страница выводится в iframe — например, в боковой панели, — расширение догружает себя в верхнее окно и передает управление его экземпляру. Поэтому просмотр не обрезается границами фрейма.
Разметить ссылку на файл
Объект ItemAttributes собирает набор атрибутов для одного файла. Создают его статическими методами, а не через new: конструктор закрыт.
|
Метод |
Описание |
|
|
Читает данные файла из файлового хранилища по идентификатору. Если файла нет, выбрасывает |
|
|
То же, но вместо исключения для несуществующего файла возвращает объект с типом |
|
|
Берет готовый массив данных файла. Если в массиве нет ключа |
|
|
То же, но вместо исключения для пустого массива возвращает объект с типом |
|
|
Создает объект без данных файла. Просмотрщик покажет заглушку с кнопкой скачивания |
Параметр $sourceUri — адрес, по которому браузер получит сам файл. По этому же адресу просмотрщик запрашивает служебные данные, поэтому точка отдачи файла должна быть подготовлена, а не вести на произвольный обработчик.
Методы с приставкой try подходят там, где файл мог быть удален, а разметку нужно вывести в любом случае. Методы buildByFileId() и buildByFileData() подходят там, где отсутствие файла — ошибка данных и ее нужно увидеть.
Объект приводится к строке, поэтому его подставляют прямо в тег. Для одного файла тег может быть любым: ссылка, блок, изображение.
Пример. Ссылка открывает в просмотрщике файл с идентификатором 2208, который отдает страница /local/show-file.php.
<?php
use Bitrix\Main\UI\Extension;
use Bitrix\Main\UI\Viewer\ItemAttributes;
Extension::load('ui.viewer');
$attributes = ItemAttributes::tryBuildByFileId(2208, '/local/show-file.php?fileId=2208');
$attributes->setTitle('Спецификация оборудования.pdf');
?>
<a href="#" <?= $attributes ?>>Спецификация оборудования.pdf</a>
Метод setTitle() задает заголовок в шапке просмотрщика. Метод экранирует значение, поэтому передавать его через htmlspecialcharsbx() заранее не нужно.
При создании объект получает три атрибута:
data-viewer— метка для обработчика клика,data-viewer-type— тип просмотрщика,data-src— адрес файла.
Тип определяется по CONTENT_TYPE файла, а если он не распознан, дополнительно по расширению имени файла.
У изображения, которое меньше окна просмотра, появляется и четвертый атрибут data-viewer-resized. По нему просмотрщик показывает исходный файл и не запрашивает у сервера уменьшенную копию.
Если размеченный элемент не ссылка и лежит внутри ссылки с атрибутом target="_blank", просмотрщик не откроется. Обработчик пропускает такой клик и оставляет переход по внешней ссылке.
Подготовить точку отдачи файла
Просмотрщик обращается к адресу из data-src не только за содержимым файла. Сначала он запрашивает по этому адресу служебные данные: готовую разметку, уменьшенную копию изображения или состояние конвертации. Служебный запрос отличается заголовками, и разбирает его класс \Bitrix\Main\UI\Viewer\PreviewManager.
|
Заголовок или параметр запроса |
Что просит просмотрщик |
|
|
Готовый HTML для показа файла и данные для плеера |
|
|
Изображение, уменьшенное до размеров окна просмотра |
|
|
Адрес исходной страницы, от которого строятся ссылки внутри ответа. Сам по себе служебный ответ не запускает и работает вместе с другими заголовками |
|
|
Запуск конвертации файла заново, если предыдущая не дала результата |
|
|
Проверку, готова ли конвертированная копия |
|
|
Содержимое конвертированной копии файла |
|
|
Исходное изображение без уменьшения |
Разбирать эти заголовки самостоятельно не нужно: класс PreviewManager встроен в стандартную отдачу файла.
Отдавайте файл методом \CFile::viewByUser() или объектом \Bitrix\Main\Engine\Response\BFile. Если обработчик читает файл с диска сам, служебный запрос до PreviewManager не дойдет и просмотрщик покажет ошибку.
Метод \CFile::viewByUser() принимает массив данных файла и массив настроек отдачи.
Пример. Отдельная страница подключает ядро Bitrix Framework, проверяет права текущего пользователя и отдает файл вместе со служебными ответами просмотрщика.
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
use Bitrix\Main\Context;
$fileId = (int)Context::getCurrent()->getRequest()->get('fileId');
// здесь проверяем, можно ли текущему пользователю смотреть этот файл
$file = \CFile::getFileArray($fileId);
if (!$file)
{
\CHTTP::setStatus('404 Not Found');
die();
}
\CFile::viewByUser($file, ['cache_time' => 0]);
Передавайте в метод \CFile::viewByUser() массив данных файла, а не идентификатор. Служебный запрос просмотрщика распознается по ключу ID в этом массиве, и с одним идентификатором проверка не пройдет.
Основные ключи массива настроек.
-
cache_time— время кеширования ответа в секундах. По умолчанию10800. -
force_download— отдает файл на скачивание вместо показа в браузере. -
attachment_name— имя файла в ответе, если оно должно отличаться от исходного. -
content_type— тип содержимого, если он должен отличаться от записанного в файловом хранилище.
Второй способ — вернуть объект \Bitrix\Main\Engine\Response\BFile из действия контроллера. Объект сам читает данные файла и сам вызывает отдачу.
Пример. Действие контроллера отдает файл для просмотра в браузере.
<?php
namespace Vendor\Catalog\Controller;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\Response\BFile;
use Bitrix\Main\Error;
class Document extends Controller
{
public function viewAction(int $fileId): ?BFile
{
// проверяем права текущего пользователя кодом своего модуля
if (!$this->isViewAllowed($fileId))
{
$this->addError(new Error('Access denied'));
return null;
}
return BFile::createByFileId($fileId)->showInline(true);
}
}
Метод showInline(true) отдает файл для показа в браузере. Без него ответ уходит с заголовком на скачивание.
Права доступа просмотрщик не проверяет: он только запрашивает файл по тому же адресу, который уже стоит в разметке. Проверку выполняет точка отдачи файла, и она же решает, показывать файл или ответить ошибкой.
Листать несколько файлов
Файлы одной записи объединяют в группу. Просмотрщик покажет стрелки для перехода между файлами, а если хотя бы у одного файла группы задан адрес миниатюры — еще и ленту миниатюр внизу окна.
|
Метод |
Описание |
|
|
Задает идентификатор группы. Метод экранирует значение |
|
|
Возвращает идентификатор группы или |
|
|
Убирает файл из группы |
Идентификатор группы должен быть уникальным на странице. Когда пользователь кликает по файлу, расширение ищет в том же документе все элементы с атрибутом data-viewer и таким же значением data-viewer-group-by, собирает из них список и открывает просмотр с того файла, по которому был клик.
Пример. Три вложения одной записи открываются как один список: пользователь листает их стрелками, не закрывая просмотрщик.
<?php
use Bitrix\Main\UI\Viewer\ItemAttributes;
$groupId = 'catalog-document-42';
foreach ($documents as $document)
{
$attributes =
ItemAttributes::tryBuildByFileId($document['FILE_ID'], $document['URL'])
->setTitle($document['NAME'])
->setGroupBy($groupId)
;
echo '<a href="#" ' . $attributes . '>' . htmlspecialcharsbx($document['NAME']) . '</a>';
}
Внутри группы работают клавиши: стрелка вправо и пробел показывают следующий файл, стрелка влево — предыдущий, клавиша Esc закрывает просмотрщик.
По умолчанию список замкнут в кольцо: за последним файлом снова идет первый. Чтобы листание останавливалось на краях, выключите кольцо для группы методом setOptionsByGroup(). Тогда на первом и последнем файле стрелка скрывается.
Пример. Группа листается без зацикливания.
BX.UI.Viewer.Instance.setOptionsByGroup('catalog-document-42', { cycleMode: false });
Дополнить разметку файла
Кроме заголовка и группы объект принимает необязательные настройки. Они меняют то, как просмотрщик показывает файл и какие кнопки выводит в шапке.
|
Метод |
Описание |
|
|
Переопределяет тип просмотрщика, выбранный по |
|
|
Задает имя JavaScript-класса, который покажет файл. Нужно для своего типа файла |
|
|
Задает расширение, которое просмотрщик загрузит перед показом файла |
|
|
Передает готовый список источников видео. Каждый элемент — массив с ключами |
|
|
Задают размеры окна видео. Работают вместе со списком источников |
|
|
Добавляет произвольный атрибут |
|
|
Удаляет атрибут |
|
|
Возвращает значение атрибута или |
|
|
Возвращает тип просмотрщика, выбранный для файла |
Часть возможностей просмотрщика включают атрибуты, для которых отдельного метода нет. Их задают методом setAttribute().
-
data-viewer-preview— адрес миниатюры для ленты в нижней части окна. Без него просмотрщик берет миниатюру из атрибутаdata-thumb-srcили, для изображения, из самого тега. -
data-unified-link— постоянная ссылка на файл. Когда атрибут задан, в шапке появляется кнопка копирования ссылки. -
data-viewer-separate-item— метка файла, который показывают без окна просмотра. Значение должно быть непустым: пустая строка в JavaScript ложна, и режим не включится. Как этим пользоваться, описано в разделе Добавить свой тип файла.
Пример. Видео открывается с готовым источником и заданным размером окна, а в шапке появляется кнопка копирования ссылки.
<?php
use Bitrix\Main\UI\Viewer\ItemAttributes;
$attributes =
ItemAttributes::tryBuildByFileId($fileId, $videoUrl)
->setTitle('Инструкция по сборке.mp4')
->setSources([
['src' => $videoUrl, 'type' => 'video/mp4'],
])
->setWidth(1280)
->setHeight(720)
->setAttribute('data-unified-link', $permanentUrl)
;
Для разметки, которую собирает не PHP-шаблон, объект отдает атрибуты массивом.
-
toDataSet()— массив для свойстваdatasetDOM-элемента: имена атрибутов приведены к видуviewerGroupBy. -
toVueBind()— массив для привязки атрибутов во Vue: имена оставлены как есть. -
toString()— строка с атрибутами, тот же результат, что и при подстановке объекта в шаблон.
Добавить действия в меню
Кнопка дополнительных действий в шапке просмотрщика открывает список того, что можно сделать с файлом. Наполняет его метод addAction(): объект накапливает вызовы и отдает их в разметку одним атрибутом data-actions. Пока у файла нет ни одного действия, эта кнопка скрыта.
|
Ключ |
Описание |
|
|
Тип действия. Для встроенных типов заполняет текст пункта меню |
|
|
Идентификатор действия, по которому его запускают из кода. Если не задан, берется значение ключа |
|
|
Текст пункта меню. Заменяет текст встроенного типа |
|
|
Обработчик: имя JavaScript-функции строкой. Функция получает объект файла, массив |
|
|
Адрес перехода. Используется, если обработчик не задан |
|
|
Массив, который придет в обработчик вторым аргументом |
|
|
Расширение с обработчиком. Просмотрщик загрузит его перед показом файла. Можно передать массив расширений |
|
|
CSS-класс пункта меню |
|
|
Пункты выпадающего меню вместо одного действия |
|
|
Блокирует пункт меню, пока файл не загрузился |
У шести типов действий текст уже задан:
download— скачивание,edit— редактирование,share— публикация,print— печать,info— сведения о файле,delete— удаление.
Ключ, заданный в самом действии, важнее значения по умолчанию, поэтому текст встроенного типа можно заменить. Свое значение ключа type создает действие, у которого текст задает разработчик.
Действие с типом download в меню не попадает: адрес из ключа href становится ссылкой кнопки скачивания в шапке. Само действие добавлять не обязательно — без него кнопка скачивания ведет на адрес из data-src.
Для PDF действие print просмотрщик добавляет сам: класс BX.UI.Viewer.Document ставит печать перед действием info, а если действия info нет — в конец списка.
Пример. В меню просмотрщика появляется действие, которое удаляет вложение.
<?php
use Bitrix\Main\UI\Viewer\ItemAttributes;
$attributes =
ItemAttributes::tryBuildByFileId($fileId, $fileUrl)
->setTitle('Спецификация оборудования.pdf')
->addAction([
'type' => 'delete',
'action' => 'BX.Catalog.Viewer.Actions.remove',
'params' => [
'documentId' => $documentId,
],
'extension' => 'catalog.viewer.actions',
])
;
Обработчик объявляют в расширении, которое указано в ключе extension. К моменту клика по кнопке функция уже объявлена.
Пример. Обработчик получает объект файла и параметры из ключа params.
BX.namespace('BX.Catalog.Viewer.Actions');
BX.Catalog.Viewer.Actions.remove = function(item, params) {
BX.ajax.runAction('catalog.document.delete', {
data: { documentId: params.documentId },
}).then(function() {
BX.UI.Viewer.Instance.close();
});
};
Метод clearActions() убирает все накопленные действия, метод getActions() возвращает текущий список.
Форматы файлов и конвертация
Тип просмотрщика выбирает класс PreviewManager по CONTENT_TYPE файла. Если тип не совпал ни с одним из встроенных классов, менеджер повторяет поиск по расширению имени файла.
|
Тип |
Что открывает |
|
|
Изображения |
|
|
Видео |
|
|
Аудио |
|
|
Файлы |
|
|
Текстовые файлы и файлы с кодом: |
|
|
Остальные файлы. Просмотрщик показывает заглушку с кнопкой скачивания |
Офисные документы просмотрщик открывает через конвертацию в PDF. Конвертацию выполняет модуль transformer: если он установлен, файл неизвестного типа уходит на преобразование, а просмотрщик ждет результат и показывает готовую копию. Пока конвертация идет, в окне выводится сообщение о ходе работы.
|
Что конвертируется |
Во что |
Предельный размер исходного файла |
|
Документы |
|
|
|
Видео |
MP4 |
|
Как только сервер сообщил, что конвертация запущена, просмотрщик начинает ждать готовую копию. Если в браузере установлено соединение Push and Pull, о завершении приходит уведомление. Без соединения просмотрщик переспрашивает сервер каждые 5 секунд. Ожидание ограничено 22 секундами: после этого в окне выводится сообщение о неудачной конвертации со ссылкой на скачивание файла.
Конвертацию можно запустить заранее, не дожидаясь, пока пользователь откроет файл. Метод generatePreview() класса PreviewManager ставит файл в очередь и возвращает объект Result с ошибками постановки. Без модуля transformer метод возвращает null.
Пример. Модуль отправляет документ на конвертацию сразу после загрузки, чтобы первый просмотр не ждал преобразования.
<?php
use Bitrix\Main\UI\Viewer\PreviewManager;
$result = (new PreviewManager())->generatePreview($fileId);
if ($result !== null && !$result->isSuccess())
{
// здесь записываем ошибки постановки в очередь в журнал
}
Без модуля transformer файл, для которого нет своего класса, так и остается типом unknown.
При любой другой ошибке загрузки просмотрщик показывает вместо содержимого файла блок с описанием ошибки, а перед этим вызывает событие BX.UI.Viewer.Controller:onItemError.
Управлять просмотрщиком из JavaScript
Экземпляр просмотрщика доступен как BX.UI.Viewer.Instance. Он создается при первом обращении и существует до перезагрузки страницы. Внутри iframe свойство возвращает экземпляр верхнего окна, поэтому обращаться к нему из фрейма можно так же, как из основной страницы.
|
Метод |
Описание |
|
|
Открывает просмотр по размеченному элементу. Группу и список файлов собирает так же, как обработчик клика |
|
|
Запускает действие для файла, не открывая окно просмотра |
|
|
Заменяет список файлов. Возвращает промис: он выполняется, когда загружены расширения, нужные действиям |
|
|
Открывает окно и показывает файл с указанным номером в списке |
|
|
Переходят к следующему и предыдущему файлу |
|
|
Закрывает окно просмотра |
|
|
Возвращает |
|
|
Возвращает объект файла, который показан сейчас |
|
|
Пересобирает показанный файл по его элементу разметки и выводит заново. Для видео ключ |
Отдельный объект файла строит функция BX.UI.Viewer.buildItemByNode(node): она читает data-атрибуты элемента и возвращает объект нужного класса.
Пример. Кнопка открывает просмотр файла, размеченного ссылкой с идентификатором documentLink.
document.getElementById('openDocument').addEventListener('click', function() {
BX.UI.Viewer.Instance.openByNode(document.getElementById('documentLink'));
});
Встроенное действие edit содержимое окна не обновляет: после того как файл сохранен, перерисуйте его сами.
Метод reloadCurrentItem() читает атрибуты исходного элемента заново и запрашивает файл у сервера. Проверка isOpen() нужна потому, что пользователь мог закрыть окно, пока шло редактирование.
Пример. Обработчик действия edit обновляет файл в окне просмотра после сохранения.
BX.Catalog.Viewer.Actions.edit = function(item, params) {
BX.ajax.runAction('catalog.document.update', {
data: { documentId: params.documentId },
}).then(function() {
if (BX.UI.Viewer.Instance.isOpen())
{
BX.UI.Viewer.Instance.reloadCurrentItem();
}
});
};
Если размеченные элементы появляются на странице после загрузки — например, их дорисовывает список, — привяжите к контейнеру делегированный обработчик функцией BX.UI.Viewer.bind(container, filter). Первый аргумент — контейнер, второй — необязательный фильтр элементов. Без фильтра обработчик реагирует на элементы с атрибутом data-viewer.
Просмотрщик вызывает события на объекте BX во время работы.
|
Событие |
Когда вызывается |
|
|
При замене списка файлов. Параметры: просмотрщик и новый список |
|
|
Перед показом файла. Параметры: просмотрщик и номер файла в списке |
|
|
После показа файла. Параметры: просмотрщик и объект файла |
|
|
При ошибке загрузки файла. Параметры: просмотрщик, причина и объект файла |
|
|
При закрытии окна просмотра. Параметр: просмотрщик |
|
|
Перед поиском класса по коду типа. Параметры: просмотрщик и код типа |
Расширение шлет эти события механизмом BX.onCustomEvent, поэтому подписываются на них функцией BX.addCustomEvent. Обработчик получает параметры по отдельности, а не одним объектом события: подписка через EventEmitter, принятая в других расширениях ui, здесь не сработает.
Пример. Обработчик читает заголовок файла, который просмотрщик только что показал.
BX.addCustomEvent('BX.UI.Viewer.Controller:onAfterShow', function(controller, item) {
console.log(item.getTitle());
});
Встроить просмотр документа в страницу
Класс BX.UI.Viewer.SingleDocumentController показывает один PDF прямо в блоке страницы, без окна поверх нее. Под документом выводятся счетчик страниц и кнопки масштаба.
|
Параметр или метод |
Описание |
|
|
Элемент, внутрь которого просмотрщик выводит документ |
|
|
Растягивает документ по ширине контейнера |
|
|
Задает источник документа: адрес или содержимое файла |
|
|
Задает масштаб. Значение |
|
|
Отправляет документ на печать |
Пример. Документ выводится в блоке страницы с масштабом больше исходного, а кнопка отправляет его на печать.
const container = document.getElementById('documentContainer');
const link = document.getElementById('documentLink');
const viewer = new BX.UI.Viewer.SingleDocumentController({
baseContainer: container,
stretch: true,
});
viewer.setItems([BX.UI.Viewer.buildItemByNode(link)]);
viewer.setScale(1.2);
viewer.open();
document.getElementById('printDocument').addEventListener('click', function() {
viewer.print();
});
Для остальных типов файлов встроенный показ дает базовый класс BX.UI.Viewer.InlineController. Он выводит файл в переданный контейнер и не меняет страницу вокруг: не добавляет отступ телу страницы, не меняет масштаб окна на мобильном устройстве и не перехватывает клавиши.
Добавить свой тип файла
Свой тип нужен, когда встроенные классы формат не открывают или когда файл показывают по-особому: в редакторе, на доске, во внешнем сервисе.
Серверную часть добавляют классом-наследником \Bitrix\Main\UI\Viewer\Renderer\Renderer. Класс объявляет типы содержимого, которые он берет на себя, код типа для JavaScript и разметку.
|
Метод |
Описание |
|
|
Возвращает массив значений |
|
|
Возвращает код типа. Он попадает в атрибут |
|
|
Возвращает предельный размер файла в байтах. Файл крупнее откроется как неизвестный. По умолчанию |
|
|
Возвращает разметку для окна просмотра. Если разметка не нужна, возвращает |
|
|
Возвращает дополнительные данные для JavaScript-класса |
Готовый класс регистрируют на событии onPreviewRendererBuildList модуля main. Обработчик события возвращает объект EventResult с типом SUCCESS и массивом классов, и просмотрщик проверяет эти классы раньше встроенных. Результат с другим типом менеджер пропускает, поэтому вернуть просто массив недостаточно.
Пример. Модуль добавляет свой класс для файлов диаграмм.
<?php
use Bitrix\Main\EventManager;
use Bitrix\Main\EventResult;
use Vendor\Catalog\Viewer\Diagram;
EventManager::getInstance()->addEventHandler(
'main',
'onPreviewRendererBuildList',
static fn() => new EventResult(EventResult::SUCCESS, [Diagram::class])
);
Клиентскую часть добавляют классом-наследником BX.UI.Viewer.Item и связывают с кодом типа функцией BX.UI.Viewer.addType(type, className).
Пример. Расширение связывает код типа diagram со своим классом.
BX.UI.Viewer.addType('diagram', 'BX.Catalog.Viewer.DiagramItem');
Второй способ связать разметку с классом — атрибут data-viewer-type-class с полным именем класса. Его задают методом setTypeClass(), а расширение с классом — методом setExtension(). Способ подходит, когда класс нужен не для всего типа содержимого, а для отдельных файлов.
Если в классе переопределить метод load() и пометить файл атрибутом data-viewer-separate-item с непустым значением, просмотрщик окно не откроет, а только вызовет этот метод. Так открывают файлы во внешнем редакторе или на отдельной странице. Просмотрщик обрабатывает такой файл в одиночку, даже если у него задана группа.
Связанные материалы
-
Загрузка файлов Uploader — прием файлов от браузера и сохранение их в файловое хранилище.
-
Боковая панель main.sidepanel — вывод страниц во фрейме поверх текущей.
-
Расширения — подключение JavaScript-расширений Bitrix Framework.
-
События — регистрация обработчиков событий модулей.