При интеграции 1С с внешними сервисами разработчики часто сталкиваются с необходимостью выполнения сложных алгоритмов шифрования, обсчета хэш-сумм или рендеринга данных, которые уже реализованы на языке JavaScript. Переписывать готовые JS-библиотеки на язык 1С ресурсоемко и не всегда возможно. Гораздо эффективнее хранить JS-код в макете конфигурации и вызывать его функции напрямую из платформы.
В этой статье мы подробно разберем, как правильно загрузить JavaScript из текстового макета 1С, передать в него параметры из управляемой формы и получить результат вычислений обратно в 1С. Мы также проанализируем распространенные ошибки разработчиков и особенности работы современных версий платформы 1С:Предприятие 8.3.
Ранее для выполнения произвольного JS-кода в среде Windows часто использовался COM-объект MSScriptControl.ScriptControl. Однако сегодня этот подход считается устаревшим и крайне ненадежным по следующим причинам:
MSScriptControl поддерживает только старые стандарты JavaScript (ES3/ES5) и не способен обрабатывать современный JS-код.Единственным кроссплатформенным и современным решением в экосистеме 1С является использование элемента формы Поле HTML документа.
Начиная с версии платформы 1С 8.3.14, внутренний движок отображения HTML был изменен. Вместо устаревшего системного Internet Explorer (Trident) теперь используется современный и быстрый WebKit (Chromium). Это нововведение изменило правила обращения к элементам DOM-модели, в частности, свойство parentWindow уступило место стандартному defaultView.
Выясним причину, по которой у автора темы на форуме возникла ошибка "Метод объекта не обнаружен". Типичная ошибка начинающих разработчиков заключается в попытке мгновенного вызова JS-функции сразу после присвоения текста макета реквизиту формы. Загрузка HTML-документа и инициализация скриптов внутри браузерного движка происходят асинхронно. Попытка обратиться к функции до завершения рендеринга страницы всегда будет приводить к ошибке.
Рассмотрим по шагам правильный и стабильный алгоритм решения этой задачи.
Создадим в нашей обработке или документе макет с типом Текстовый документ и назовем его МодульJS. Вставим в него полноценный HTML-код, содержащий JavaScript-функцию, к которой мы планируем обращаться. Обратите внимание на наличие тегов структуры документа:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript">
// Наша целевая функция, выполняющая условное шифрование или подпись данных
function getDigest(objStr) {
try {
// Пример простой обработки строки для демонстрации результата
var result = "Хэш для строки [" + objStr + "] успешно сформирован!";
return result;
} catch (e) {
return "Ошибка выполнения JS: " + e.message;
}
}
</script>
</head>
<body>
<div>Контекст JS-скрипта инициализирован.</div>
</body>
</html>
Добавим на управляемую форму новый реквизит и настроим элемент управления:
РеквизитHTML, тип данных — Строка (длина неограниченна).ПолеHTML.ПолеHTML находим раздел "События" и создаем обработчик для события ПриНажатии или ДокументСформирован. Нам потребуется именно событие ДокументСформирован.Добавим в модуль формы переменную-флаг, которая будет сигнализировать о готовности документа к работе:
&НаКлиенте
Перем ДокументЗагружен;
Реализуем чтение макета на сервере и его передачу в поле HTML-документа на клиенте при открытии формы. Также настроим обработчик завершения загрузки:
&НаСервере
Функция ПолучитьТекстМакетаНаСервере()
// Получаем макет "МодульJS" из нашей обработки
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
Макет = ОбработкаОбъект.ПолучитьМакет("МодульJS");
Возврат Макет.ПолучитьТекст();
КонецФункции
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ДокументЗагружен = Ложь;
// Загружаем HTML-текст в реквизит формы, связанный с Полем HTML документа
РеквизитHTML = ПолучитьТекстМакетаНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура ПолеHTMLДокументСформирован(Элемент)
// Данное событие срабатывает, когда веб-страница полностью отрисовалась и JS готов к работе
ДокументЗагружен = Истина;
КонецПроцедуры
Напишем функцию, которая будет извлекать глобальный контекст окна браузера с учетом особенностей различных версий платформы 1С (совместимость с IE и WebKit), а затем вызывать целевой JS-метод с передачей параметров:
&НаКлиенте
Функция ПолучитьКонтекстОкна(ЭлементHTML)
Документ = ЭлементHTML.Документ;
Если Документ = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
// В современном WebKit используется defaultView, в старом IE - parentWindow
КонтекстОкна = Документ.defaultView;
Если КонтекстОкна = Неопределено Тогда
КонтекстОкна = Документ.parentWindow;
КонецЕсли;
Возврат КонтекстОкна;
КонецФункции
&НаКлиенте
Функция ВызватьШифрованиеJS(Знач ИсходныеДанные)
Если Не ДокументЗагружен Тогда
ПоказатьПредупреждение(, "Пожалуйста, подождите, HTML-документ еще загружается!");
Возврат Неопределено;
КонецЕсли;
ЭлементHTML = Элементы.ПолеHTML;
КонтекстОкна = ПолучитьКонтекстОкна(ЭлементHTML);
Если КонтекстОкна = Неопределено Тогда
ВызватьИсключение "Не удалось получить доступ к окну браузера.";
КонецЕсли;
Попытка
// Обращаемся напрямую к JS-функции, передавая параметры
РезультатJS = КонтекстОкна.getDigest(ИсходныеДанные);
Возврат РезультатJS;
Исключение
Сообщить("Произошла ошибка при вызове getDigest: " + ОписаниеОшибки(), СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
Теперь мы можем привязать вызов этой функции к любой кнопке на нашей форме:
&НаКлиенте
Процедура ВыполнитьКоманду(Команда)
Результат = ВызватьШифрованиеJS("Тестовая строка для передачи в JS");
Если Результат <> Неопределено Тогда
ПоказатьПредупреждение(, Результат);
КонецЕсли;
КонецПроцедуры
Рассмотрим альтернативную ситуацию. Допустим, ваш макет МодульJS содержит исключительно чистый JavaScript-код, без обертки в HTML-теги. Создавать под каждый такой скрипт статичную HTML-структуру неудобно. В этом случае мы можем применить технологию динамического внедрения (инъекции) JS-кода в пустой документ.
Для реализации этого метода загрузим в Поле HTML документа простейшую базовую структуру, а затем программно добавим наш скрипт в элемент head:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ДокументЗагружен = Ложь;
// Загружаем пустую базовую страницу-заглушку
РеквизитHTML = "<html><head></head><body></body></html>";
КонецПроцедуры
&НаКлиенте
Процедура ПолеHTMLДокументСформирован(Элемент)
ДокументЗагружен = Истина;
// Получаем чистый JS-код из макета
ТекстСкрипта = ПолучитьТекстМакетаНаСервере();
// Внедряем скрипт в DOM-модель документа
ДокументHTML = Элементы.ПолеHTML.Документ;
НовыйЭлементСкрипт = ДокументHTML.createElement("script");
НовыйЭлементСкрипт.type = "text/javascript";
НовыйЭлементСкрипт.text = ТекстСкрипта;
ДокументHTML.head.appendChild(НовыйЭлементСкрипт);
// Теперь функции из макета добавлены в глобальный контекст defaultView
КонецПроцедуры
Этот способ позволяет использовать одно поле HTML для поочередной загрузки и выполнения совершенно разных JavaScript-библиотек "на лету" без перезагрузки всей страницы.
Когда требуется передать в JS не простую строку или число, а сложный объект (например, массив структур, содержащих таблицы данных), стандартные COM-объекты преобразования типов могут отработать некорректно или обрезать типы данных.
Для надежного обмена сложными данными всегда используйте сериализацию в формат JSON. Проанализируем пример такого взаимодействия:
ЗаписьJSON.var data = JSON.parse(objStr);.return JSON.stringify(resultObj);.ЧтениеJSON.Этот подход гарантирует 100% сохранность типов данных и защитит от непредвиденных ошибок преобразования при работе на любых версиях операционных систем.