10 KiB
Гайд: как встроить конструктор диаграмм в поле Bitrix
Короткая практическая версия — только три вопроса: куда положить билд, как собрать HTML и куда писать начальное значение. Про полноценный переиспользуемый тип свойства инфоблока (регистрация, ConvertToDB и т.п.) — отдельный документ, docs/bitrix-integration.md. Если поле одно и на одной странице — этого гайда достаточно.
1. Куда положить билд конструктора
Приложение — статическая сборка (npm run build → dist/), которая раздаётся тем же nginx, что и сам портал. Ничего разворачивать отдельно не нужно.
- Собрать в репозитории
portal-chart-builder:npm run build- Переменные окружения не нужны. Ссылки на JS/CSS внутри
index.htmlотносительные (base: './'вvite.config.ts), поэтому сборка работает из любого каталога, куда её положат, — подпуть на этапе сборки знать не требуется. VITE_BITRIX_ORIGINздесь не нужен: приложение и портал на одном домене, origin хоста приложение берёт изwindow.location.originв рантайме. Задавать переменную нужно только если хост на другом origin — например, локальная разработка (dev-сервер хоста иvite previewэтого приложения на разных портах) или деплой отдельным контейнером (см.bitrix-integration.md, раздел 7, вариант B).
- Переменные окружения не нужны. Ссылки на JS/CSS внутри
- Скопировать содержимое
dist/вlocal/diagram-app/внутри каталога сайта на сервере (тот же каталог, где лежит остальной код портала —bitrix_www/аналог на проде). - Готово — но с оговоркой: приложение доступно не по фиксированному
index.html.
Имя index.html в dist/ — с хешем содержимого, например index-2fddeca6.html, а не просто index.html (см. vite.config.ts, плагин hashIndexHtml — хеширует и сам HTML-файл, не только JS/CSS, которые Vite и так хеширует по умолчанию). Хеш меняется при каждой значимой пересборке и не меняется, если содержимое не менялось. Значит:
srcу iframe нельзя хардкодить строкой — нужно на своей стороне подставлять актуальное имя файла. Готовый способ: рядом с билдом сборка кладётapp-manifest.jsсо стабильным именем и актуальным именем точки входа внутри —{ "index": "index-2fddeca6.html" }. Прочитать его можно и с бэкенда, и прямо из браузера (fetch('app-manifest.js', { cache: 'no-store' })). Альтернативы — взять единственныйindex-*.htmlв каталоге или отдать имя отдельным методом; выбор за бэкендом.- Ручной cache-busting (
?v=...) для JS/CSS/HTML больше не нужен: раз URL меняется вместе с содержимым, старый закэшированный файл просто никогда больше не запрашивается, как только известно новое имя. - После каждого редеплоя старые
index-<hash>.html/index-<hash>.js/index-<hash>.cssот прошлых версий стоит убирать изlocal/diagram-app/— они не удаляются сами, и если способ поиска актуального файла — «взять единственный файл по маске», их накопление сломает эту логику.
Если схему нужно только показать, без правки, — добавьте к src iframe ?mode=view: редактор откроется в режиме просмотра, переключатель режимов будет скрыт (параметр дописывается к уже вычисленному актуальному имени файла).
2. Как сформировать HTML
Три части: скрытое поле со значением, iframe с приложением, скрипт-мост между ними.
<input type="hidden" id="DIAGRAM-DATA" name="DIAGRAM-DATA" value="<?= htmlspecialcharsbx($currentValue) ?>">
<iframe id="diagram-frame" src="/local/diagram-app/<?= htmlspecialcharsbx($actualIndexFileName) ?>" style="width:100%;height:600px;border:1px solid #d7dae0;border-radius:6px"></iframe>
<!-- $actualIndexFileName — актуальное имя index-<hash>.html, см. раздел 1 -->
<script>
(function () {
var APP_ORIGIN = window.location.origin; // приложение и портал на одном домене
var input = document.getElementById('DIAGRAM-DATA');
var frame = document.getElementById('diagram-frame');
window.addEventListener('message', function (event) {
if (event.origin !== APP_ORIGIN || event.source !== frame.contentWindow) return;
if (!event.data || typeof event.data !== 'object') return;
if (event.data.type === 'diagram:ready') {
frame.contentWindow.postMessage({ type: 'diagram:init', payload: input.value || null }, APP_ORIGIN);
} else if (event.data.type === 'diagram:change') {
input.value = event.data.payload;
}
});
})();
</script>
Как это работает:
- iframe грузит приложение → оно шлёт мосту
{ type: 'diagram:ready' }. - Мост в ответ шлёт
{ type: 'diagram:init', payload: input.value }— приложение рисует диаграмму из этого значения (или пустой холст, еслиpayloadпуст). - Каждая правка в редакторе прилетает мосту как
{ type: 'diagram:change', payload: '<JSON-строка>' }— мост тут же кладёт её вinput.value. - Дальше это обычный
<input>внутри формы: если у него естьid,nameи он лежит в<form>— браузер сам отправит его значение при обычном сабмите формы. Никакого дополнительного JS для сохранения не нужно.
id/name = "DIAGRAM-DATA" захардкожены — это ок, если поле на странице ровно одно. Если полей несколько (например, полноценный переиспользуемый тип свойства, который может стоять на разных элементах) — name должен быть уникальным и совпадать с тем, что ожидает обработчик сохранения на бэкенде (в терминах Bitrix custom property type — $strHTMLControlName['VALUE'], см. docs/bitrix-integration.md, раздел 3).
Обязательно к соблюдению, не опционально:
event.originпроверяется у входящих сообщений (уже в сниппете выше).targetOriginуpostMessage— всегда конкретный домен (APP_ORIGIN), никогда'*'.- Значение попадает на страницу только через
value-атрибут (htmlspecialcharsbx()), не через встраивание в JS-код — так не нужно никакого JSON-специфичного экранирования, обычногоhtmlspecialcharsbx()достаточно.
3. Куда записывать начальное значение
value="<?= htmlspecialcharsbx($currentValue) ?>" — сюда подставляется то, что уже сохранено для этого элемента/записи (JSON-строка со схемой диаграммы) или пустая строка, если диаграммы ещё нет.
Откуда взять $currentValue — зависит от того, где именно хранится значение на вашей стороне (поле БД, свойство инфоблока, опция и т.п.) — это единственное, что специфично для конкретной интеграции, дальше всё стандартно:
$currentValue = /* ваш способ прочитать сохранённое значение, например: */
$arElement['PROPERTY_DIAGRAM_VALUE'] ?? '';
Требований к содержимому со стороны бэкенда — минимум: это либо пустая строка, либо синтаксически валидный JSON. Всю содержательную валидацию (обязательные поля узлов и т.п.) делает само React-приложение — бэкенду глубже проверять не нужно. Пример реального значения — в docs/bitrix-integration.md, раздел 6.
Проверка
- Открыть страницу — iframe показывает пустую диаграмму (для нового элемента) или ранее сохранённую.
- Отредактировать (подвигать фигуры, поменять текст).
- Сабмитнуть форму как обычно (не внутри iframe).
- Перезагрузить страницу — изменения на месте.
- Проверить напрямую в БД/хранилище — там лежит валидный JSON.