В разработке на платформе 1С:Предприятие часто возникает задача динамического управления интерфейсом. Например, нам необходимо показывать или скрывать определенные поля, кнопки или целые группы на форме документа в зависимости от того, какие данные пользователь ввел в табличную часть (ТЧ). Если в таблице присутствуют только определенные «обычные» номенклатурные позиции, мы можем скрыть лишние настройки интерфейса, но как только появляется хотя бы одна позиция, требующая особого внимания (например, хрупкий груз или отправление, требующее страховки), мы должны мгновенно отобразить нужные элементы управления.
В рамках этой статьи мы подробно разберем, как правильно реализовать подобную проверку. Мы проанализируем ошибки, с которыми часто сталкиваются начинающие разработчики, выясним причины странного поведения стандартных методов поиска и рассмотрим три различных архитектурных подхода — от простейшего алгоритма обхода коллекций до высокопроизводительных запросов и объектных настроек в соответствии с лучшими практиками разработки 1С.
Рассмотрим ситуацию, аналогичную обсуждаемой на форуме. У нас есть табличная часть СоставВложимого, в которой присутствует колонка ВидПередачи (тип СправочникСсылка.ВидыПередачиДляКурьерскойСлужбы). На форме расположена группа ГруппаСтрахованиеОтправителя. Нам нужно сделать эту группу доступной только тогда, когда в табличной части есть хотя бы один вид передачи, требующий страховки (например, «Выставочный экспонат», «Радиокомпоненты» и другие).
Начинающие разработчики часто пытаются решить эту задачу «в лоб», совершая при этом критические архитектурные и алгоритмические ошибки. Давайте посмотрим на типичный некорректный пример кода:
// ПРИМЕР С ОШИБКАМИ (НЕ ИСПОЛЬЗОВАТЬ В РЕАЛЬНЫХ ПРОЕКТАХ!)
Массив = Новый Массив;
Массив.Добавить(Справочники.ВидыПередачиДляКурьерскойСлужбы.НайтиПоНаименованию("Буклет"));
Массив.Добавить(Справочники.ВидыПередачиДляКурьерскойСлужбы.НайтиПоНаименованию("Выставочный экспонат"));
Элементы.ГруппаСтрахованиеОтправителя.Доступность = Ложь;
Для Каждого ЭлементМассива Из Массив Цикл
Отбор = Новый Структура;
Отбор.Вставить("ВидПередачи", ЭлементМассива);
// Ошибка №1: Попытка сравнения результата НайтиСтроки с Неопределено
Если Объект.СоставВложимого.НайтиСтроки(Отбор) <> Неопределено Тогда
Элементы.ГруппаСтрахованиеОтправителя.Доступность = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Разберем детально, почему этот код работает плохо, а в некоторых случаях приводит к неочевидным багам.
Во-первых, метод табличной части НайтиСтроки() никогда не возвращает Неопределено. В случае если строки по заданному отбору не найдены, метод возвращает пустой массив (объект типа Массив с количеством элементов равным 0). Следовательно, условие <> Неопределено всегда будет истинным, из-за чего доступность группы на форме будет включаться ошибочно. Чтобы исправить это, нужно проверять свойство Количество() полученного массива:
Если Объект.СоставВложимого.НайтиСтроки(Отбор).Количество() <> 0 Тогда
// Строки найдены
КонецЕсли;
Во-вторых, возникает опасная проблема с пустыми значениями. Если метод НайтиПоНаименованию() не найдет в базе данных элемент (например, если пользователь переименовал его или исправил опечатку), он вернет пустую ссылку — Справочники.ВидыПередачиДляКурьерскойСлужбы.ПустаяСсылка(). Эта пустая ссылка добавится в массив поиска. Когда пользователь добавит в табличную часть новую пустую строку, система выполнит поиск по отбору с пустой ссылкой, обнаружит совпадение с пустой строкой в табличной части и вернет массив с количеством элементов больше нуля! Это приведет к тому, что интерфейс формы изменит свое состояние просто из-за наличия пустой строки в таблице.
В-третьих, использование НайтиПоНаименованию или НайтиПоКоду непосредственно в коде — это грубая архитектурная ошибка (так называемый «хардкод»). Если обычный пользователь в режиме «1С:Предприятие» изменит название элемента (например, напишет «буклеты» вместо «Буклет»), логика программы неявно сломается. Кроме того, поиск по наименованию при каждом открытии или изменении формы создает избыточную нагрузку на базу данных.
Выяснив причины ошибок, перейдем к проектированию правильных и оптимизированных решений.
Если по каким-то причинам мы вынуждены хранить список проверяемых элементов непосредственно в коде программы (например, это жестко зафиксированные системные настройки), нам нужно минимизировать сложность алгоритма. В исходном ошибочном коде на каждую итерацию цикла по элементам массива вызывался тяжелый метод НайтиСтроки по всей табличной части. Это дает алгоритмическую сложность порядка O(N * M).
Мы можем значительно ускорить работу, выполнив всего один проход по табличной части. Для быстрой проверки вхождения элемента в список сравнения вместо последовательного поиска по массиву мы применим коллекцию Соответствие, так как поиск по ключу в ней работает за константное время O(1).
Рассмотрим пример правильной реализации обхода:
&НаКлиенте
Процедура СоставВложимогоПриИзменении(Элемент)
УстановитьДоступностьСтрахования();
КонецПроцедуры
&НаСервере
Процедура УстановитьДоступностьСтрахования()
// Шаг 1. Формируем соответствие контролируемых элементов.
// Используем предопределенные элементы, чтобы избежать поиска по наименованию.
КонтролируемыеВиды = Новый Соответствие;
// В реальной базе рекомендуется обращаться к предопределенным элементам конфигурации:
// КонтролируемыеВиды.Вставить(ПредопределенноеЗначение("Справочник.ВидыПередачиДляКурьерскойСлужбы.ВыставочныйЭкспонат"), Истина);
// Если предопределенных нет, можно использовать безопасный поиск с проверкой на пустое значение:
ВидБуклет = Справочники.ВидыПередачиДляКурьерскойСлужбы.НайтиПоНаименованию("Буклет");
Если ЗначениеЗаполнено(ВидБуклет) Тогда
КонтролируемыеВиды.Вставить(ВидБуклет, Истина);
КонецЕсли;
ВидЭкспонат = Справочники.ВидыПередачиДляКурьерскойСлужбы.НайтиПоНаименованию("Выставочный экспонат");
Если ЗначениеЗаполнено(ВидЭкспонат) Тогда
КонтролируемыеВиды.Вставить(ВидЭкспонат, Истина);
КонецЕсли;
// Шаг 2. Анализируем табличную часть за один проход
НужноВключитьДоступность = Ложь;
Для Каждого СтрокаТЧ Из Объект.СоставВложимого Цикл
// Пропускаем незаполненные строки, чтобы избежать ложных срабатываний
Если НЕ ЗначениеЗаполнено(СтрокаТЧ.ВидПередачи) Тогда
Продолжить;
КонецЕсли;
// Быстрая проверка наличия ключа в соответствии
Если КонтролируемыеВиды[СтрокаТЧ.ВидПередачи] <> Неопределено Тогда
НужноВключитьДоступность = Истина;
Прервать; // Цель достигнута, продолжать обход ТЧ нет смысла
КонецЕсли;
КонецЦикла;
// Шаг 3. Управляем доступностью элемента формы
Элементы.ГруппаСтрахованиеОтправителя.Доступность = НужноВключитьДоступность;
КонецПроцедуры
Этот алгоритм работает очень быстро и надежно предохраняет от ложного срабатывания на пустых строках.
Давайте сделаем наше решение по-настоящему гибким и профессиональным. Самым правильным архитектурным подходом в экосистеме 1С является перенос бизнес-логики из программного кода в структуру метаданных и данные.
Для этого выполним следующие действия по шагам:
ВидыПередачиДляКурьерскойСлужбы.ТребуетСтрахования и типом Булево.После этого нам больше не нужно хранить списки элементов в коде программы! Нам достаточно проверить, установлен ли этот флаг у тех элементов, которые пользователь добавил в табличную часть документа.
Для мгновенной и максимально производительной проверки мы задействуем механизм запросов платформы 1С. Запрос выполнится на стороне сервера СУБД, эффективно обработает временную таблицу данных нашей ТЧ и вернет точный результат.
Посмотрим на пример программной реализации этого подхода:
&НаСервереБезКонтекста
Функция ЕстьСтрокиТребующиеСтрахования(ТаблицаСостава)
// Создаем запрос для проверки наличия флага у элементов ТЧ
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле
|ИЗ
| &ВТ_Состав КАК Состав
|ГДЕ
| Состав.ВидПередачи.ТребуетСтрахования = ИСТИНА";
// Передаем выгруженную табличную часть в качестве параметра запроса
Запрос.УстановитьПараметр("ВТ_Состав", ТаблицаСостава);
РезультатЗапроса = Запрос.Выполнить();
// Если результат запроса не пустой, значит есть хотя бы одна подходящая строка
Возврат НЕ РезультатЗапроса.Пустой();
КонецФункции
&НаКлиенте
Процедура СоставВложимогоПриИзменении(Элемент)
// Чтобы не передавать всю тяжелую форму на сервер, выгружаем только колонку ТЧ
ТаблицаДляАнализа = Объект.СоставВложимого.Выгрузить( , "ВидПередачи");
// Вызываем легкую серверную функцию
ДоступностьГруппы = ЕстьСтрокиТребующиеСтрахования(ТаблицаДляАнализа);
// Управляем интерфейсом на клиенте
Элементы.ГруппаСтрахованиеОтправителя.Доступность = ДоступностьГруппы;
КонецПроцедуры
Использование связки «Реквизит справочника + Запрос» предоставляет разработчику и бизнесу целый ряд существенных преимуществ:
ТребуетСтрахования.&НаСервереБезКонтекста позволяет избежать передачи всех данных формы (включая неактивные вкладки, реквизиты шапки и т.д.) между клиентом и сервером. На сервер отправляется только один массив значений из проверяемой колонки, что положительно сказывается на работе системы в условиях медленного интернет-соединения или веб-клиента.Таким образом, мы рассмотрели эволюцию решения задачи от простых, но ошибочных циклов до профессионального архитектурного паттерна, гарантирующего стабильную и быструю работу приложения.