При работе с механизмом Поле HTML документа на платформе 1С:Предприятие разработчики часто сталкиваются со странным поведением: код JavaScript, прописанный внутри тегов script, выполняется два раза. Например, вместо одного модального окна alert пользователь видит два, или данные в документе дублируются. Особенно часто это проявляется на версиях платформы до 8.3.14, где в качестве движка отображения используется Internet Explorer (MSHTML).
В этой статье мы подробно разберем, почему это происходит, и рассмотрим несколько надежных способов решения проблемы — от простых "костылей" до правильной архитектуры взаимодействия JS и 1С.
Проанализируем ситуацию с точки зрения работы платформы. Когда мы присваиваем строку реквизиту, связанному с полем HTML документа, или программно устанавливаем текст через свойство Документ, 1С инициирует процесс загрузки контента. В этот момент происходят следующие процессы:
about:blank).script: первый раз при разборе (парсинге) документа и второй раз при окончательной отрисовке формы и связывании данных.Использование метода document.write, который мы видим в примере автора, только усугубляет ситуацию. Этот метод напрямую пишет в поток вывода документа, и если он вызывается в момент неопределенного состояния готовности страницы, это гарантированно приводит к визуальным артефактам и дублированию.
Самый простой и действенный способ избежать преждевременного выполнения кода — дождаться полной загрузки объектной модели документа (DOM). Если мы вынесем логику в обработчик события onload, браузер выполнит её только один раз, когда документ будет полностью сформирован в памяти.
Рассмотрим, как изменить HTML-код. Вместо прямого вызова внутри тега script, используем следующий подход:
|<html>
|<head>
| <script>
| window.onload = function() {
| // Ваш код здесь
| var p = document.createElement('p');
| p.innerText = 'qwerty';
| document.body.appendChild(p);
| };
| </script>
|</head>
|<body>
|</body>
|</html>
Также можно использовать атрибут onload непосредственно в теге body. Это решение считается более "топорным", но в условиях 1С оно работает безотказно:
|<body onload="console.log('Выполнено один раз');">
| <p>Контент страницы...</p>
|</body>
Если специфика вашей задачи не позволяет ждать полной загрузки страницы (например, скрипт должен подготовить данные для рендеринга), можно применить технику "защелки" или флага. Мы создаем глобальную переменную в контексте window и проверяем её наличие перед выполнением кода.
Разберем пример реализации такой защиты:
|<script>
| if (typeof window.alreadyRun === 'undefined') {
| window.alreadyRun = true;
|
| // Основная логика скрипта
| console.log('Этот код выполнится только один раз');
| // ...
| }
|</script>
Поскольку при повторном "прогоне" скрипта переменная alreadyRun уже будет существовать в глобальной области видимости текущей страницы, блок кода внутри if будет проигнорирован.
Наиболее современный и правильный подход — не вшивать исполняемую логику в сам текст HTML, а вызывать JS-функции из кода 1С тогда, когда это действительно нужно. В новых версиях платформы (8.3.15+) для этого предназначен метод ИсполнитьJavaScript(), но в версии 8.3.13 мы можем использовать объектную модель документа.
Рассмотрим поэтапно, как организовать такое взаимодействие. Сначала подготовим HTML-шаблон с функциями в секции head, но без их автоматического вызова:
|<!DOCTYPE html>
|<html>
|<head>
| <meta http-equiv="X-UA-Compatible" content="IE=edge" />
| <script>
| function CalculateData(param) {
| // Выполняем полезную работу
| var result = "Обработано: " + param;
| document.getElementById("resultField").innerHTML = result;
| return result;
| }
| </script>
|</head>
|<body>
| <div id="resultField">Ожидание данных...</div>
|</body>
|</html>
Затем в коде 1С (например, в обработчике нажатия кнопки на форме) вызовем эту функцию напрямую через parentWindow (для старых движков) или через коллекцию scripts. Однако самый стабильный способ для 8.3.13 — использование метода eval через объект Документ:
&НаКлиенте
Процедура ВыполнитьJS(Команда)
// Получаем COM-объект документа
ДокументHTML = Элементы.ПолеHTMLДокумента.Документ;
Если ДокументHTML = Неопределено Тогда
Возврат;
КонецЕсли;
// Вызываем функцию JS
Попытка
ДокументHTML.parentWindow.eval("CalculateData('Привет из 1С!')");
Исключение
Сообщить("Ошибка выполнения JS: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Иногда нужно не просто запустить скрипт, а передать в него массив данных, дождаться обработки и вернуть результат обратно в 1С. Рассмотрим пример с использованием скрытых элементов (буферов), который упоминался в обсуждении на форуме. Это отличный способ обойти ограничения безопасности и избежать двойного срабатывания.
В HTML создается скрытый div для хранения результата и кнопка (или другой элемент), событие которого мы будем "имитировать" из 1С:
|<!-- Внутри HTML -->
|<div id="BufferData" style="display:none"></div>
|<button id="HiddenButton" style="display:none" onclick="document.getElementById('BufferData').innerHTML = myComplexFunction();"></button>
В 1С мы программно нажимаем на эту кнопку и считываем значение из буфера:
&НаКлиенте
Функция ПолучитьДанныеИзJS()
Документ = Элементы.ПолеHTMLДокумента.Документ;
// Эмулируем клик для выполнения логики
Кнопка = Документ.getElementById("HiddenButton");
Кнопка.click();
// Забираем результат из невидимого поля
Результат = Документ.getElementById("BufferData").innerHTML;
Возврат Результат;
КонецФункции
Проанализируем еще несколько технических моментов, которые помогут сделать работу с JS в 1С стабильной:
head мета-тег: <meta http-equiv="X-UA-Compatible" content="IE=edge" />.appendChild, innerHTML, textContent).Подводя итог, можно сказать: двойное срабатывание скриптов — это не ошибка вашего кода, а особенность взаимодействия 1С с системным компонентом браузера. Используя событие window.onload или программный вызов функций через объектную модель Документ, вы обеспечите надежную и предсказуемую работу вашего интерфейса.