Разберем распространенную задачу по проектированию интерфейсов в системе «1С:Предприятие 8.3». Представим рабочее место диспетчера или оператора, которому необходимо выбрать автомобиль для рейса. Простой отбор, скрывающий занятые машины, лишает пользователя важного контекста. Оператор перестает видеть полную картину: какие машины вообще есть в автопарке, почему нужного автомобиля нет в списке и когда он освободится.
Гораздо более совершенным решением с точки зрения юзабилити является отображение всех элементов справочника, при котором занятые (зарезервированные) позиции визуально затемняются (выделяются серым цветом), а система жестко блокирует возможность их случайного или намеренного выбора. В этой статье мы подробно рассмотрим, как реализовать этот механизм, оптимизировать запрос динамического списка и устранить коварный баг платформы 1С, позволяющий выбирать заблокированные строки.
Для реализации нашей задачи стандартная форма списка справочника не подойдет — нам потребуется перевести список в режим произвольного запроса. Основным источником данных будет выступать сам справочник Справочник.Машины, который мы соединим с регистром сведений, хранящим информацию о занятости транспорта.
Проанализируем важный архитектурный нюанс. В сообщении форума упоминалось использование временных таблиц (ВТ) в запросе динамического списка. Начиная с версии платформы 8.3.8, использование ВТ разрешено, однако стандарты разработки 1С настоятельно рекомендуют избегать временных таблиц в запросах динамических списков. Поскольку динамический список считывает данные порциями (постранично) при прокрутке, СУБД будет вынуждена повторно инициализировать и наполнять временную таблицу при каждом обращении, что приведет к серьезным задержкам интерфейса на больших базах данных.
Правильным решением будет использование обычного ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей СрезПоследних регистра сведений. Посмотрим на пример такого запроса для динамического списка:
ВЫБРАТЬ
СправочникМашины.Ссылка КАК Ссылка,
СправочникМашины.Код КАК Код,
СправочникМашины.Наименование КАК Наименование,
ВЫБОР
КОГДА ЗанятостьМашинСрезПоследних.ПериодЗавершения > &ТекущаяДата
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Занята,
ВЫБОР
КОГДА ЗанятостьМашинСрезПоследних.ПериодЗавершения > &ТекущаяДата
ТОГДА "Занята до " + ВЫРАЗИТЬ(ЗанятостьМашинСрезПоследних.ПериодЗавершения КАК СТРОКА(100))
ИНАЧЕ "Свободна"
КОНЕЦ КАК СтатусТекст
ИЗ
Справочник.Машины КАК СправочникМашины
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗанятостьМашин.СрезПоследних(&ТекущаяДата, ) КАК ЗанятостьМашинСрезПоследних
ПО СправочникМашины.Ссылка = ЗанятостьМашинСрезПоследних.Машина
После настройки запроса необходимо инициализировать параметр ТекущаяДата при создании формы на сервере. Для этого пропишем следующий код:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Инициализируем параметр запроса динамического списка
Список.Параметры.УстановитьЗначениеПараметра("ТекущаяДата", ТекущаяДатаСеанса());
// Вызываем процедуру настройки оформления
НастроитьУсловноеОформление();
КонецПроцедуры
Теперь настроим визуальное отображение. Чтобы зарезервированные машины выглядели тусклыми и не отвлекали внимание, мы задействуем механизм условного оформления динамического списка. Мы можем настроить его интерактивно в свойствах списка, однако программная настройка более надежна и наглядна для разработчика.
Разберем по шагам процедуру настройки программного условного оформления:
&НаСервере
Процедура НастроитьУсловноеОформление()
// 1. Создаем новый элемент условного оформления
ЭлементОформления = Список.УсловноеОформление.Элементы.Добавить();
// 2. Указываем свойства оформления: серый цвет текста и наклонный шрифт
ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", WebЦвета.Серый);
НовыйШрифт = Новый Шрифт(,, Истина); // Наклонный (курсив)
ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Шрифт", НовыйШрифт);
// 3. Задаем условие применения: оформлять только строки, где Занята = Истина
ЭлементУсловия = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементУсловия.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Занята");
ЭлементУсловия.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементУсловия.ПравоеЗначение = Истина;
ЭлементУсловия.Использование = Истина;
// 4. Указываем оформляемые поля формы
ПолеОформленияНаименование = ЭлементОформления.Поля.Элементы.Добавить();
ПолеОформленияНаименование.Поле = Новый ПолеКомпоновкиДанных("Наименование");
ПолеОформленияНаименование.Использование = Истина;
ПолеОформленияКод = ЭлементОформления.Поля.Элементы.Добавить();
ПолеОформленияКод.Поле = Новый ПолеКомпоновкиДанных("Код");
ПолеОформленияКод.Использование = Истина;
ПолеОформленияСтатус = ЭлементОформления.Поля.Элементы.Добавить();
ПолеОформленияСтатус.Поле = Новый ПолеКомпоновкиДанных("СтатусТекст");
ПолеОформленияСтатус.Использование = Истина;
КонецПроцедуры
Выясним причину проблемы, описанной автором в Сообщении 12. Даже если мы заблокируем изменение полей строки, установив в условном оформлении свойство ТолькоПросмотр = Истина или Доступность = Ложь, это не отменит режим выбора самой формы. Когда пользователь открывает группу (папку) в иерархическом списке, фокус автоматически устанавливается на первую строку. Если эта строка занята, нажатие клавиши Enter или двойной клик мыши все равно приведут к выбору этого элемента и закрытию формы.
Чтобы исключить эту уязвимость, необходимо перехватить событие выбора на клиенте и отменить стандартную обработку платформы. Нам потребуются два обработчика событий формы выбора.
Во-первых, переопределим обработчик события Выбор у таблицы формы Список:
&НаКлиенте
Процедура СписокВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
// Проверяем, зарезервирована ли машина
Если ТекущиеДанные.Занята Тогда
// Отменяем стандартный выбор 1С
СтандартнаяОбработка = Ложь;
// Предупреждаем оператора
ПоказатьПредупреждение(, "Этот автомобиль сейчас зарезервирован! Пожалуйста, выберите свободный.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Во-вторых, оператор может выделить занятую строку и нажать стандартную кнопку «Выбрать» на командной панели формы. Чтобы перекрыть этот путь обхода, добавим проверку в обработчик события формы ПередЗакрытием:
&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершитьРаботу, ТекстПредупреждения, СтандартнаяОбработка)
// Проверяем, что форма закрывается именно в результате выбора элемента
Если ЗакрыватьПриВыборе Тогда
ТекущиеДанные = Элементы.Список.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено И ТекущиеДанные.Занята Тогда
Отказ = Истина;
ПоказатьПредупреждение(, "Выбор невозможен! Выбранный автомобиль занят.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Опытный пользователь может обойти форму выбора целиком. Если он помнит код или госномер занятой машины, он может начать вводить его клавиатурой непосредственно в поле ввода документа. Сработает стандартный механизм автоподбора («ввод по строке»), форма выбора не откроется, и заблокированный элемент успешно запишется в документ.
Для создания полноценной защиты бизнес-логики необходимо реализовать проверку в двух местах:
Машины для исключения занятых элементов из быстрого автоподбора.Рассмотрим реализацию обработчика в модуле менеджера справочника Машины:
Процедура ОбработкаПолученияДанныхВыбора(ДанныеВыбора, Параметры, СтандартнаяОбработка)
// Отменяем стандартную генерацию данных автоподбора
СтандартнаяОбработка = Ложь;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
| СправочникМашины.Ссылка КАК Ссылка,
| СправочникМашины.Наименование КАК Представление
|ИЗ
| Справочник.Машины КАК СправочникМашины
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗанятостьМашин.СрезПоследних(&ТекущаяДата, ) КАК ЗанятостьМашинСрезПоследних
| ПО СправочникМашины.Ссылка = ЗанятостьМашинСрезПоследних.Машина
|ГДЕ
| СправочникМашины.Наименование ПОДОБНО &СтрокаПоиска + ""%""
| И (ЗанятостьМашинСрезПоследних.ПериодЗавершения ЕСТЬ NULL
| ИЛИ ЗанятостьМашинСрезПоследних.ПериодЗавершения <= &ТекущаяДата)";
Запрос.УстановитьПараметр("СтрокаПоиска", Параметры.СтрокаПоиска);
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса());
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
СписокВыбора = Новый СписокЗначений;
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
СписокВыбора.Добавить(СтрокаТЗ.Ссылка, СтрокаТЗ.Представление);
КонецЦикла;
ДанныеВыбора = СписокВыбора;
КонецПроцедуры
Теперь реализуем проверку на форме документа в событии ОбработкаВыбора поля ввода машины. Это гарантирует, что даже при копировании строк или обходных путях занятая машина не попадет в документ:
&НаКлиенте
Процедура АвтомобильОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
Если ЗначениеЗаполнено(ВыбранноеЗначение) Тогда
// Проверяем статус занятости на сервере
Если ПроверитьЗанятостьАвтомобиля(ВыбранноеЗначение) Тогда
СтандартнаяОбработка = Ложь; // Отменяем выбор
Элемент.Очистить();
ПоказатьПредупреждение(, "Выбранный автомобиль зарезервирован и не может быть добавлен!");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьЗанятостьАвтомобиля(Знач Автомобиль)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИСТИНА КАК Занята
|ИЗ
| РегистрСведений.ЗанятостьМашин.СрезПоследних(&ТекущаяДата, Машина = &Автомобиль) КАК ЗанятостьМашинСрезПоследних
|ГДЕ
| ЗанятостьМашинСрезПоследних.ПериодЗавершения > &ТекущаяДата";
Запрос.УстановитьПараметр("Автомобиль", Автомобиль);
Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДатаСеанса());
Результат = Запрос.Выполнить();
Возврат Не Результат.Пустой();
КонецФункции
Для идеального удобства пользователя настроим динамическое отображение всплывающей подсказки. В условном оформлении, которое мы создали во втором шаге, можно дополнительно использовать свойство ТекстАссоциативнойПодсказки. Добавим строку кода в процедуру НастроитьУсловноеОформление():
// Добавляем понятный текст подсказки при наведении на серые строки
ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ТекстАссоциативнойПодсказки", "Автомобиль зарезервирован под другой рейс. Выберите другую машину.");
Пользователю больше не придется гадать, почему строка окрашена в серый цвет — при наведении курсора система сама подскажет причину.
Мы спроектировали и внедрили элегантное интерфейсное решение. Комбинация произвольного запроса динамического списка с левым соединением обеспечивает высокое быстродействие системы. Настройка условного оформления сделала интерфейс интуитивно понятным, а перехват событий на клиенте и сервере полностью исключил возможность ошибочного ввода зарезервированных данных.