Введение
Разрабатывая мобильное приложение для салонов оптики на 1С (подробнее на ютубе и в телеграме) дойдя до этапа переноса подготовленного функционала на 1C, возник вопрос, какой метод для этого выбрать, чтобы упростить и ускорить перенос функционала на 1С. Для этого разберем учебный пример.
Задача
Реализовать нижнее меню для мобильного приложения на 1С.
При клике на пункты меняется активный пункт в меню и основной заголовок. Нижнее меню формируется динамически, для учебного примера пункты меню будут храниться в справочнике в мобильном приложении, который заполняется при старте.
Для экспериментов сделал пример в VS Code с которым будем работать:
Использовались следующие версии 1С:
- Платформа: 8.3.17.1496
- Мобильная платформа: 8.3.16.142
Ссылка на все исходники и материалы будет в конце статьи также будут приводиться только ключевые моменты по коду, остальное сможете самостоятельно изучить за чашечкой чая.
Анализ верстки
В исходниках на GitHub нужно обратить внимание на следующие моменты:
- Файл index.js: используются стрелочные функции, они в 1С не будут работать поэтому, когда будем переносить на 1С, нужно будет переделать под обычные функции.
- Файл index.html: в теге script используется атрибут defer, он также не будет работать в 1С т.к. атрибут defer предназначен только для внешних скриптов и для учебного примера содержимое скрипта полностью будет внутри тега.
Рассмотрим структуру HTML учебного примера в классическом варианте:
Содержимое тегов div с классом title и footer__inner нужно будет менять.
Первый метод
Идея данного метода заключается в формировании HTML структуры с помощью JavaScript. Примерная логика:
- 1С формирует строку JSON с данными
- Передает сформированную строку в функцию JavaScript, которая преобразует ее в массив с объектами
- Формируем HTML и меняем содержимое с помощью JavaScript функций
В итоге на стороне 1С остается только сформировать строки в формате JSON, передать их в нужные функции и вызвать функции JavaScript для изменения структуры HTML.
Возвращаемся к исходникам на GitHub и проанализируем основные файлы, там можем увидеть вариант формирования HTML структуры с помощью JavaScript:
- Файл index.html. Тег body пустой
- Файл index.js. У нас есть переменная footerMenuJSON, которая содержит пункты меню в формате Json. Данная строка передает функцию setItemsMenu, которая преобразует ее в массив с объектами. Далее вызываются две функции для отрисовки main (generateMain()) и footer (generateFooter())
Переходим к 1С. В исходниках файл с конфигурацией первого метода называется mobile1c_adapt_js.cf. В конфигурации добавлена единственная обработка на форме которой расположено ПолеHTML и реквизит формы с типом строка, с аналогичным названием. Код формы рассмотрим ниже:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ФормаКлиент.СформироватьСтраницу(ПолеHTML, ЭтаФорма);
ПодключитьОбработчикОжидания("ПослеОткрытия", 1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПослеОткрытия()
Документ = ЭтаФорма.Элементы.ПолеHTML.Документ;
ДанныеНижнееМеню = ОбщегоНазначенияВызовСервера.НижнееМенюJSON();
Документ.defaultView.setItemsMenu(ДанныеНижнееМеню);
Документ.defaultView.generateMain();
Документ.defaultView.generateFooter();
КонецПроцедуры;
В процедуре СформироватьСтраницу получаем стартовою структуру HTML, которая заполнена по шаблону.
Первая проблема, которая возникает, это невозможность вызвать функции JavaScript ПриОткрытии формы т.к. они еще не доступны. Для обхода данной проблемы используется обработчик ожидания. За все остальное дальше будет отвечать JavaScript.
Плюсы
- Большая часть логики для работы с HTML размещена в JavaScript. Для 1Сника все сводится к правильному формированию строк Json и вызову нужных функций JavaScript, как работа с каким-либо api. Данный подход будет удобен если версткой и адаптацией под 1С занимаются разные люди.
- Интерфейс более плавно перерисовывается по сравнению, если структура формировалась и изменялась на стороне 1С (об этом в след методе).
Минусы
- На конкретном примере пришлось подключать обработчик ожидания, чтобы отрисовать начальную страницу, эта задержка заметна в пользовательском режиме. Но она связана с особенностью, что данные пунктов меню хранятся в справочнике в мобильном приложении, альтернативой можно забирать эти данные сразу с сервера методами JavaScript (если они будут работать в 1С) или формировать начальные пункты в начальной структуре HTML.
- Отладка. Т.к объем кода JavaScript заметно увеличится при таком методе, вы можете столкнутся с проблемой, когда итоговая строка HTML будет на столько большая, что при попытке открыть ее в конфигураторе он будет зависать, а при копировании строки из окошка ниже она может обрезаться. С данной проблемой вы так же можете столкнуться если подключаете много дополнительных компонентов JavaScript. Предполагаю, что данный метод Инструменты отладки Web-страниц в приложении на устройстве Android позволяет обойти данную проблему (не тестировался).
Второй метод
Он основывается на подходе описанным в статье Асинхронный обмен данными с JavaScript (и не только) без потерь.
Данный метод был изучен на занятии по разработке HTML интерфейса, автор Илья Низамов (ютуб).
Несколько полезных статей на взаимодействия 1С и JavaScript:
- Выполнение JavaScript кода из 1С в объекте Поле HTML Документа (HTML 5) и вызов события в 1С ПриНажатии
- Javascript и 1С. Кросс-платформенное взаимодействие
В исходниках файл с конфигурацией второго метода называется mobile1c_adapt_1c.cf.
Здесь шаблон HTML в 1С выглядит по-другому:
Добавляется функция для формирования пунктов меню:
Функция ПолучитьМеню(ВыбранID = "") Экспорт
ШаблонПункта = "
|<a href=""%1"" class=""footer__item %4"" on click=""paramChange('menu', '%2')"">
|<img src=""%3"" class=""footer__icon"">
|</a>";
Меню = "";
МассивПунктов = ОбщегоНазначенияВызовСервера.ПунктыМеню();
Для Каждого СтрокаПункт Из МассивПунктов Цикл
Если (ВыбранID = "" И СтрокаПункт.active)
ИЛИ ВыбранID = СтрокаПункт.id
Тогда
ДопКласс = "footer__item--focus";
Иначе
ДопКласс = "";
КонецЕсли;
Меню = Меню + СтрШаблон(ШаблонПункта, СформироватьСсылку(СтрокаПункт.id), СтрокаПункт.id,
СтрокаПункт.icon, ДопКласс);
КонецЦикла;
Возврат Меню;
КонецФункции
В строке on click=""paramChange('menu', '%2')""> умышленно поставлен пробел т.к редактор текста на инфостаре вырезает данный кусок.
Рассмотрим код формы обработки, на которой расположено ПолеHTML:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ФормыКлиент.СформироватьФорму(ПолеHTML, ЭтаФорма);
ПодключитьОбработчикОжидания("ПолучитьИОбработатьДанные", 1);
КонецПроцедуры
&НаКлиенте
Процедура ПолеHTMLПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ПолучитьИОбработатьДанные();
КонецПроцедуры
&НаКлиенте
Процедура ПолучитьИОбработатьДанные() Экспорт
Документ = ЭтаФорма.Элементы.ПолеHTML.Документ;
Если Документ = Неопределено Тогда
Возврат;
КонецЕсли;
Пока ОкноДокумента.queueHead < ОкноДокумента.queueTail Цикл
Данные = ПрочитатьДанныеИзJSON(ОкноДокумента.getEvent(ОкноДокумента.queueHead + 1));
ОкноДокумента.queueHead = ОкноДокумента.queueHead + 1;
Если Данные = Неопределено Тогда
Возврат;
КонецЕсли;
Если НЕ Данные["menu"] = Неопределено Тогда
ОбработатьМеню(Данные["menu"]);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПрочитатьДанныеИзJSON(СтрокаJSON)
Результат = ОбщегоНазначения.ПрочитатьСтрокуJSON(СтрокаJSON);
Если ТипЗнч(Результат) = Тип("Строка") Тогда
Возврат ОбщегоНазначения.ПрочитатьСтрокуJSON(Результат);
Иначе
Возврат Результат;
КонецЕсли;
КонецФункции
&НаКлиенте
Процедура ОбработатьМеню(Данные)
Документ = ЭтаФорма.Элементы.ПолеHTML.Документ;
Если Документ = Неопределено Тогда
Возврат;
КонецЕсли;
ЗаголовокСтраницы = html.ПолучитьЗаголовок(Данные);
Меню = html.ПолучитьМеню(Данные);
html.ВставитьВHTML(Документ, ".title", ЗаголовокСтраницы);
html.ВставитьВHTML(Документ, ".footer__inner", Меню);
КонецПроцедуры
Код процедуры ВставитьВHTML:
Процедура ВставитьВHTML(document, querySelector, htmlText) Экспорт
Selector = document.querySelector(querySelector);
Selector.innerHTML = htmlText;
КонецПроцедуры
Принцип работы:
- Все пункты меню и заголовок формируются в начальной структуре HTML в процедуре СформироватьФорму
- Каждому пункту меню присваивается событие клика on click=""paramChange(Имя, Значение)"
- Функция paramChange создает объект из параметров, который передает в функцию pushEvent
- Функция pushEvent добавляет объект в массив очереди queue
- На стороне 1С в процедуре ПолучитьИОбработатьДанные получаем значения из очереди и обрабатываем в зависимости от ключа
- В процедуре ОбработатьМеню выполняем замену содержимого блоков с классом title и footer__inner
Основной минус метода, что он не отрабатывает в веб клиенте.
Заключение
В заключение можно посмотреть видео с демонстрацией результата каждого метода:
На видео не так заметно, что первый вариант работает более плавно и быстрее. Пользователи, которые принимали участие в тестирование замечали разницу и определяли более быстрым первый вариант.
Объемы нашего приложения пока не такие большие и визуальная разница пока не настолько заметна.
Если у статьи будет хороший отклик, реализуем более сложные примеры на основе первого метода, но немного модернизированным способом. Например, создадим страницу с карточкой товара или корзиной, а в будущем возможно дойти до порционного списка товаров. В этих примерах уже будут рассмотрены наиболее удобные варианты для передачи данных обратно в 1С.
UPDATE
05/11/20
Внесем следующие улучшения для первого метода:
1) Откажемся от обработчика ожидания. Будем использовать обработчик события ПолеHTML ДокументСфомирован (Спасибо Владимиру Голованову):
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ФормаКлиент.СформироватьСтраницу(ПолеHTML, ЭтаФорма);
КонецПроцедуры
&НаКлиенте
Процедура ПолеHTMLДокументСформирован(Элемент)
Документ = ЭтаФорма.Элементы.ПолеHTML.Документ;
ДанныеНижнееМеню = ОбщегоНазначенияВызовСервера.НижнееМенюJSON();
Документ.defaultView.setItemsMenu(ДанныеНижнееМеню);
Документ.defaultView.generateMain();
Документ.defaultView.generateFooter();
КонецПроцедуры
2) Начальное формирование пунктов меню сделаем при формирование начальной структуры:
Функция ПолучитьСкрипт() Экспорт
ДанныеНижнееМеню = ОбщегоНазначенияВызовСервера.НижнееМенюJSON();
Текст = "'use strict'
|let footerMenuJSON = `" + ДанныеНижнееМеню + "`
...
...
...
|setItemsMenu(footerMenuJSON)
|generateMain()
|generateFooter()
|";
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ФормаКлиент.СформироватьСтраницу(ПолеHTML, ЭтаФорма);
КонецПроцедуры
В результате уменьшили код и убрали задержку, которая была при обработчике ожидания. Конфигурация в исходниках обновлена.
Ссылка на все исходники и материалы
Мой список. Управляй своими заметками. JavaScript внутри 1С
Приложение доступно в Play market
Следите за проектом в телеграмм канале ovmst
ver 1.0
ver 1.1
Другие материалы, которые могут вас заинтересовать: