При работе с расширениями конфигураций в платформе «1С:Предприятие 8.3» разработчики часто сталкиваются с ситуацией, когда после очередного обновления типовой конфигурации (например, «Управление торговлей», «ERP» или «Комплексная автоматизация») стабильно работавший ранее код в расширении внезапно начинает выдавать ошибки. Одна из классических проблем — появление значения Неопределено при обращении к свойству ТекущиеДанные таблицы формы, связанной с динамическим списком подбора номенклатуры.
В этой статье мы подробно разберем, почему возникает эта ошибка, как устроены внутренние механизмы платформы «1С» в отношении отрисовки интерфейса и отложенного считывания данных, а также рассмотрим несколько надежных архитектурных решений, которые позволят вашему коду безболезненно переживать любые обновления конфигурации.
Для начала проанализируем поведение платформы. В типовых конфигурациях обработка подбора товаров (например, ПодборТоваровВДокументПродажи или ПодборТовараПродаж) содержит сложную форму, на которой расположены сразу несколько альтернативных таблиц для поиска номенклатуры:
СписокСтандартныйПоискНоменклатура — используется при обычном поиске;СписокРасширенныйПоискНоменклатура — используется, когда в настройках системы включен расширенный режим поиска.Эти таблицы обычно располагаются на разных страницах одной панели (например, группы страниц СтраницыСписков). С точки зрения оптимизации производительности и экономии трафика между сервером и тонким клиентом, платформа «1С» использует механизм отложенного (ленивого) рендеринга и считывания данных для скрытых элементов формы.
Если страница, на которой физически расположена таблица СписокРасширенныйПоискНоменклатура, в данный момент скрыта (неактивна), платформа не инициализирует строки этой таблицы на клиенте и не запрашивает для нее данные с сервера. Как результат:
Элементы существует.Элементы.СписокРасширенныйПоискНоменклатура.ТекущиеДанные на клиенте вернет Неопределено.СписокСтандартныйПоискНоменклатура) свойство ТекущиеДанные будет успешно заполнено актуальной строкой, на которой стоит курсор пользователя.Когда вы обновляете конфигурацию, настройки по умолчанию или функциональные опции, отвечающие за видимость этих страниц (например, константа ИспользоватьСтандартныйПоискПриПодбореТоваров), могут измениться. В итоге активной становится другая страница, и ваш код, жестко привязанный к конкретному элементу формы, перестает работать.
Чтобы сделать код в расширении независимым от того, какая именно страница поиска активна у пользователя в данный момент, мы должны отказаться от жесткого обращения к конкретной таблице. Напишем универсальный алгоритм, который динамически определяет активную таблицу или последовательно ищет заполненные данные.
Рассмотрим первый вариант реализации — через проверку того, какая страница панели сейчас выбрана пользователем:
&НаКлиенте
Процедура Расш1_ДобавитьТоварПосле(Команда)
ТекущиеДанные = Неопределено;
АктивнаяСтраница = Элементы.СтраницыСписков.ТекущаяСтраница;
Если АктивнаяСтраница = Элементы.ГруппаСтраницаРасширенныйПоиск Тогда
ТекущиеДанные = Элементы.СписокРасширенныйПоискНоменклатура.ТекущиеДанные;
ИначеЕсли АктивнаяСтраница = Элементы.ГруппаСтраницаСтандартныйПоиск Тогда
ТекущиеДанные = Элементы.СписокСтандартныйПоискНоменклатура.ТекущиеДанные;
КонецЕсли;
Если ТекущиеДанные = Неопределено Тогда
ПоказатьПредупреждение(, NStr("ru = 'Пожалуйста, выберите товар из списка!'"));
Возврат;
КонецЕсли;
// Дальнейшая обработка полученных данных
ОбработатьВыбранныйТовар(ТекущиеДанные);
КонецПроцедуры
Если структура страниц сложная или может меняться при обновлениях, применим еще более гибкий и лаконичный подход — последовательный опрос таблиц на наличие заполненного свойства ТекущиеДанные:
&НаКлиенте
Процедура Расш1_ЗаполнитьТабличнуюЧасть()
ТекущиеДанные = Элементы.СписокРасширенныйПоискНоменклатура.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
// Если в расширенном списке пусто, пробуем получить данные из стандартного
ТекущиеДанные = Элементы.СписокСтандартныйПоискНоменклатура.ТекущиеДанные;
КонецЕсли;
Если ТекущиеДанные = Неопределено Тогда
// Если и там пусто, возможно, используется таблица характеристик или аналогов
// Проверяем все доступные источники данных на форме подбора
Если Элементы.Найти("СписокХарактеристикиНоменклатуры") <> Неопределено Тогда
ТекущиеДанные = Элементы.СписокХарактеристикиНоменклатуры.ТекущиеДанные;
КонецЕсли;
КонецЕсли;
Если ТекущиеДанные = Неопределено Тогда
Возврат; // Данные не выбраны ни в одном из списков
КонецЕсли;
// Работаем с универсальной переменной ТекущиеДанные...
ПровестиАнализНоменклатуры(ТекущиеДанные.Номенклатура);
КонецПроцедуры
Одной из главных ошибок при разработке расширений является добавление стандартной формы в расширение «как есть» с последующим визуальным изменением ее элементов в редакторе форм расширения. При обновлении основной конфигурации разработчики фирмы «1С» могут переименовать элементы формы, изменить их подчиненность в дереве элементов или вовсе удалить старые элементы. В этом случае форма в расширении теряет связь с оригиналом (наследование ломается), что приводит к непредсказуемым ошибкам в режиме «Предприятие».
Чтобы избежать подобных проблем, настоятельно рекомендуется минимизировать визуальные изменения форм в расширениях. Проанализируем, как внедрить изменения программно в обработчике события ПриСозданииНаСервере:
ПриСозданииНаСервере с помощью аннотации &После.Рассмотрим практический пример программного добавления колонки и кнопки на форму подбора:
&После("ПриСозданииНаСервере")
Процедура Расш1_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// 1. Программно добавляем колонку "ОстатокНаСкладе" в динамические списки
ДобавитьКолонкуВТаблицуФормы("СписокСтандартныйПоискНоменклатура", "ОстатокНаСкладе", "Остаток");
ДобавитьКолонкуВТаблицуФормы("СписокРасширенныйПоискНоменклатура", "ОстатокНаСкладе", "Остаток");
КонецПроцедуры
&НаСервере
Процедура ДобавитьКолонкуВТаблицуФормы(ИмяТаблицыФормы, ИмяПутиКДанным, ЗаголовокКолонки)
ТаблицаФормы = Элементы.Найти(ИмяТаблицыФормы);
Если ТаблицаФормы <> Неопределено Тогда
ИмяНовогоЭлемента = ИмяТаблицыФормы + "_" + ИмяПутиКДанным;
Если Элементы.Найти(ИмяНовогоЭлемента) = Неопределено Тогда
НоваяКолонка = Элементы.Добавить(ИмяНовогоЭлемента, Тип("ПолеФормы"), ТаблицаФормы);
НоваяКолонка.Вид = ВидПоляФормы.ПолеВвода;
НоваяКолонка.ПутьКДанным = ИмяТаблицыФормы + ".Данные." + ИмяПутиКДанным;
НоваяКолонка.Заголовок = ЗаголовокКолонки;
НоваяКолонка.ТолькоПросмотр = Истина;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
При таком подходе вам не нужно беспокоиться о том, что при обновлении формы в основной конфигурации затрутся ваши визуальные доработки. Платформа динамически перестроит интерфейс, добавив нужные элементы управления «на лету».
Иногда корни проблемы лежат глубже — в несоответствии структуры метаданных расширения и основной конфигурации после обновления. Если вы используете обращение к реквизитам динамического списка (например, к полям справочника Номенклатура), убедитесь, что соблюдены следующие правила:
Для обновления режима совместимости и структуры формы в расширении выполните следующие шаги:
Применение описанных практик позволит вам создавать гибкие, отказоустойчивые расширения, которые будут работать корректно вне зависимости от настроек интерфейса у конкретного пользователя и легко перенесут любые последующие обновления конфигурации.