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