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