Как в управляемых формах 1С 8.3 лучше сделать строковое поле ввода с историей введённых значений?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки интерфейсов на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с задачей повышения удобства ввода данных (UX). Типичный пример: пользователю необходимо вводить в текстовое поле пути к сетевым каталогам, шаблоны комментариев, имена водителей или другие часто повторяющиеся строки. Чтобы избавить пользователей от рутинного ручного ввода, логично реализовать механизм автосохранения истории ввода с возможностью выбора из выпадающего списка. Для комплексного исключения рутины при переносе данных между окнами или заполнении форм применяются программные роботы на платформе One RPA.

Существует несколько концептуально разных подходов к решению этой задачи. Мы можем использовать метаданные конфигурации (создать вспомогательный справочник или регистр сведений), извлекать данные динамически из уже существующих документов или задействовать штатные механизмы сохранения настроек платформы 1С. Давайте подробно разберем каждый из этих вариантов, оценим их плюсы и минусы, а также изучим примеры программного кода.

Метод 1. Динамический запрос к существующим документам (Без создания новых объектов)

Если вводимые данные уже сохраняются в реквизитах документов или справочников базы данных, создавать для них отдельное хранилище не имеет смысла. Мы можем извлекать историю ввода на лету, выполнив запрос к существующим таблицам. Рассмотрим этот вариант подробнее.

При открытии формы документа мы можем программно заполнить свойство СписокВыбора нашего текстового поля, выбрав последние уникальные значения из базы данных за определенный период.

Разберем по шагам реализацию данного метода:

  1. Создадим на сервере процедуру, которая выбирает уникальные непустые значения строкового поля из последних документов.
  2. Сгруппируем данные по значению и упорядочим их по убыванию даты документа, чтобы наиболее свежие варианты находились вверху списка.
  3. Заполним свойство элемента формы СписокВыбора полученными результатами.

Посмотрим на пример кода, реализующий этот подход:


