При переходе с обычных форм на управляемые разработчики часто сталкиваются с проблемой оптимизации вывода больших объемов данных. В обычных формах существовало удобное событие ПриПолученииДанных, которое позволяло легко реализовать подгрузку строк «на лету». В управляемых формах для этих целей предусмотрен ДинамическийСписок, однако он отлично работает только со статичными структурами, описанными в конфигураторе.
Что делать, если структура таблицы формируется динамически в режиме «1С:Предприятие» (всегда разные колонки), а сами данные запрашиваются напрямую из внешней СУБД MS SQL или PostgreSQL? Попытка загрузить все данные сразу в таблицу значений на форме приведет к критическому падению производительности и зависанию интерфейса. В этой статье мы подробно разберем, как обойти это ограничение и настроить плавный бесконечный скроллинг.
Прежде чем перейти к решениям, проанализируем причины ограничений стандартных механизмов 1С:
Рассмотрим четыре эффективных архитектурных сценария решения этой задачи.
---
Наиболее близким аналогом старого подхода является отслеживание текущей позиции пользователя в таблице формы. Чтобы избежать зависания формы во время выполнения SQL-запроса, мы применим асинхронный паттерн с использованием клиентского флага блокировки и короткого обработчика ожидания.
Разберем логику работы по шагам:
OFFSET и FETCH.ПриАктивизацииСтроки мы проверяем индекс текущей строки.ЗагрузкаВПроцессе.ПодключитьОбработчикОжидания с минимальным интервалом. Это позволяет форме завершить текущие интерфейсные операции и отрисовать фокус перед обращением к базе данных.Посмотрим на пример реализации данного алгоритма в модуле управляемой формы:
&НаКлиенте
Перем ЗагрузкаВПроцессе; // Флаг для блокировки повторных запросов
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Инициализируем параметры порционного чтения
РазмерПорции = 50;
ТекущееСмещение = 0;
// Динамически создаем колонки таблицы на форме (если структура неизвестна заранее)
СоздатьДинамическуюСтруктуруТаблицыНаСервере();
// Загружаем первую порцию данных
ЗагрузитьСледующуюПорциюНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ЗагрузкаВПроцессе = Ложь;
КонецПроцедуры
&НаКлиенте
Процедура ТаблицаДанныхПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элементы.ТаблицаДанных.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Определяем индекс текущей строки в коллекции
ИндексТекущейСтроки = Объект.ТаблицаДанных.Индекс(ТекущиеДанные);
ВсегоСтрокВТаблице = Объект.ТаблицаДанных.Количество();
// Если пользователь подошел близко к концу (осталось менее 5 строк)
Если (ИндексТекущейСтроки >= ВсегоСтрокВТаблице - 5) И Не ЗагрузкаВПроцессе Тогда
ЗагрузкаВПроцессе = Истина;
// Подключаем однократный обработчик с минимальной задержкой
ПодключитьОбработчикОжидания("ПодключитьАсинхроннуюЗагрузку", 0.1, Истина);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПодключитьАсинхроннуюЗагрузку()
Попытка
ЗагрузитьСледующуюПорциюНаСервере();
Исключение
ЗагрузкаВПроцессе = Ложь;
ВызватьИсключение;
КонецПопытки;
ЗагрузкаВПроцессе = Ложь;
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьСледующуюПорциюНаСервере()
// Выполняем быстрый SQL-запрос с пагинацией
// Пример текста SQL-запроса:
// "SELECT * FROM TableName ORDER BY ID OFFSET " + ТекущееСмещение + " ROWS FETCH NEXT " + РазмерПорции + " ROWS ONLY"
ТЗПорции = ПолучитьДанныеИзВнешнейБД(ТекущееСмещение, РазмерПорции);
Если ТЗПорции.Количество() > 0 Тогда
// Заполняем реквизит формы новыми строками
Для Каждого СтрокаИсточника Из ТЗПорции Цикл
НоваяСтрока = Объект.ТаблицаДанных.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника);
КонецЦикла;
// Сдвигаем указатель смещения для следующего шага
ТекущееСмещение = ТекущееСмещение + РазмерПорции;
КонецЕсли;
КонецПроцедуры
---
Если стандартная таблица 1С при быстром скроллинге начинает подтормаживать из-за серверных вызовов отрисовки интерфейса, прогрессивным решением будет полный перенос рендеринга на сторону HTML/JS.
Этот метод основан на использовании элемента формы типа ПолеHTMLДокумента. Внутрь него загружается легковесный JS-код, который строит таблицу и самостоятельно отслеживает скроллинг страницы с помощью современного API браузеров — IntersectionObserver.
Разберем схему взаимодействия систем:
IntersectionObserver.ДокументСформирован или ПриНажатии (в зависимости от версии платформы), быстро достает из SQL очередную пачку данных, преобразует её в формат JSON и отправляет обратно в HTML-поле.Преимущества данного метода очевидны: скроллинг происходит плавно за счет аппаратного ускорения рендеринга браузера, а интерфейс 1С полностью освобожден от тяжелых расчетов по динамическому изменению состава колонок.
---
Чтобы пользователь вообще не замечал задержек во время получения данных из внешней СУБД, можно применить стратегию упреждающего чтения. Вместо того чтобы запрашивать данные в момент достижения конца таблицы, мы делаем это заранее.
Проанализируем этот паттерн:
Для реализации фоновых вызовов без блокировки интерфейса можно использовать механизм ФоновыеЗадания 1С, результаты которых считываются периодическим обработчиком ожидания.
---
Если вы хотите сохранить богатый стандартный функционал 1С (группировки, условное оформление, стандартный поиск), можно обойти ограничения конфигуратора и использовать динамический список программно.
Для этого в момент открытия формы выполняется следующий алгоритм:
ИзменитьРеквизиты() программно создается реквизит формы с типом ДинамическийСписок.Таблица и связывается с созданным динамическим списком.Этот метод сложнее в реализации, но он незаменим, когда пользователю необходимы мощные интерактивные инструменты анализа данных, встроенные в платформу 1С.
Реализация бесконечной прокрутки динамических данных в управляемых формах 1С вполне возможна и показывает отличные результаты производительности, если не загружать всю СУБД-таблицу целиком. Оптимальным выбором по соотношению «скорость разработки / надежность» является схема с обработчиком ожидания и флагом блокировки (Решение 1). Если же требуется максимальная кастомизация интерфейса и абсолютная плавность — обратите внимание на интеграцию с HTML/JS (Решение 2).