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

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




