Как вызвать процедуры и функции 1С из JavaScript в ПолеHTMLДокумента на движке WebKit?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

С выходом платформ 1С:Предприятие версий 8.3.14 и 8.3.15 произошел важный технологический сдвиг: старый движок рендеринга Internet Explorer (Trident) в элементе управления ПолеHTMLДокумента был заменен на современный WebKit (Chromium). Это нововведение принесло поддержку современных стандартов HTML5, CSS3 и быструю работу JavaScript-движка V8, однако полностью сломало старые механизмы интеграции между 1С и JS.

В старых версиях платформы разработчики могли напрямую передать объект формы 1С в контекст браузера (например, через window.externalForm = ЭтаФорма) и вызывать экспортные методы модуля формы из скриптов страницы. В WebKit из соображений безопасности и архитектурных ограничений этот механизм заблокирован. Контекст WebKit полностью изолирован, и передача сложных COM-объектов из 1С в браузер невозможна — переменные будут иметь значение undefined. Нам доступны только примитивные типы данных (строки, числа, булевы значения).

В этой статье мы подробно разберем, как обойти данные ограничения и настроить надежную передачу данных и вызов функций 1С из JavaScript с помощью нескольких альтернативных и современных подходов.

Решение 1. Использование скрытых элементов DOM и перехвата событий клика

Это классический рабочий подход, предложенный на форуме. Суть метода заключается в том, что JavaScript на веб-странице записывает передаваемые параметры в скрытые теги <div>, после чего программно инициирует событие клика. Платформа 1С перехватывает событие нажатия в стандартном обработчике, считывает данные из DOM-дерева страницы, а затем очищает буферные элементы.

Рассмотрим реализацию этого метода по шагам. Сначала подготовим HTML-код страницы со скриптом инициализации карты (на примере библиотеки Leaflet) и скрытыми полями обмена:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css"/>
    <script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>
    <style>
        html, body, #map { height: 100%; width: 100%; margin: 0; padding: 0; }
    </style>
</head>
<body>
    <div id="map"></div>
    
    <!-- Скрытые элементы для передачи данных в 1С -->
    <div id="message_key" style="display: none"></div>
    <div id="message_data" style="display: none"></div>

    <script type="text/javascript">
        var this_webkit = true; // Флаг работы под WebKit (1С 8.3.14+)
        var map = L.map('map').setView([55.751244, 37.618423], 12);
        
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 18
        }).addTo(map);

        // Обработчик клика по карте
        map.on('click', function(event) {
            var location = event.latlng;
            // Передаем имя события и координаты в формате JSON
            sendTo1C('map.onclick', JSON.stringify(location));
        });

        // Функция записи данных в DOM и отправки уведомления в 1С
        function sendTo1C(name, data) { 
            if (this_webkit) {
                // На новом движке WebKit записываем данные в скрытые div-ы
                document.getElementById('message_key').innerHTML = name;
                document.getElementById('message_data').innerHTML = data;
                
                // Программно вызываем событие клика на документе, чтобы 1С его перехватила
                var event = document.createEvent('HTMLEvents');
                event.initEvent('click', true, true);
                document.dispatchEvent(event);
            } else {
                // Резервный вариант для старого движка IE (Trident)
                var evt = document.createEventObject();
                evt.propertyName = name;
                evt.data = data;
                evt.cancelBubble = true;
                evt.returnValue = false;
                document.fireEvent('onclick', evt);
            }
        }
    </script>
</body>
</html>

Теперь проанализируем модуль управляемой формы 1С, где размещен реквизит ПолеHTML. Нам необходимо перехватить нажатие на документ и извлечь данные:


&НаКлиенте
Перем ОкноКарты;

&НаКлиенте
Процедура ПолеHTMLДокументСформирован(Элемент)
    // Получаем окно браузера для последующего прямого вызова JS-функций (если потребуется)
    ДокументБраузера = Элемент.Документ;
    ОкноКарты = ДокументБраузера.defaultView;
КонецПроцедуры

