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