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