&НаКлиенте
Процедура ПолеHTMLПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
    
    // Блокируем стандартную реакцию браузера на клик
    СтандартнаяОбработка = Ложь;
    
    Если ОкноКарты = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Считываем значения из скрытых div-ов на странице
    Попытка
        КлючСобытия = ОкноКарты.document.getElementById("message_key").innerHTML;
        ЗначениеСобытия = ОкноКарты.document.getElementById("message_data").innerHTML;
        
        // Очищаем буферные элементы, чтобы избежать повторного срабатывания
        ОкноКарты.document.getElementById("message_key").innerHTML = "";
        ОкноКарты.document.getElementById("message_data").innerHTML = "";
    Исключение
        // Элементы не найдены на странице
        Возврат;
    КонецПопытки;
    
    Если Не ПустаяСтрока(ЗначениеСобытия) Тогда
        // Обрабатываем полученные данные
        ВыполнитьДействие1С(КлючСобытия, ЗначениеСобытия);
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьДействие1С(КлючСобытия, ДанныеJSON)
    
    // Десериализуем данные из JSON-строки в структуру 1С
    Данные = ДесериализоватьJSON(ДанныеJSON);
    
    Если КлючСобытия = "map.onclick" Тогда
        // Обрабатываем клик по карте и получение координат
        Широта = Неопределено;
        Долгота = Неопределено;
        
        Если Данные.Свойство("lat") Тогда
            Широта = Данные.lat;
        КонецЕсли;
        Если Данные.Свойство("lng") Тогда
            Долгота = Данные.lng;
        КонецЕсли;
        
        Сообщить(СтрШаблон("Получены координаты из JS: Широта = %1, Долгота = %2", Широта, Долгота));
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Функция ДесериализоватьJSON(СтрокаJSON)
    Чтение = Новый ЧтениеJSON;
    Чтение.УстановитьСтроку(СтрокаJSON);
    Результат = ПрочитатьJSON(Чтение);
    Чтение.Закрыть();
    Возврат Результат;
КонецФункции

Важное примечание: Обратите внимание на замену свойства parentWindow на defaultView. В движке WebKit глобальный контекст браузера (объект window) получается исключительно через defaultView.

