Files
Portal-Chart-Builder/docs/backend-quickstart.md
T

10 KiB
Raw Blame History

Гайд: как встроить конструктор диаграмм в поле Bitrix

Короткая практическая версия — только три вопроса: куда положить билд, как собрать HTML и куда писать начальное значение. Про полноценный переиспользуемый тип свойства инфоблока (регистрация, ConvertToDB и т.п.) — отдельный документ, docs/bitrix-integration.md. Если поле одно и на одной странице — этого гайда достаточно.

1. Куда положить билд конструктора

Приложение — статическая сборка (npm run build → dist/), которая раздаётся тем же nginx, что и сам портал. Ничего разворачивать отдельно не нужно.

  1. Собрать в репозитории 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).
  2. Скопировать содержимое dist/ в local/diagram-app/ внутри каталога сайта на сервере (тот же каталог, где лежит остальной код портала — bitrix_www/аналог на проде).
  3. Готово — но с оговоркой: приложение доступно не по фиксированному index.html.

Имя index.html в dist/ — с хешем содержимого, например index-2fddeca6.html, а не просто index.html (см. vite.config.ts, плагин hashIndexHtml — хеширует и сам HTML-файл, не только JS/CSS, которые Vite и так хеширует по умолчанию). Хеш меняется при каждой значимой пересборке и не меняется, если содержимое не менялось. Значит:

  • src у iframe нельзя хардкодить строкой — нужно на своей стороне подставлять актуальное имя файла. Готовый способ: рядом с билдом сборка кладёт app.json со стабильным именем и актуальным именем точки входа внутри — { "index": "index-2fddeca6.html" }. Прочитать его можно и с бэкенда, и прямо из браузера (fetch('app.json', { 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.

Проверка

  1. Открыть страницу — iframe показывает пустую диаграмму (для нового элемента) или ранее сохранённую.
  2. Отредактировать (подвигать фигуры, поменять текст).
  3. Сабмитнуть форму как обычно (не внутри iframe).
  4. Перезагрузить страницу — изменения на месте.
  5. Проверить напрямую в БД/хранилище — там лежит валидный JSON.