При разработке интерфейсов на управляемых формах часто возникает задача отображения итогов по выбранным позициям. Например, пользователь выделяет несколько документов в списке, а система должна мгновенно показать общую сумму, вес или объем этих документов. Несмотря на кажущуюся простоту, реализация этой функции в ДинамическомСписке имеет ряд подводных камней, связанных с клиент-серверным взаимодействием и особенностями кэширования данных. В этой статье мы подробно разберем, как правильно реализовать такой расчет, избежав типичных ошибок и «подтормаживания» интерфейса.
Рассмотрим стандартную ситуацию: у нас есть форма списка, где свойство РежимВыделения установлено в значение Множественный. Когда мы обращаемся к свойству Элементы.Список.ВыделенныеСтроки, платформа возвращает нам массив идентификаторов строк. В большинстве случаев это ссылки на объекты базы данных. Основная сложность заключается в том, что нам нужны не сами ссылки, а значения конкретных колонок, которые уже отображены в списке.
Многие начинающие разработчики пытаются получить данные, обращаясь к серверу в цикле для каждой ссылки, что крайне негативно сказывается на производительности. Проанализируем более эффективный способ — использование метода ДанныеСтроки().
Метод ДанныеСтроки() позволяет получить данные, которые в данный момент доступны на клиенте в кэше динамического списка. Это работает быстро, так как не требует дополнительного обращения к базе данных. Разберем пример реализации в обработчике события ПриАктивизацииСтроки:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ИтоговаяСумма = 0;
Для Каждого ИдентификаторСтроки Из Элементы.Список.ВыделенныеСтроки Цикл
ДанныеСтроки = Элементы.Список.ДанныеСтроки(ИдентификаторСтроки);
// Обязательно проверяем на Неопределено, так как данные могут быть не подгружены
Если ДанныеСтроки <> Неопределено Тогда
ИтоговаяСумма = ИтоговаяСумма + ДанныеСтроки.СуммаДокумента;
КонецЕсли;
КонецЦикла;
// Присваиваем результат реквизиту формы
ОбщаяСуммаВыделенных = ИтоговаяСумма;
КонецПроцедуры
Важный нюанс: Метод ДанныеСтроки() вернет Неопределено, если строка еще не была отрисована на экране или была вытеснена из кэша. Если пользователь выделит 500 строк (например, через Ctrl+A), расчет по этому методу будет неполным. В таких случаях необходимо комбинировать методы или переходить к серверному расчету.
Выясним причину одной из самых коварных ошибок: когда при изменении суммы событие ПриАктивизацииСтроки начинает срабатывать бесконечно или дважды. Это часто происходит, если для отображения итога используется Декорация (вид — Надпись), и разработчик программно меняет её Заголовок.
Проанализируем ситуацию: изменение заголовка декорации заставляет форму перерисовываться. В некоторых версиях платформы или при определенных настройках состава командной панели это инициирует обновление состояния элементов, что платформа интерпретирует как повод снова вызвать ПриАктивизацииСтроки. Получается замкнутый круг: изменение заголовка -> перерисовка -> активация строки -> изменение заголовка.
Решение проблемы: Вместо декорации следует использовать обычный Реквизит формы (тип — Число или Строка). Этот реквизит нужно вынести на форму как Поле ввода, установив ему вид Поле надписи. Изменение значения реквизита формы не вызывает таких агрессивных побочных эффектов, как прямое изменение свойств элементов формы (декораций).
Событие ПриАктивизацииСтроки срабатывает очень часто — при каждом перемещении курсора. Если расчет сложный или требует обращения к серверу, интерфейс будет «зависать» при прокрутке списка. Посмотрим, как применить механизм «дребезга» (debouncing) с помощью обработчика ожидания.
Суть метода заключается в том, чтобы запустить расчет не мгновенно, а с небольшой задержкой (например, 0.1 – 0.3 секунды). Если за это время пользователь перейдет на другую строку, старый расчет отменяется и запускается новый отсчет.
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
// Отключаем предыдущий вызов, если он еще не сработал
ОтключитьОбработчикОжидания("ВыполнитьРасчетИтогов");
// Запускаем новый таймер на 200 миллисекунд
ПодключитьОбработчикОжидания("ВыполнитьРасчетИтогов", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьРасчетИтогов()
// Здесь выполняем суммирование
РассчитатьСуммуНаСервере();
КонецПроцедуры
Если нам критично получить точную сумму по всем выделенным строкам, даже если их тысячи, мы должны выполнить запрос к базе данных на сервере, передав массив выделенных ссылок.
&НаСервереБезКонтекста
Функция ПолучитьСуммуВыделенныхНаСервере(МассивСсылок)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СУММА(Док.СуммаДокумента) КАК Итого
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
| Док.Ссылка В(&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат ?(Выборка.Итого = NULL, 0, Выборка.Итого);
КонецЕсли;
Возврат 0;
КонецФункции
Этот метод является самым надежным. Чтобы минимизировать количество серверных вызовов, используйте его совместно с описанным выше обработчиком ожидания.
ДанныеСтроки(), убедитесь, что у соответствующих полей в настройках ДинамическогоСписка на форме установлен флаг Использовать всегда. В противном случае, если пользователь скроет колонку через «Изменить форму», её значения перестанут передаваться на клиент, и сумма станет нулевой.ВыделенныхСтрок может работать некорректно, особенно в сложных произвольных запросах.ИсполняемаяСхемаКомпоновкиДанных. Это позволит программно выполнить тот же запрос, что и список, включая все пользовательские фильтры.Подводя итог, можно сказать, что для простых списков и небольшого количества строк вполне достаточно клиентского метода ДанныеСтроки() в связке с реквизитом формы. Для профессиональных интерфейсов, устойчивых к большим объемам данных, оптимальным решением будет связка Обработчик ожидания + Серверный запрос по массиву ссылок.