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