В разработке на управляемых формах часто возникает задача предоставить пользователю возможность редактировать данные не по одной строке, а массово. Типичный пример: установка флага участия или ответственного лица для целого списка сотрудников. Если динамический список построен на основе произвольного запроса (например, объединяющего данные справочников и регистров), стандартные механизмы редактирования могут быть ограничены. Разберем подробно, как программно обработать выделенные строки, получить их значения и корректно внести изменения в базу данных.
При работе с динамическим списком программисты часто сталкиваются с тем, что свойство ВыделенныеСтроки возвращает массив идентификаторов, а не сами данные. Если для текущей строки мы можем легко обратиться к свойству ТекущиеДанные, то для обработки множественного выбора нам нужен другой подход. Рассмотрим ситуацию, когда у нас есть список, отображающий «матрицу ответственности» (физические лица и услуги), и нам нужно инвертировать флаг участия для всех выбранных строк.
Самый простой способ получить значения полей для выделенных строк — использование метода ДанныеСтроки() элемента формы (таблицы). Этот метод принимает идентификатор строки и возвращает структуру со значениями полей этой строки.
Рассмотрим пример реализации в обработчике события выбора или по нажатию специальной кнопки:
&НаКлиенте
Процедура ИзменитьФлагДляВыделенных(Команда)
// Получаем массив идентификаторов выделенных строк
ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
Для Каждого Идентификатор Из ВыделенныеСтроки Цикл
// Получаем данные конкретной строки по её ID
ДанныеСтроки = Элементы.Список.ДанныеСтроки(Идентификатор);
// Проверяем, что данные были получены (могут быть не определены, если строка не в кэше)
Если ДанныеСтроки <> Неопределено Тогда
// Вызываем серверную процедуру для записи изменений в БД
ЗаписатьИзмененияВРегистрНаСервере(
ДанныеСтроки.Организация,
ДанныеСтроки.ФизическоеЛицо,
ДанныеСтроки.Услуга,
ДанныеСтроки.ФлагУчастия
);
КонецЕсли;
КонецЦикла;
// Обновляем список, чтобы пользователь увидел изменения
Элементы.Список.Обновить();
КонецПроцедуры
Важное ограничение: Метод ДанныеСтроки() работает только с теми данными, которые в данный момент загружены в клиентский кэш. Динамический список подгружает данные порциями. Если пользователь выделит 2000 строк и попытается их обработать, для тех строк, которые еще не были отрисованы на экране, метод вернет Неопределено.
Чтобы избежать проблем с кэшем и минимизировать количество клиент-серверных вызовов (которые сильно замедляют работу интерфейса), правильно будет собрать все идентификаторы в один массив и передать их на сервер одним вызовом.
Проанализируем ситуацию: если наш динамический список имеет «Основную таблицу», идентификаторами строк будут ссылки или ключи записей. Если же это произвольный запрос (как в примере автора с UNION ALL), идентификатором может выступать ключ, автоматически сформированный платформой.
Рекомендуемый алгоритм работы:
ВыделенныеСтроки на клиенте.Посмотрим на пример серверного метода, который обрабатывает сразу весь массив:
&НаСервере
Процедура МассовоеИзменениеНаСервере(МассивИдентификаторов)
Для Каждого СтрокаID Из МассивИдентификаторов Цикл
// В случае произвольного запроса идентификатор часто совпадает с ТекущиеДанные
// Если ключ строки составной, логика получения данных усложняется
// Здесь мы предполагаем, что на сервер переданы уже готовые структуры данных
МенеджерЗаписи = РегистрыСведений.ИТ_УслугиПоФизЛицам.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Организация = СтрокаID.Организация;
МенеджерЗаписи.ФизическоеЛицо = СтрокаID.ФизическоеЛицо;
МенеджерЗаписи.Услуга = СтрокаID.Услуга;
// Инвертируем флаг
МенеджерЗаписи.Прочитать();
МенеджерЗаписи.ФлагУчастия = НЕ МенеджерЗаписи.ФлагУчастия;
МенеджерЗаписи.Записать();
КонецЦикла;
КонецПроцедуры
Выясним причину, почему автору темы было сложно работать с идентификаторами. В его запросе используется ГРУППИРОВАТЬ ПО и ОБЪЕДИНИТЬ ВСЕ. В таких случаях динамический список может не иметь четкого соответствия одной физической таблице БД.
Чтобы работа с выделенными строками была стабильной, проанализируем запрос:
ВЫБРАТЬ
ВложЗапрос.ФизическоеЛицо,
ВложЗапрос.Услуга,
МАКСИМУМ(ВложЗапрос.ФлагУчастия) КАК ФлагУчастия
ИЗ (...) КАК ВложЗапрос
СГРУППИРОВАТЬ ПО
ВложЗапрос.ФизическоеЛицо,
ВложЗапрос.Услуга
В настройках такого динамического списка в конфигураторе крайне рекомендуется вручную указать Ключ строки. Для данного примера ключом будет комбинация ФизическоеЛицо и Услуга. Это позволит платформе однозначно идентифицировать каждую строку, даже если данные меняются.
Рассмотрим распространенную ошибку: изменение данных напрямую в ТекущиеДанные на клиенте. Как справедливо замечено в обсуждении, это «багофича». Изменения, внесенные только в визуальную строку, будут потеряны при первой же прокрутке списка или обновлении страницы, так как динамический список перечитает данные из базы.
Правильная последовательность действий:
Обновить() для элемента формы.ОповеститьОбИзменении().Пример корректного завершения операции:
&НаКлиенте
Процедура ПослеМассовогоИзменения()
// Сообщаем системе, что данные регистра изменились
ОповеститьОбИзменении(Тип("РегистрСведенийЗапись.ИТ_УслугиПоФизЛицам"));
// Принудительно обновляем текущий список
Элементы.ТаблицаВвода.Обновить();
КонецПроцедуры
Проанализируем ситуацию с большими объемами данных. Если в списке 2500 сотрудников и 40 услуг, общее количество комбинаций достигает 100 000. При массовом выделении (Ctrl+A) и попытке записи каждой строки через МенеджерЗаписи сервер может столкнуться с блокировками или таймаутом.
Рекомендации для высокой производительности:
МенеджерЗаписи.Записать() в цикле, используйте НаборЗаписей. Это позволит записать множество строк в рамках одной транзакции, что значительно быстрее.Элементы.Список.ВыделенныеСтроки.Количество() > 0, чтобы избежать лишних вызовов.Таким образом, мы разобрали, что для редактирования выделенных строк в динамическом списке необходимо комбинировать получение идентификаторов на клиенте через ВыделенныеСтроки, чтение данных через ДанныеСтроки (с учетом ограничений кэша) и последующую транзакционную запись на сервере с обязательным обновлением интерфейса через механизм оповещений.