При интерактивной работе с большими таблицами в управляемых формах часто возникает задача ограничения выбора данных. Рассмотрим типичную бизнес-ситуацию: в табличной части (например, Накладные) отображается большой массив строк, в котором поле Идентификатор может дублироваться в разных позициях. Пользователь отмечает нужные строки с помощью флажка (поле типа Булево, например ВыбратьСтроку). Требуется обеспечить условие: если для определенного идентификатора флаг уже установлен в одной из строк, при попытке отметить другую строку с таким же идентификатором программа должна выводить предупреждение и автоматически отменять выбор.
Разберем по шагам, как правильно реализовать такую проверку на клиенте, выясним причины типичных ошибок начинающих разработчиков и рассмотрим оптимизацию кода.
Проанализируем распространенную ошибку, возникающую при попытке выполнить проверку полным перебором всей таблицы в цикле:
Для каждого по всей коллекции Объект.Накладные с вызовом метода НайтиСтроки() на каждой итерации приводит к квадратичной сложности алгоритма и зависанию интерфейса при сотнях записей.стр.ВыбратьСтроку = Истина без учета того, сколько именно строк с данным идентификатором уже выбрано, система будет блокировать даже самый первый выбор элемента. Так как пользователь только что установил флаг, текущая строка уже находится в состоянии Истина.Рассмотрим правильный подход. Обработку необходимо выполнять в событии ПриИзменении элемента формы, связанного с булевым полем (флажком). Проверку достаточно выполнять только в момент включения флажка (когда значение стало Истина).
Разберем рабочий алгоритм:
Элементы.Накладные.ТекущиеДанные.Ложь), завершаем работу процедуры, так как отмена выбора не нарушает уникальность.СтруктураНакладной, куда передаем сразу два параметра: Идентификатор текущей строки и ВыбратьСтроку = Истина.НайтиСтроки(). Если количество найденных строк строго больше 1 — это означает, что помимо текущей строки уже существовала другая ранее отмеченная строка с таким же идентификатором.ТекущиеДанные.ВыбратьСтроку = Ложь.Посмотрим на пример клиентской процедуры:
&НаКлиенте
Процедура НакладныеВыбратьСтрокуПриИзменении(Элемент)
ТекущиеДанные = Элементы.Накладные.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Если флаг снят пользователем — проверка не требуется
Если НЕ ТекущиеДанные.ВыбратьСтроку Тогда
Возврат;
КонецЕсли;
// Формируем отбор по идентификатору и признаку активности
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Идентификатор", СокрЛП(ТекущиеДанные.Идентификатор));
ПараметрыОтбора.Вставить("ВыбратьСтроку", Истина);
НайденныеСтроки = Объект.Накладные.НайтиСтроки(ПараметрыОтбора);
// Если найдено более одной строки, значит дубль уже был выбран ранее
Если НайденныеСтроки.Количество() > 1 Тогда
ПоказатьОповещениеПользователя("Внимание", , "Запись с данным идентификатором уже выбрана!");
ТекущиеДанные.ВыбратьСтроку = Ложь;
КонецЕсли;
КонецПроцедуры
Иногда с точки зрения пользовательского интерфейса (UX) удобнее не блокировать выбор с выводом диалоговых окон, а автоматически переносить выбор на новую позицию, снимая флаг с предыдущей строки с таким же идентификатором. Рассмотрим реализацию такого поведения:
&НаКлиенте
Процедура НакладныеВыбратьСтрокуПриИзменении(Элемент)
ТекущиеДанные = Элементы.Накладные.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Или НЕ ТекущиеДанные.ВыбратьСтроку Тогда
Возврат;
КонецЕсли;
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Идентификатор", СокрЛП(ТекущиеДанные.Идентификатор));
ПараметрыОтбора.Вставить("ВыбратьСтроку", Истина);
НайденныеСтроки = Объект.Накладные.НайтиСтроки(ПараметрыОтбора);
// Снимаем выбор со всех строк с данным идентификатором, кроме текущей
Для Каждого Стр Из НайденныеСтроки Цикл
Если Стр <> ТекущиеДанные Тогда
Стр.ВыбратьСтроку = Ложь;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Рассмотрим дополнительные технические аспекты, которые необходимо учитывать при реализации подобной логики:
ДанныеФормыКоллекция (в которой хранятся строки таблицы на клиенте) не поддерживает индексирование, в отличие от серверной таблицы значений. Метод НайтиСтроки() выполняет линейный перебор элементов. Для 400–500 строк это выполняется мгновенно, но при объемах в десятки тысяч строк для мгновенного отклика можно использовать объект Соответствие в реквизитах формы для кэширования выбранных идентификаторов со сложностью доступа $O(1)$.СокрЛП(), чтобы случайные концевые или начальные пробелы не приводили к ложным несовпадениям.ПриИзменении вызывается только при интерактивном клике пользователя. При программном заполнении таблицы или пакетной обработке строк клиентские события формы не отрабатывают, поэтому финальную проверку уникальности всегда следует дублировать на сервере (например, в обработчике ПередЗаписью или перед запуском результирующей обработки данных).