&НаСервере
Процедура ЗаполнитьСписокВыбораИзДокументовНаСервере()
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ РАЗЛИЧНЫЕ ПЕРВЫЕ 10
		|	РеализацияТоваровУслуг.ПутьККаталогу КАК ПутьККаталогу,
		|	МАКСИМУМ(РеализацияТоваровУслуг.Ссылка.Дата) КАК ДатаДокумента
		|ИЗ
		|	Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
		|ГДЕ
		|	РеализацияТоваровУслуг.ПутьККаталогу <> """"
		|СГРУППИРОВАТЬ ПО
		|	РеализацияТоваровУслуг.ПутьККаталогу
		|УПОРЯДОЧИТЬ ПО
		|	ДатаДокумента УБЫВ";
	
	РезультатЗапроса = Запрос.Выполнить();
	Если Не РезультатЗапроса.Пустой() Тогда
		Выборка = РезультатЗапроса.Выбрать();
		СписокВыбораЭлемента = Элементы.ПутьККаталогу.СписокВыбора;
		СписокВыбораЭлемента.Очистить();
		
		Пока Выборка.Следующий() Цикл
			СписокВыбораЭлемента.Добавить(Выборка.ПутьККаталогу);
		КонецЦикла;
	КонецЕсли;
	
КонецПроцедуры

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	ЗаполнитьСписокВыбораИзДокументовНаСервере();
КонецПроцедуры

Преимущества метода: Абсолютная чистота конфигурации. Нам не требуется вносить изменения в структуру метаданных, создавать новые справочники или регистры. Соответственно, размер базы данных никак не увеличивается за счет вспомогательной информации.

Недостатки метода: На больших базах данных запрос к таблицам документов за длительный период может вызывать существенные задержки при открытии формы. Кроме того, если пользователь еще не создавал документов, список выбора окажется пустым.

Метод 2. Использование регистра сведений (Классический прикладной подход)

Если данные не хранятся в базе или их получение запросом нецелесообразно из-за низкой производительности, можно создать специальный регистр сведений. Этот способ наиболее гибок, когда историю необходимо хранить централизованно.

Создадим новый непериодический независимый регистр сведений ИсторияВводаДанных со следующей структурой:

Проанализируем программную реализацию записи и чтения данных из этого регистра.

При изменении текстового поля на клиенте мы должны передать новое значение на сервер и записать его в регистр сведений:


&НаКлиенте
Процедура ПутьККаталогуПриИзменении(Элемент)
	Если Не ПустаяСтрока(Объект.ПутьККаталогу) Тогда
		ЗаписатьЗначениеВИсториюНаСервере(Объект.ПутьККаталогу);
	КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаписатьЗначениеВИсториюНаСервере(Значение)
	
	МенеджерЗаписи = РегистрыСведений.ИсторияВводаДанных.СоздатьМенеджерЗаписи();
	МенеджерЗаписи.Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
	МенеджерЗаписи.ИмяПоля     = "ПутьККаталогу";
	МенеджерЗаписи.ЗначениеСтроки = Значение;
	МенеджерЗаписи.ДатаВвода   = ТекущаяДатаСеанса();
	
	Попытка
		МенеджерЗаписи.Записать(Истина);
	Исключение
		ЗаписьЖурналаРегистрации("ИсторияВвода.ОшибкаЗаписи", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
	КонецПопытки;
	
КонецПроцедуры

При создании формы на сервере мы считываем накопленную историю, ограничивая выборку последними 5–10 записями:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ ПЕРВЫЕ 10
		|	ИсторияВводаДанных.ЗначениеСтроки КАК ЗначениеСтроки
		|ИЗ
		|	РегистрСведений.ИсторияВводаДанных КАК ИсторияВводаДанных
		|ГДЕ
		|	ИсторияВводаДанных.Пользователь = &ТекущийПользователь
		|	И ИсторияВводаДанных.ИмяПоля = ""ПутьККаталогу""
		|УПОРЯДОЧИТЬ ПО
		|	ИсторияВводаДанных.ДатаВвода УБЫВ";
	
	Запрос.УстановитьПараметр("ТекущийПользователь", Справочники.Пользователи.НайтиПоРеквизиту("ИдентификаторПользователяИБ", ПользователиИнформационнойБазы.ТекущийПользователь().УникальныйИдентификатор()));
	
	РезультатЗапроса = Запрос.Выполнить();
	Если Не РезультатЗапроса.Пустой() Тогда
		Выборка = РезультатЗапроса.Выбрать();
		СписокВыбора = Элементы.ПутьККаталогу.СписокВыбора;
		СписокВыбора.Очистить();
		Пока Выборка.Следующий() Цикл
			СписокВыбора.Добавить(Выборка.ЗначениеСтроки);
		КонецЦикла;
	КонецЕсли;
	
КонецПроцедуры

Преимущества метода: Высокая скорость работы. Механизм легко масштабируется на любые другие строковые поля ввода в конфигурации. Записи хранятся индивидуально для каждого пользователя.

Недостатки метода: Требует добавления нового объекта метаданных в конфигурацию, что может усложнить процесс поддержки нетиповых решений.

Метод 3. Хранилище системных настроек (Наиболее чистое и гибкое программное решение)

Платформа 1С предлагает встроенный механизм сохранения индивидуальных параметров без изменения структуры базы данных — ХранилищеСистемныхНастроек. Это идеальный способ организации кэширования для интерфейсных задач. Для сохранения настроек формы средствами БСП без изменения структуры метаданных можно использовать обработку для программного сохранения настроек формы.

При таком подходе история ввода хранится в виде объекта СписокЗначений или обычного Массив непосредственно в системных таблицах настроек платформы в разрезе пользователей ИБ.

Давайте подробно разберем, как реализовать сохранение и извлечение истории через системное хранилище:


&НаСервере
Процедура ЗагрузитьИсториюИзХранилищаНастроек()
	
	КлючНастройки = "ИсторияПутейККаталогам";
	// Загружаем сохраненный ранее список значений из системного хранилища
	СохраненнаяИстория = ХранилищеСистемныхНастроек.Загрузить(КлючНастройки, "");
	
	Если ТипЗнч(СохраненнаяИстория) = Тип("СписокЗначений") Тогда
		// Заполняем список выбора элемента формы
		Для Каждого ЭлементСписка Из СохраненнаяИстория Цикл
			Элементы.ПутьККаталогу.СписокВыбора.Добавить(ЭлементСписка.Значение);
		КонецЦикла;
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура ПутьККаталогуПриИзменении(Элемент)
	
	ВведенноеЗначение = Объект.ПутьККаталогу;
	Если ПустаяСтрока(ВведенноеЗначение) Тогда
		Возврат;
	КонецЕсли;
	
	СохранитьЗначениеВХранилищеНастроек(ВведенноеЗначение);
	
КонецПроцедуры

&НаСервереБезКонтекста
Процедура СохранитьЗначениеВХранилищеНастроек(НовоеЗначение)
	
	КлючНастройки = "ИсторияПутейККаталогам";
	// Считываем текущую историю
	СохраненнаяИстория = ХранилищеСистемныхНастроек.Загрузить(КлючНастройки, "");
	
	Если Не ТипЗнч(СохраненнаяИстория) = Тип("СписокЗначений") Тогда
		СохраненнаяИстория = Новый СписокЗначений;
	КонецЕсли;
	
	// Удаляем дубликат значения, чтобы переместить его в начало списка
	НайденныйЭлемент = СохраненнаяИстория.НайтиПоЗначению(НовоеЗначение);
	Если НайденныйЭлемент <> Неопределено Тогда
		СохраненнаяИстория.Удалить(НайденныйЭлемент);
	КонецЕсли;
	
	// Вставляем новое значение на первое место (индекс 0)
	СохраненнаяИстория.Вставить(0, НовоеЗначение);
	
	// Ограничиваем количество хранимых строк (например, оставляем последние 10 записей)
	Пока СохраненнаяИстория.Количество() > 10 Цикл
		СохраненнаяИстория.Удалить(СохраненнаяИстория.Количество() - 1);
	КонецЦикла;
	
	// Записываем обновленный список значений обратно в системные настройки
	ХранилищеСистемныхНастроек.Сохранить(КлючНастройки, "", СохраненнаяИстория);
	
КонецПроцедуры

Преимущества метода: Метод не раздувает базу данных прикладными объектами, работает быстро, изолирован в рамках сеанса конкретного пользователя и легко переносится между конфигурациями.

Метод 4. Сохранение данных в настройках управляемой формы

Если история ввода нужна только в рамках одной конкретной формы (например, в обработке выгрузки данных), мы можем воспользоваться штатным свойством управляемой формы «Сохранение данных в настройках». При разработке инструментов миграции стоит также изучить раздел с решениями по переносу данных 1С по тарифу PRO, где механизмы настройки параметров уже предусмотрены.

Поскольку платформа автоматически умеет сохранять в настройках только текущее (последнее) значение строкового поля, нам придется вручную перехватить события загрузки и сохранения настроек для сериализации всей накопленной истории:

  1. В свойствах управляемой формы включим свойство Сохранение данных в настройках в значение Использовать или Использовать список.
  2. В обработчике события формы ПриСохраненииДанныхВНастройкахНаСервере упакуем накопленный список выбора в структуру настроек формы.
  3. В обработчике события ПриЗагрузкеДанныхИзНастроекНаСервере восстановим элементы списка выбора.

Посмотрим, как выглядит программная реализация этой логики:


&НаСервере
Процедура ПриСохраненииДанныхВНастройкахНаСервере(Настройки)
	
	// Преобразуем список выбора элемента формы в массив для сериализации
	МассивИстории = Новый Массив;
	Для Каждого ЭлементСписка Из Элементы.ПутьККаталогу.СписокВыбора Цикл
		МассивИстории.Добавить(ЭлементСписка.Значение);
	КонецЦикла;
	
	Настройки.Вставить("ИсторияПутиККаталогу", МассивИстории);
	
КонецПроцедуры

&НаСервере
Процедура ПриЗагрузкеДанныхИзНастроекНаСервере(Настройки)
	
	МассивИстории = Неопределено;
	Если Настройки.Свойство("ИсторияПутиККаталогу", МассивИстории) И ТипЗнч(МассивИстории) = Тип("Массив") Тогда
		Элементы.ПутьККаталогу.СписокВыбора.Очистить();
		Для Каждого ЗначениеИзИстории Из МассивИстории Цикл
			Элементы.ПутьККаталогу.СписокВыбора.Добавить(ЗначениеИзИстории);
		КонецЦикла;
	КонецЕсли;
	
КонецПроцедуры

Метод 5. Реализация "умного" автоподбора при вводе символов

Чтобы предоставить пользователю наиболее удобный инструмент работы со строковыми полями ввода (особенно при работе с длинными путями к файлам), рекомендуется использовать механизм автоподбора.

Штатный поиск в списках выбора часто ищет только по началу строки. Если переопределить стандартное событие АвтоПодбор, мы сможем организовать поиск по любой части строки. Если пользователь помнит только название конечной папки в середине длинного пути, система все равно успешно найдет этот путь в истории.

Реализуем это с помощью функции СтрНайти в обработчике события:


&НаКлиенте
Процедура ПутьККаталогуАвтоПодбор(Элемент, Текст, ДанныеВыбора, Параметры, Ожидание, СтандартнаяОбработка)
	
	// Отключаем стандартный поиск платформы по началу строки
	СтандартнаяОбработка = Ложь;
	
	СписокПодобраныхЗначений = Новый СписокЗначений;
	ТекстПоиска = НРег(Текст);
	
	// Перебираем элементы списка выбора, сохраненные в элементе управления
	Для Каждого ЭлементИстории Из Элементы.ПутьККаталогу.СписокВыбора Цикл
		ТекущаяСтрока = ЭлементИстории.Значение;
		
		// Осуществляем поиск подстроки без учета регистра
		Если СтрНайти(НРег(ТекущаяСтрока), ТекстПоиска) > 0 Тогда
			СписокПодобраныхЗначений.Добавить(ТекущаяСтрока);
		КонецЕсли;
	КонецЦикла;
	
	// Возвращаем отфильтрованные варианты в выпадающий список выбора
	ДанныеВыбора = СписокПодобраныхЗначений;
	
КонецПроцедуры

Особенности работы в интерфейсах "НЕ Такси"

В исходном обсуждении авторы отмечают важную деталь: конфигурация работает в режиме управляемых форм, но в интерфейсе, отличном от "Такси" (например, классический интерфейс «Формы в закладках»). В таком режиме поведение выпадающих списков имеет ряд важных особенностей, о которых нужно помнить разработчику:

Резюме и рекомендации

Мы проанализировали различные варианты решения задачи. Какой же метод выбрать?

Если данные уже хранятся в документах, а сама информационная база небольшая — смело используйте Метод 1 (динамический запрос при открытии формы).

Если вам необходим абсолютно независимый кэш для конкретного пользователя, который не затрагивает структуру метаданных конфигурации, идеальным решением станет использование Метода 3 (Хранилище системных настроек) совместно с программным автоподбором (Метод 5). Это обеспечит максимальное быстродействие и удобство для конечных пользователей системы. В дополнение к интерфейсным доработкам, контроль качества перенесенной информации обеспечивает программа МС:Автовыверка для автоматического поиска расхождений между базами.

← На главную