Решение 2. Использование кастомной схемы URL (v8action://)

Предыдущий способ со скрытыми <div> полностью работоспособен, однако он вынуждает разработчика засорять DOM-дерево страницы техническими элементами и постоянно очищать их. Существует более изящный и современный способ передачи данных — использование кастомных псевдо-протоколов (схем URL).

При этом подходе JavaScript-код инициирует изменение адреса страницы через window.location.href, подставляя специальный префикс (например, v8action://) и передавая параметры в URI-кодированном JSON. Платформа 1С перехватывает эту попытку перехода в событии ПриНажатии, предотвращает реальный переход браузера и забирает переданные параметры.

Посмотрим на пример JS-функции на веб-странице:


function sendDataTo1C(actionName, payload) {
    const dataTransfer = {
        action: actionName,
        data: payload
    };
    
    // Сериализуем объект в строку JSON и кодируем для безопасной передачи в URL
    const jsonString = encodeURIComponent(JSON.stringify(dataTransfer));
    
    // Инициируем переход по нашему кастомному протоколу
    window.location.href = "v8action://" + jsonString;
}

// Пример использования при клике на маркер карты:
sendDataTo1C("markerSelected", { id: "000001234", lat: 55.75, lng: 37.61 });

Теперь разберем, как поймать этот вызов на стороне управляемой формы 1С в обработчике события ПоляHTMLДокумента:


&НаКлиенте
Процедура ПолеHTMLПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
    
    // Проверяем, что адрес перехода начинается с нашей схемы протокола
    Если СтрНачинаетсяС(ДанныеСобытия.Href, "v8action://") Тогда
        
        // Отменяем стандартный переход браузера по ссылке
        СтандартнаяОбработка = Ложь;
        
        // Извлекаем закодированную JSON-строку (отбрасываем префикс "v8action://" длиной 11 символов)
        ЗакодированныйJSON = Сред(ДанныеСобытия.Href, 12);
        
        // Декодируем URI-последовательность обратно в обычную строку JSON
        СтрокаJSON = ДекодироватьСтроку(ЗакодированныйJSON, СпособКодированияСтроки.КодированиеURL);
        
        // Преобразуем JSON в структуру 1С
        ПараметрыСобытия = ДесериализоватьJSON(СтрокаJSON);
        
        // Вызываем соответствующий обработчик в зависимости от действия
        Если ПараметрыСобытия.action = "markerSelected" Тогда
            ОбработатьВыборМаркера(ПараметрыСобытия.data);
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ОбработатьВыборМаркера(ДанныеМаркера)
    Сообщить("В 1С выбран маркер с ИД: " + ДанныеМаркера.id);
КонецПроцедуры

Данный подход является наиболее чистым, так как не требует работы с разметкой DOM и не вызывает ложных срабатываний кликов на других элементах формы.

Решение 3. Использование HTML5 Data-атрибутов

Если на вашей HTML-странице размещены стандартные кнопки, ссылки или списки, по нажатию на которые требуется вызывать определенный код в 1С, вы можете использовать стандартные HTML5 data-атрибуты (data-*). Это избавит вас от написания сложного JS-кода отправки событий.

Разметим HTML-элементы на странице следующим образом:


<button class="action-btn" data-action="createDocument" data-type="Invoice" data-id="1054">Создать счет</button>
<button class="action-btn" data-action="openReport" data-type="Sales" data-id="2023">Открыть отчет</button>

В обработчике события 1С мы можем напрямую получить доступ к свойствам нажатого HTML-элемента через свойство ДанныеСобытия.Element:


&НаКлиенте
Процедура ПолеHTMLПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
    
    ТекущийЭлемент = ДанныеСобытия.Element;
    
    Если ТекущийЭлемент <> Неопределено Тогда
        
        // Проверяем класс элемента, чтобы обрабатывать только наши кнопки
        Если СтрНайти(ТекущийЭлемент.className, "action-btn") > 0 Тогда
            
            СтандартнаяОбработка = Ложь;
            
            // Получаем значения кастомных data-атрибутов
            Действие = ТекущийЭлемент.getAttribute("data-action");
            ТипДокумента = ТекущийЭлемент.getAttribute("data-type");
            Идентификатор = ТекущийЭлемент.getAttribute("data-id");
            
            ВыполнитьКомандуИзИнтерфейса(Действие, ТипДокумента, Идентификатор);
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьКомандуИзИнтерфейса(Действие, ТипДокумента, Идентификатор)
    Сообщить(СтрШаблон("Команда 1С: Действие - %1, Тип - %2, ИД - %3", Действие, ТипДокумента, Идентификатор));
КонецПроцедуры

Дополнительно: Вызов JavaScript функций из 1С на движке WebKit

Рассмотрим обратную задачу — вызов функций JavaScript из кода 1С. В WebKit этот процесс также претерпел изменения. Нам необходимо учитывать два важных правила:

  1. Для обращения к контексту страницы всегда используйте defaultView вместо устаревшего parentWindow. Например:
    Элементы.ПолеHTML.Документ.defaultView.myJSFunction(Параметр1);
  2. Платформа 1С из соображений безопасности блокирует прямой вызов метода eval() из кода встроенного языка 1С. Если вам нужно динамически выполнять произвольный код JS, добавьте на саму HTML-страницу функцию-обертку, выполняющую локальный eval:
    
    function executeEval(codeString) {
        return eval(codeString);
    }
    

    После этого вы можете вызывать ее из 1С, передавая любой динамический JS-код в виде строки:
    
    КодJS = "map.setView([55.75, 37.61], 14);";
    Элементы.ПолеHTML.Документ.defaultView.executeEval(КодJS);
    

Решение проблемы агрессивного кэширования в WebKit

При активной разработке интерактивных интерфейсов на WebKit программисты часто сталкиваются с тем, что движок браузера агрессивно кэширует локальные HTML-файлы, скрипты и картинки. Даже если вы изменили код макета или JS-файла на диске, в ПолеHTMLДокумента может отображаться старая закэшированная версия.

Чтобы гарантированно сбросить кэш при загрузке страницы или скрипта, добавляйте к путям файлов уникальный динамический параметр (например, текущую дату и время в виде таймстампа):


// Пример динамической сборки пути к файлу скрипта для обхода кэша
ИмяСкрипта = "my_map_logic.js";
УникальныйПараметр = Формат(ТекущаяДата(), "ДФ=yyyyMMddhhmmss");
ПутьКСкрипту = СтрШаблон("%1?v=%2", ИмяСкрипта, УникальныйПараметр);

// Итог: "my_map_logic.js?v=20231024123045" - браузер воспримет это как новый файл и загрузит заново

Используя предложенные методики (перехват кликов через скрытые div-элементы, кастомную схему URL v8action:// или HTML5 data-атрибуты), вы сможете построить гибкое, быстрое и отказоустойчивое взаимодействие между управляемыми формами 1С и веб-интерфейсом на базе современного и безопасного движка WebKit.

← На главную