Перейти к основному содержанию
`Ctrl`+`K`

# Выборка

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

Выборка определяет:

- Способ получения данных;

- Способ отображения данных пользователю;

- Бизнес-логику обработки пользовательских действий.

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

## Отображения

Отображение — группирует бизнес-логику выборки в зависимости от способа представления данных.

Стандартные отображения для класса:

- `Default` — отображение по умолчанию.
  В данном отображении определена общая функциональность для данных выборки.
  Остальные отображения выборки обычно наследуют это отображение;

- `List` — отображение для списка объектов класса;

- `Card` — отображение для карточки объекта;

- и т.д.

## Отношение экземпляров выборок

Экземпляры выборок, открытые в приложении, связаны друг с другом в отношении `мастер-деталь`. Это означает, что одна выборка всегда является подчинённой к другой выборке. Подчинённая выборка имеет доступ к параметрам и данным мастер-выборки.

Корнем выборок экземпляра приложения является выборка главного меню.
Все остальные выборки являются подчинёнными к ней.
Выборка, являющаяся корнем формы, имеет доступ к данным главного меню.
Экземпляры выборок внутри формы также выстраиваются в иерархии `мастер-деталь` в соответствии с бизнес-логикой отображения.

## Доступность параметров главной выборки в подчинённых

Получение значения параметра осуществляется с помощью метода `getVar()` и преобразованием результата к нужному типу.

Иерархическая структура создаваемых объектов (выборок) позволяет обеспечивать доступность и передачу параметров по отношению «мастер-деталь», а также производить автоматическое обновление при изменении используемых параметров. Параметры экземпляров выборок — это, прежде всего, атрибуты (поля) их датасета. Также это могут быть дополнительно созданные параметры или специальные параметры фреймов.

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

Обращение к параметру непосредственного мастера осуществляется прибавлением к имени параметра префикса `super$`.
При этом, если необходимо обратиться к параметру мастер-выборки своей собственной мастер-выборки (т.е. перепрыгнуть через один экземпляр выборки), необходимо использовать префикс `super$super$`.

В случае, если при любом из обращений к параметру он не будет найден, произойдёт автоматический поиск параметра по дереву отношений «мастер-деталь» выборок вплоть до тех пор, пока мы не найдём этот параметр или не дойдём до главной выборки приложения. Поэтому при создании глобальных параметров на уровне главной выборки приложения для них имеет смысл давать уникальное имя, а затем обращаться к ним из любого места приложения, используя один единственный префикс `super$`.

Пример получения строкового параметра из мастер-выборки:

    getVar("super$sCaption").asNString

## Передача параметров в выборку

При открытии выборки в неё можно передать карту с дополнительными параметрами, используя метод `params()`.

Пример кода:

    Bs_GoodsAvi.defCard.newForm().params(Map(CardRep.IdItemSharp -> idvGds, CardRep.EditingType -> EditingType.edit)).open()

В случае такой передачи параметров они становятся доступны в открытом отображении.

## Открытие выборок в различных режимах

При открытии выборки в любом из режимов:

- `newForm().open()`;

- `newForm().openModal()`;

- `newForm().openLookup()`,

необходимо учитывать, что родителем для открывшейся выборки будет являться `***_MainMenu`, а сама открывшаяся выборка будет главной формой (`selection.isMainOnForm = true`).

Поэтому открывать выборки, в которых установлены зависимости от параметров (`"super$..."`) родительской выборки, без самой родительской выборки нельзя, т.к. никаким способом в открывающуюся выборку не получится передать подобные параметры.

## Разметка выборки

Фреймворк использует систему декларативной разметки выборок. Это позволяет ускорить разработку визуальных форм, т.к. разработчику не требуется заниматься низкоуровневым программированием.

Разметка выборки задаёт правила отображения данных выборки и её дочерних элементов в `xml`-файле с расширением `.avm.xml`.

Ключевые элементы разметки:

- Отображения;

- Компоновка формы;

- Фрейм — задаёт тип представляемых данных (список, дерево и т.д.);

- Атрибуты;

- Операции;

- Фильтры.

### Разметка карточки. Контейнеры

Существует два режима разметки отображения:

1. Разметка на основе настроек атрибутов — старый режим разметки, основанный на порядковом номере и поле `isLastInLine` атрибутов;

2. Разметка на основе контейнеров — новый режим, основанный на разметке с помощью контейнеров, указанных внутри тега `<card>`.

Разметка атрибутов может быть задана с помощью следующих контейнеров:

- `hBox` — горизонтальный контейнер: объекты внутри располагаются друг за другом горизонтально;

- `vBox` — вертикальный контейнер: объекты внутри располагаются друг за другом вертикально;

- `vGroup` — группа объединения: по сути тот же вертикальный контейнер, но визуально обводит содержимое рамкой и может иметь наименование;

- `vSection` — сворачиваемая секция: вертикальный контейнер, который имеет наименование и функцию сворачивания, которая скрывает содержимое. Можно указать, как по умолчанию будет отображаться секция — свёрнутой или развёрнутой.

Контейнеры могут быть вложены друг в друга.

Пример разметки с помощью контейнеров:

    <representation editMode="edit" name="Card" stdFilter.isAvailable="false">
            <layout>
                <simpleComposer>
                    <frame filter.isVisible="false">
                        <card>
                            <layout>
                                <vBox>
                                    <hBox>
                                        <vBox>
                                            <attr name="idCustomerHL"/>
                                            <attr name="dOutDate"/>
                                        </vBox>
                                        <vBox>
                                            <attr name="gidSrcDoc" visible="true"/>
                                            <attr name="nOrder"/>
                                        </vBox>
                                    </hBox>
                                        <vGroup caption="Приемка">
                                            <attr name="idCustomerHL"/>
                                            <attr name="dExecuteDate"/>
                                        </vGroup>
                                </vBox>
                                <vSection caption="Примечание" collapsed="false">
                                    <attr name="sComment" caption="Комментарий"/>
                                </vSection>
                            </layout>
                        </card>
                    </frame>
                </simpleComposer>
            </layout>
        <attributes/>
    <operations/>
    </representation>

### Использование шаблонов в разметке

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

Для использования шаблонов языка шаблонов необходимо подключить в выборку пространство имён:
`xmlns:th="http://www.global-system.ru/xsd/global3-view-template-1.0"`

### Наследование разметки

Для наследования разметки другой выборки необходимо:

1. Подключить пространство имён языка шаблонов;

2. Добавить к тегу `view` указание о наследовании другой разметки `th:extends="Some_Other.avm.xml"`.

Полный пример xml с наследованием:

    <view xmlns="http://www.global-system.ru/xsd/global3-view-1.0"
          xmlns:th="http://www.global-system.ru/xsd/global3-view-template-1.0"
          name="RplTst_AllDbTypes" th:extends="RplTst_AllDbTypes.dvm.xml">
      <representation name="Default"/>
    </view>

Алгоритм наследования выборки:

1. При отсутствии наследования — скопировать отображение предка в наследника.
   Копирование происходит в случае, если в наследнике нет данного отображения;

2. Произвести наследование отображений.
   Наследование производится в случае, если и в предке, и в потомке есть отображение с тем же именем;

3. Выполнить оставшиеся команды языка разметки.

Алгоритм наследования отображений:

1. При отсутствии — скопировать:

   - `levelGroups`;

   - `bandGroups`;

   - `layout`;

   - `filter`;

2. Добавить отсутствующие атрибуты;

3. Добавить отсутствующие операции.

## Операции

Выборка взаимодействует с программным окружением системы с помощью операций. Операция — это некая обработка того или иного события, вызванного пользователем или каким-то процессом. Операции бывают служебными, выполняя логику обработки событий, или пользовательскими, срабатывающими в результате явного действия пользователя.

### Классификация операций

- Базовые операции — операции, предназначенные для работы с набором данных. Они отвечают за добавление, удаление, обновление записей, получение данных, навигацию по датасету и т.п. Эти операции, как правило, уже определены на уровне базовых выборок-предков. Некоторые из этих операций срабатывают автоматически на выполнение определённых пользовательских действий (`BeforeEdit` при начале редактирования, `AfterEdit` при завершении, `CheckWorkAbility` при перемещении с записи на запись);

- Служебные операции — операции, выполняющие строго определённые вспомогательные действия;

- Системные операции — операции, создающиеся автоматически;

- Сеттеры — операции, предназначенные для изменения значений атрибутов (полей) выборки. При вводе значения в какое-то поле формы вызывается связанная с данным полем операция (сеттер), которая по умолчанию вызывает серверные методы изменения значений атрибутов класса. Сеттеры для атрибутов класса создаются автоматически;

- Операции фильтров — операции, необходимые для работы с фильтрами;

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

### Базовые операции выборки

Для автоматического срабатывания операций при различных системных событиях ключевой параметр — это их системное имя. Базовые и служебные операции выборки имеют предопределённые системные имена.

- onRefresh — операция, загружающая данные в датасет. При повторном вызове производит обновление всего набора данных (`refresh`);

- onRefreshItem — операция обновления одной (текущей) записи;

- onRefreshExt — операция с дополнительным запросом к базе для получения полей-заголовков ссылочных объектов. Вызывается только для объектных запросов в `onRefresh`/`onRefreshItem`.
  Для использования в запросе атрибутов класса необходимо передать их в блоке `with`. При этом обязательно для НЕ `Long` атрибутов нужно явно указывать тип в аннотации `/*@...*/`:

  - Для `Varchar` атрибутов — `"String"`, `"NString"` или `"varchar"`, например — `/*@NString*/`;

  - Для `Number` атрибутов — `"Number"`, например — `/*@Number*/`.
    В противном случае атрибуты попытаются преобразоваться к `bigint` типу;

- beforeEdit — операция, выполняемая перед началом редактирования записи для выполнения подготовительных действий перед началом редактирования. Автоматически выполняется в момент начала внесения изменений в объект;

- afterEdit — операция финальной проверки ввода записи. Срабатывает автоматически при попытке перейти с отредактированной записи на другую запись выборки или при закрытии выборки;

- insert — операция вставки новой записи в выборку;

- delete — операция удаления записи;

- checkWorkAbility — операция, предназначенная для осуществления проверок изменения состояния операций выборок, доступности редактирования её атрибутов. Вызывается автоматически при переходе с записи на запись, при открытии выборки, а также после выполнения операции, для которой выставлен флаг «выполнять операцию применимости после выполнения операции»;

- onLoadMeta — операция, срабатывающая при загрузке метаданных. Т.к. метаданные загружаются только один раз при первом открытии экземпляра выборки на фрейме, то и данная операция срабатывает только один раз;

- onUnloadMeta — операция, срабатывающая при выгрузке метаданных;

- beforeOpen — операция, срабатывающая перед открытием выборки. Данная операция рекомендуется для создания дополнительных параметров, используемых в выборке;

- afterOpen — операция, срабатывающая сразу после открытия выборки. Необходимо использовать для переопределения наименований столбцов выборки и т.д.;

- onShow — операция, срабатывающая сразу после того, как отрисован интерфейс формы и были созданы элементы формы;

- onControllerCreated — операция, срабатывающая сразу после того, был создан фрейм отображения выборки. Эта операция аналогична `OnShow`, но срабатывает в том числе и при открытии интерфейсных элементов формы, которые были скрыты при открытии формы (например, закладки, которые не были первыми в перечне закладок).

### Служебные операции выборки

- saveForm — операция сохраняет данные формы;

- cancelForm — операция отменяет изменения на форме;

- closeFormOk — операция закрытия формы с подтверждением выбора;

- closeFormCancel — операция закрытия формы по кнопке «Выход» или по кнопке закрытия окна «крестик»;

- beforeCloseForm — операция срабатывает перед закрытием формы;

- afterCloseForm — операция срабатывает после закрытия формы;

- onCloseFormQuery — операция вызывается в начале процесса закрытия формы и позволяет отменить закрытие.

### Предопределённые операции выборок

- applyUniFilter — операция применяет настроенные условия фильтрации, выполняется перезапрос данных с наложенными условиями фильтрации;

- resetUniFilter — операция отменяет все наложенные на выборку фильтры;

- clearUniFilter — операция отчищает все условия фильтрации;

- showAuditObject — операция вызывает окно аудита выполняемых пользователями действий в БД с наложенным фильтром по текущему объекту;

- showAboutObject — операция открывает окно системной информации об объекте;

- copyObject — операция копирования объекта;

- cardEdit — операция открытия объекта в карточке, предварительно запросив, какую использовать выборку и отображение;

- allowEdit — разрешение/запрещение редактирования объектов в списке. По умолчанию операция доступна для редактируемых списков;

- showTab — операция открытия/закрытия детальной части.

### Сеттеры изменения атрибутов

Операции установки значений атрибутов (так называемые сеттеры) используются для того, чтобы установить определённое значение в поле записи объекта, соответствующее его типу. Имя сеттера состоит из префикса `set` и имени атрибута `attributeName`: `setattributeName`.

### Специальные операции контролов

- onFocusedFieldChanged — это специальная операция, срабатывающая после перехода фокуса ввода в списке или дереве с одного столбца на другой, а также при переходе с контрола на контрол в карточке или панели фильтров;

- onFocusedCellChanged — это специальная операция, срабатывающая после перехода фокуса ввода в списке с одной ячейки на другую;

- onFrameActivated — специальная операция, срабатывающая при переходе фокуса ввода в грид, дерево, карточку.

### Способы визуализации операций

Существует 4 основных способа визуального отображения операций:

- на тулбаре фрейма;

- в выпадающем (контекстном) списке (popup-меню);

- в качестве пункта главного меню (доступно только для выборок приложений главного меню);

- на навбаре сразу под главным меню (доступно только для выборок приложений главного меню).

Для настройки отображения операции каждым из способов существует собственная настройка при помощи соответствующих свойств операции.

### Пользовательские Jexl-операции

Использование подсистемы пользовательских Jexl-операций позволяет удобно добавлять операции на существующие выборки без вмешательства в прикладной код. Реализовано добавление, удаление и переименование операции.

Примечание

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

#### Свойства Jexl-операции

В таблице описаны основные и полезные свойства операций, определяющие их поведение и отображение в интерфейсе. Все остальные свойства — системные, не редактируются вручную или не влияют на пользовательский опыт.

Свойство | Описание
--- | ---
Наименование | Имя операции. Оно при наведении курсора.
Описание | Пояснение назначения операции.
Порядковый номер | Числовое значение, используемое для сортировки. Чем меньше номер, тем выше (при вертикальном отображении) или левее (при горизонтальном) операция в списке меню.
Иконка | Графическое представление операции. Номер изображения, отображаемого на тулбаре.
Активность | Если свойство выключено, операция не отображается в интерфейсе и недоступна даже при вызове из кода.
Видимость | Если свойство выключено, операция скрыта в интерфейсе, но остаётся доступной при вызове из кода.
Доступность для взаимодействия | Если свойство выключено, операция отображается серым цветом и недоступна для нажатия. Для корректной работы этого свойства должны быть включены одновременно Активность и Видимость.
Флаг (checked) | Указывает, что операция является флагом. При нажатии на соответствующий пункт меню будет установлена/снята галочка.
Вызвать checkWorkability | После выполнения операции автоматически запускается проверка работоспособности.
Обновить запись после выполнения | После выполнения операции запись автоматически обновляется.
Обновить запись перед выполнением | Перед запуском операции обновляются данные записи, с которой она работает. Это полезно, если предыдущие операции не обновляли данные.
Видимость операции на всех панелях | Операция сразу отображается на всех основных панелях интерфейса.
Видимость в главном меню | Операция отображается в верхней панели интерфейса.
Видимость на панели быстрого запуска | Операция отображается на второй панели сверху.
Видимость в контекстном меню | Операция отображается в меню, вызываемом правой кнопкой мыши.
Видимость на панели управления | Операция отображается на панели управления объектом.
Тип привилегий для доступа | Обозначает тип объектной привилегии, которая должна быть выдана пользователю, чтобы он мог видеть операцию в интерфейсе и взаимодействовать с ней.
Комбинация горячих клавиш | Это клавиатурное сочетание, которое вызывает операцию.
Коллекция изображений | Название коллекции изображений, из которой присваивается иконка.
Группа на панели быстрого доступа | Определяет группу, к которой относится операция на панели быстрого запуска.
Операция-предок | Задаёт вложенность операции в дереве интерфейса. Например, если указать Сущности, операция будет отображаться в разделе Сущности вместе с другими операциями этого раздела.

#### Особенности реализации

Каждое свойство, в том числе jexl-скрипт и клонирующий SQL-запрос, хранятся как отдельная строка в базе данных в таблице `Btk_SelOperOverride`. Однако значения jexl-скрипта и клонирующего SQL-запроса хранятся в поле `sJexlScript`, а не в `sValue`, как все остальные параметры операции, так как `sValue` имеет ограничение на длину в 255 символов.

Примечание

Все системные имена jexl-операций начинаются с префикса `"jexl_"`.

Структура таблицы:

- `sPropName` — используется для дифференцирования свойств Jexl-скрипт;

- `sJexlOperScript` — jexl-скрипт операции. Имеет смысл только у свойств с `sPropName` = `sJexl` или `cloneQuery`;

- `sValue` — значение параметра операции;

- `sOperName` — системное имя операции;

- `sMasterSel` — выборка, в которой задано свойство операции;

- `sMasterRep` — отображение, в котором задано свойство операции.

Примечание

- Операция существует в данной выборке и отображении, если существует запись с `sMasterSel`, равным выборке, `sMasterRep` — названию отображения или `Default`, и именем свойства `sPropName = "sJexl"`;

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

## Диалоги

Диалоги позволяют задавать вопросы пользователю и получать от него ответы на эти вопросы.
Для доступа к методам диалогов используется специальный объект `ru.bitec.app.gtk.gl.Dialogs`, который доступен в `Avi` через переменную `dialogs`.
Кроме стандартных диалогов, есть возможность открыть форму в режиме выбора (`openLookup`) и после обработать выбранное пользователем значение, что так же из себя представляет специализированный вид диалогов.

### Стандартные диалоги

- showMessage — выводит на экран текст в модальном окне.
  Пример использования:

      dialogs.showMessage("Текст, который увидит пользователь")

- showMsgDialog — открывает диалог указанного типа с сообщением и указанным перечнем кнопок.
  Возможные типы диалогов см. в `ru.bitec.app.gtk.gl.msgdlg.MsgDlg`.
  Возможные типы кнопок см. в `ru.bitec.app.gtk.gl.msgdlgbutton.MsgDlgButton`.
  Пример использования:

      val dlgRes = dialogs.showMsgDialog(
        //тип диалога "Подтверждение"
        MsgDlg.confirmation,
        //текст диалога
        "Включить автонумерацию?",
        //список кнопок: Да, Нет
        List(MsgDlgButton.yes,MsgDlgButton.no)
      )
      if (dlgRes == MsgDlgButton.yes) {
        //обработка нажатия на кнопку "Да"
      } else if (dlgRes == MsgDlgButton.no) {
        //обработка нажатия на кнопку "Нет"
      }

- showButtonsDialog — открывает диалог с произвольными кнопками.
  Позволяет формировать диалоги, кнопки которых будут не из перечня стандартных кнопок.
  Пример использования:

      dialogs.showButtonsDialog(
        caption = "",
        text = "Удалить форму или убрать ссылку на вид отчетности?",
        colCount = 2,
        buttons = List(List("Удалить"), List("Убрать ссылку")),
        imageCollectionName = "ToolBarPrimaryHot",
        focusButtonNumber = 0L
      ) match {
        case 0L =>
          //обработка нажатия кнопки "Удалить"
        case 1L =>
          //обработка нажатия кнопки "Убрать ссылку"
      }

- showPromptDialog — открывает диалог с запросом ввода строки.
  Пример использования:

      val svDialogRes = dialogs.showPromptDialog("Заголовок окна", "Текст диалога", "Значение по умолчанию").ns
      if (svDialogRes.isNotNullOrEmpty) {
        //обработка введенного значения
      }

- showConfirmDialog — открывает диалог с типом «Подтверждение» и кнопками «Да» и «Нет».
  Пример использования:

      val dialogRes = dialogs.showConfirmDialog("Текст диалога")
      if (dialogRes) {
        //обработка случая, когда пользователь нажал "Да"
      }

- showInfoForm — отображает информационное сообщение с переданным текстом, без возможности его закрытия пользователем.
  Скрывается при вызове метода `hideInfoForm`.
  Применяется, когда в бизнес-логике выполняется продолжительная задача, и требуется проинформировать пользователя об её выполнении.
  Пример использования:

      dialogs.showInfoForm("Внимание, идет загрузка Госреестра типов СИ")
      try {
        //продолжительное действие
        thisApi().download()
      } finally {
        dialogs.hideInfoForm()
      }

- hideInfoForm — скрывает информационное сообщение, см. описание метода `showInfoForm`.

- withInfoForm — метод включает в себя открытие информационного окна и его закрытие после завершения выполнения переданной функции.
  Пример из описания метода `showInfoForm` можно переписать, используя этот метод:

      dialogs.withInfoForm("Внимание, идет загрузка Госреестра типов СИ") {
        //продолжительное действие
        thisApi().download()
      }

- showEditPaintStyleDialog — вызывает диалог настройки стилей раскраски текстовых полей.

### Форма в режиме выбора значения

Одной из типичных задач является предложить выбрать пользователю какой-либо объект и затем сделать обработку его выбора. Например, чаще всего ссылочные поля открывают для выбора список объектов ссылочного класса, а после выбора пользователем значения устанавливают его в текущей форме.

Для реализации такого типа диалогов используется синтаксис открытия формы в режиме `openLookup`.

Пример открытия формы и обработки выбора пользователя:

    //создаем новую форму
    val data = Btk_ClassAvi.listForChoose().newForm()
      //указываем список полей, которые требуется получить из формы
      .results("id" :: "sCaption" :: Nil)
      //открываем форму в режиме выбора
      .openLookup()
    //проверяем, что пользователь подтвердил выбор
    if (data.getLookupResult eq LookupResult.ok) {
      //получаем значение первого поля из списка results
      val id = data.getData(1, 0)
      //получаем значение второго поля из списка results
      val sCaption = data.getData(1, 1)
    }

#### Выбор нескольких строк (мультиселект)

Для того чтобы в форме в режиме выбора пользователь мог выбрать несколько строк, используется опция `useMultiSelect`.

Пример открытия формы в режиме мультиселекта и обработка выбранных значений:

    //создаем новую форму
    val data = Btk_ClassAvi.listForChoose().newForm()
      //указываем список полей, которые требуется получить из формы
      .results("id" :: "sCaption" :: Nil)
      //указываем возможность выбора нескольких строк
      .useMultiSelect
      //открываем форму в режиме выбора
      .openLookup()
    //проверяем, что пользователь подтвердил выбор
    if (data.getLookupResult eq LookupResult.ok) {
      //цикл по выбранным строкам
      for (i <- 1 to data.size) {
        //получаем значение первого поля из списка results для строки i
        val id = data.getData(i, 0)
        //получаем значение второго поля из списка results для строки i
        val sCaption = data.getData(i, 1)
      }
    }

## Мультиселект

Мультиселект — возможность выбрать несколько строк в списках или деревьях.
Для включения мультиселекта в отображении укажите в теге `grid` или `tree` свойство `isMultiSelectEnabled="true"`.

Пример разметки:

    <representation name="List">
      <layout>
        <simpleComposer>
          <frame>
            <grid isMultiSelectEnabled="true"/>
          </frame>
         </simpleComposer>
      </layout>
    </representation>

Пример обработки выделенных строк пользователем и получения значений атрибутов этих строк:

    //цикл по выделенным строкам
    for (i <- 0 until selection.selectedRecordsCount()) {
      //получение значения поля с типом NLong
      val id = NLong.fromAny(selection.selectedValueByName("id", i))
      //получение значения поля с типом NNumber
      val nOrder = NNumber.fromAny(selection.selectedValueByName("nOrder", i))
      //получение значения поля с типом NGid
      val gid = NGid(selection.selectedValueByName("gid", i).asInstanceOf[String])
      //получение значения поля с типом NString
      val sCaption = selection.selectedValueByName("sCaption", i).asInstanceOf[String].ns
      //получение значения поля с типом NDate
      val dDate = NDate(selection.selectedValueByName("dDate", i).asInstanceOf[JDate])
    }

Примечание

Для формы в режиме выбора мультиселект регулируется опцией `useMultiSelect` при создании формы.
Подробнее в главе «Диалоги».

## Настройка стилей

Стиль — набор свойств, включающий в себя:

- `Color` — цвет заднего фона в формате `$00BBGGRR` или Global-константой цвета;

- `FontColor` — цвет шрифта в формате `$00BBGGRR` или Global-константой цвета;

- `FontSize` — размер шрифта натуральным числом;

- `FontItalic` — курсив. Значение `0` — нет курсива, `1` — есть курсив;

- `FontBold` — жирность. Значение `0` — не жирный шрифт, `1` — жирный шрифт;

- `FontUnderLine` — подчёркивание. Значение `0` — нет подчёркивания шрифта, `1` — есть подчёркивание шрифта;

- `FontStrikeOut` — перечеркивание. Значение `0` — нет перечеркивания шрифта, `1` — есть перечеркивание шрифта.

Стиль задаётся строкой формата:

    "param1=value1;param2=value2;param3=value3;...;paramn=valuen"

Пресеты стилей хранятся в классе `Btk_Registry`, их можно использовать для типовых задач.
Стили можно применять в отображении к строкам и столбцам.

### Применение стиля к строке

Чтобы применить стиль к строке, нужно в теге `representation` присвоить свойству `rowStyleAttr` имя атрибута, в котором будет храниться либо готовая строка со стилем, либо системное имя пресета.

    <representation name="List" editMode="notEdit" rowStyleAttr="sStyle1">
            <attributes>
                <attr name="sCode" caption="Код" editorType="edit" order="10" isRequired="true"/>
                <attr name="sCaption" caption="Наименование" editorType="edit" order="20" isRequired="true"/>
                <attr name="sStyle1" caption="Первый стиль" editorType="edit" order="40"/>
            </attributes>
        </representation>

### Применение стиля к столбцу

Чтобы применить стиль к столбцу, нужно в теге `attr` создать тег `style` и в нём использовать либо свойство `name`, либо `attr`.
В свойстве `name` указывается непосредственно строка стиля или системное имя пресета, в `attr` указывается атрибут, где хранится либо строка, либо системное имя.

    <representation name="List" editMode="notEdit">
            <attributes>
                <attr name="sCode" caption="Код" editorType="edit" order="10" isRequired="true">
                    <style name="Color=$00880000"/>
                </attr>
                <attr name="sCaption" caption="Наименование" editorType="edit" order="20" isRequired="true">
                    <style name="BDG_URTotalRow"/>
                </attr>
                <attr name="sStyle1" caption="Первый стиль" editorType="edit" order="40">
                    <style attr="sStyle1"/>
                </attr>
            </attributes>
        </representation>

Также стиль можно менять в avi, используя `selection`:

    selection.attrs("Имя атрибута, на который мы хотим применить стиль").styleAttributeName = "Имя атрибута со строкой стиля или с системным именем пресета"

## Создание строки стиля. StyleBuilder

`StyleBuilder` — класс, позволяющий работать со стилем не как со строкой, а как с объектом, в котором хранятся параметры. Помимо этого в `StyleBuilder` хранятся цветовые и шрифтовые константы для удобства использования.

Внутри класса хранится изменяемый словарь, который хранит пары `название параметра GS -> (значение параметра, название параметра CSS)`.
Для его корректной работы есть вспомогательный неизменяемый словарь, хранящий пары `название параметра GS -> название параметра CSS`.
Все манипуляции со словарём происходят через сеттеры и геттеры. Для создания результирующей строки тоже есть отдельный метод.

Изначально класс создан для стилей, которые используются в Global, но класс также поддерживает создание результирующей строки и для CSS.

### Создание экземпляра StyleBuilder

Для использования `StyleBuilder` нужно сначала создать его экземпляр. Можно создать как пустой стиль:

    val builder = StyleBuilder()
    builder.build() // Результирующая строка пустая

так и взять за основу уже существующую строку стиля:

    val stringStyle = "color=$00FF4400;font=arial;fontsize=15".ns
    val builder = StyleBuilder(stringStyle)
    builder.build() // В результирующей строке будут все те же параметры c теми же значениями,
                    // что и в stringStyle, но, возможно, параметры будут расположены в другом порядке.

### Работа с цветовыми параметрами

Для работы с цветовыми параметрами внутри `StyleBuilder` создан класс `Color`. Методы работы с цветовыми параметрами принимают на вход и возвращают экземпляры этого класса.
На вход в сеттер можно подавать обычную строку типа `String`, она будет неявно преобразована в экземпляр `Color`.

При создании от строки `Color` пытается преобразовать цвет в HEX, если цвет невозможно преобразовать — бросается исключение. `Color` умеет преобразовывать строки типа:

- `$00BBGGRR`;

- `$BBGGRR`;

- `#RRGGBB`;

- Наименование GS-константы.

Из `Color` можно получить строку обратно в формате HEX или GS-кода:

    val color = Color.Red // В Color есть константы GS-цветов
    // ИЛИ
    val color = Color("$000000FF")
    // ИЛИ
    val color = Color("$0000FF")
    // ИЛИ
    val color = Color("#FF0000")
    // ИЛИ
    val color = Color("clRed")
    // -------------------------------
    color.getGsHex // Вернётся $000000FF
    color.getHex // Вернётся #FF0000

Если в `Color` подать пустую строку — это считается как отсутствие цвета.

### Примеры

    val style = StyleBuilder()
      .setBackgroundColor(Color.Blue)
      .setFontColor("#FFAAFF")
      .setFont(Font.Impact)
      .setFontUnderLine(1.nn)
      .build() // style = "color=$00FF0000;fontcolor=$00FFAAFF;fontunderline=1;fontname=Impact"

    val style2 = StyleBuilder(style)
      .setBackgroundColor(Color("")) // Убрать параметр цвета заднего фона
      .setFontColor("$00FFFFFF")
      .setFont("") // Убрать параметр шрифта
      .setFontUnderLine(NNumber()) // Убрать параметр нижнего подчёркивания
      .setFontBold(1.nn)
      .buildCSS // style2 = "color: #FFFFFF;
                //          font-weight: bold;"

## Жизненный цикл формы

## Иконки

Фреймворк поддерживает три варианта подключения иконок:

1. Из ресурсов сервера (основной режим).
   Коллекции изображений берутся из каталога с ресурсами `[G3_HOME]/resources/imagecollection`.
   Каждый подкаталог является коллекцией, вложенные файлы — элементы коллекции, номера которых соответствуют именам файлов;

2. Из базы данных.
   Коллекции изображений загружаются из таблицы `btk_component`, поле `clobdataxml`;

3. Подключение иконки по URL.
   Для иконки указывается явный URL.

### Использование иконок из java-ресурсов модуля

Файлы изображений размещаются в ресурсных каталогах соответствующего модуля.

На скриншоте приведён пример структуры каталогов с ресурсами модуля BTK.
Коллекции изображений размещены в каталоге ресурсов `../ru/bitec/app/btk/images`. Каждой коллекции соответствует одноимённый каталог.

Рассмотрим структуру каталога `ru/bitec/app/btk/images/toolbar`:

- В корне каталога размещаются файлы изображений с минимальным разрешением 16×16 точек;

- В подкаталоге `ru/bitec/app/btk/images/toolbar/24x24` размещаются файлы изображений с разрешением 24×24 точек. В случае необходимости могут быть добавлены каталоги изображений с большим разрешением;

- Подкаталог `ru/bitec/app/btk/images/toolbar/disabled` содержит обесцвеченные копии изображений коллекции `toolbar`, необходимые для отображения неактивных операций на панелях управления.

Пример указания изображения для операции с использованием аннотации:

    @Oper(
      caption = "Род",
      headOperation = "references",
      imageUri ="ru/bitec/app/gs3/images/toolbar/61.png")
    def open_Bs_Kind_RoList(): Unit = {
      Bs_KindAvi.roList().newForm().open()
    }

При одновременном указании свойств `imageIndex` и `imageUri`, второе имеет приоритет.

## Частично-загружаемые деревья

### Введение

Частично-прогружаемое дерево — это такой вид отображения, где список в форме дерева подгружается из базы только для тех записей, которые раскрыты в дереве.
Частично-прогружаемые деревья используются для отображений, в которых заведомо известно, что будет огромное количество записей, например `Mct_Structure`.

### Настройка avm-файла

Для отключения полной загрузки дерева в avm-файле в настройках отображения необходимо указать свойство:

    fetchAllTree="false"

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

    <tree idAttr="gid"
          idParentAttr="gidParent"
          hasChildrenAttr="bHasChild"
    />

### Настройка refresh

Для отображения необходимых записей нужно получить список `idAttr` и `idParentAttr`, по которым будут загружаться данные из базы.
Для этого нужно воспользоваться значениями параметров выборки `OPENNODEIDARRAY` и `OPENNODEID`:

- `OPENNODEIDARRAY` — параметр выборки, хранящий в себе список открытых нод через запятую в формате `ftString`;

- `OPENNODEID` — параметр выборки, передающий `idAttr` открываемой ноды в формате `ftString`.

`OPENNODEIDARRAY` и `OPENNODEID` — системные параметры.
Пользовательская установка значений этих параметров запрещена.

### Пример

Пример запроса для `Mct_StructureAvi#StructureTree`, находящегося в пакете `Mct_StructurePkg.getOnRefreshBs`:

    --п.1.- открытые узлы
    with  nodes as (
      SELECT distinct tVal.tVal as gid
        FROM regexp_split_to_table(
              concat_ws(
                 ', '
                ,:OpenNodeIdArray#
                ,:OpenNodeIdArray
              )
              ,', '
             ) tVal
        where :OpenNodeId is null -- узлы нужны только при рефреше
      union
      select :gidParent as gid
      where :gidCurrentGid# is not null
    ),
    -- фильтрация объектов для вывода в дерево
    gidFlt as (
      select h.gid
            ,h.gidParent
        from (
          -- п.2.  блок рефреша узлов "RefreshNodes"
          select t.gid
                ,case when ll.id is null then null else l.gidParent end as gidParent
            from mct_structure t
            join mct_structurelink l on l.gidstructure=t.gid
            left join mct_structurelink ll on (
                        l.gidparent=ll.gidstructure and
                        l.idviewtype=ll.idviewtype
                      )
            where t.idPrjVer = :flt_idPrjVer
              and l.idviewtype=:flt_idViewType
              and l.gidparent in (select n.gid from nodes n)
          -- Блок OnRefresh
          union all
          --  п.3. 1. Раскрываем заказ – корневые записи
          select t.gid
                ,null as gidParent
            from mct_structure t
            join mct_structurelink l on l.gidstructure=t.gid
            l.idviewtype=ll.idviewtype
        )
          --left join mct_structure tt on (l.gidparent=tt.gid)
        where t.idPrjVer = :flt_idPrjVer
          and l.idviewtype=:flt_idViewType
          and not exists (
              select 1
                from mct_structurelink ll
               where ll.gidstructure=l.gidparent
                 and ll.idviewtype=:flt_idViewType)
                and :OpenNodeId is null
          --
          union all
      --п.4. - 2. Раскрываем потомков – по нажатию ноды
      select l.gidstructure as gid
            ,l.gidparent
        from mct_structurelink l
       where l.idviewtype = :flt_idViewType
         and l.gidparent = :OpenNodeId
    ) h
    --п.5.
    SELECT
           t.id
          ,t.idClass
          ,t.gid
          ,tt.gidParent
          ,(select COALESCE(max(1),0) from mct_structurelink l where
          l.gidparent=t.gid) as bHasChild
          ,t.gidDoc
          ,t.gidDocVer
          ,t.gidSourceObj
          ,t.idPrjVer
          ,t1.sCode as idPrjVerHL
          ,t.idEskd
          ,t2.sCaption as idEskdHL
          ,t.idPosType
          ,t3.sCaption as idPosTypeHL
          ,t.sCode
          ,t.sCaption
          ,t.sSysName
      FROM Mct_Structure t
      join gidFlt tt on t.gid=tt.gid
      LEFT JOIN Bs_PrjVer t1 on t.idPrjVer = t1.id
      LEFT JOIN Mct_Eskd t2 on t.idEskd = t2.id
      LEFT JOIN Mct_PosType t3 on t.idPosType = t3.id
      LEFT JOIN Bs_Goods t4 on t.idGds = t4.id
      LEFT JOIN Msr_MeasureItem t5 on t.idMsr = t5.id
      LEFT JOIN Mct_OrderSheet t6 on t.idOrderSheet = t6.id

- п.1 — получение таблицы значений открытых нод с учётом открытия ноды для новой созданной записи уровнем ниже;

- п.2 — получение записей, которые входят в открытые ноды;

- п.3 — получение корневых записей дерева;

- п.4 — получение записей, которые входят в открытую ноду — блок, срабатывающий при первом раскрытии ноды;

- п.5 — основной запрос для получения необходимых колонок.

Так же важно обратить внимание, что в основном запросе присутствуют изменения:

- Добавлен атрибут `bHasChild` — признак наличия дочерних элементов.
  Текст запроса атрибута:

      (select COALESCE(max(1),0) from mct_structurelink l where
        l.gidparent=t.gid) as bHasChild

- Добавлено ограничение получения записей по отфильтрованному списку:

      join gidFlt tt on t.gid = tt.gid

### Загрузка дочерних записей на открытие

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

Примечание

Изменение параметров, участвующих в запросе, спровоцирует операцию `refresh`!

Для установки параметров без вызова `refresh` необходимо воспользоваться конструкцией:

    try {
      selection.ignoreParamChange = true
      setVar("gidParent", curGidParent)
      setVar("gidCurrentGid#", thisRop().gid)
    }
    finally {
      selection.ignoreParamChange = false
    }

## Пример разметки выборки

    <?xml version="1.0"?>
    <view xmlns="http://www.global-system.ru/xsd/global3-view-1.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.global-system.ru/xsd/global3-view-1.0"
          name="Bs_GdsCostDeviationType">
        <representation name="Default" doubleClickOperation.createFormMode="CardEdit"
                        doubleClickOperation.lookupMode="CloseFormOK"
                        caption="Виды отклонений в стоимости ТМЦ">
            <filter name="Bs_GdsCostDeviationTypeFilter">
                <macros name="DefFltReferenceMacro">
                    <condition logicalOperator="and" id="shownotused"
                               isExpression="true"
                               expression="(:filter$flt_bShowNotUsed = 1
                                               or
                                            (coalesce(t.bnotactive,0) = 0
                                             and (t.dexpirydate is null or t.dexpirydate > current_date)
                                                )
                                              )">
                        <filterAttr name="flt_bShowNotUsed" attribute-type="Long" caption="Отображать неиспользуемые"
                                    isLastInLine="false" order="10" defaultValue="0" editorType="check"/>
                    </condition>
                </macros>
            </filter>
            <layout>
                <simpleComposer>
                    <frame filter.isVisible="true" toolBar.isVisible="true">
                        <grid/>
                    </frame>
                </simpleComposer>
            </layout>
            <attributes>
                <attr name="id" caption="Идентификатор" isVisible="false" editorType="edit" order="-1"/>
                <attr name="idClass" caption="idClass" isVisible="false" editorType="edit" order="-2"/>
                <attr name="gid" isVisible="false" editorType="edit"/>
                <attr name="sCode" caption="Код" editorType="edit" order="10" isRequired="true"/>
                <attr name="sCaption" caption="Наименование" editorType="edit" order="20" isRequired="true"/>
                <attr name="sDescription" caption="Описание" editorType="memo" order="30"/>
                <attr name="bNotActive" caption="Не используется" editorType="check"/>
                <attr name="dExpiryDate" caption="Дата окончания использования" editorType="datePick"/>
            </attributes>
            <operations>
            </operations>
        </representation>
        <representation name="List" editMode="notEdit">
        </representation>
        <representation name="Card" editMode="edit" stdFilter.isAvailable="false">
            <layout>
                <simpleComposer>
                    <frame filter.isVisible="false">
                        <card/>
                    </frame>
                </simpleComposer>
            </layout>
        </representation>
        <representation name="Lookup" editMode="notEdit">
        </representation>
    </view>

## Пользовательская блокировка

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

### Встроенная пользовательская блокировка

Пользовательская блокировка включается при взаимодействии пользователя с интерфейсом в методе `Dvi#beforeEdit`, который является результатом кодогенератора.

    override def beforeEdit(): Unit = {
        ...
        val rop = defaultRep.thisRop()
        if (rop != null && (Set(ReadRopMode, UpdateRopMode) contains rop.ropMode)) {
            defaultRep.tryUserLock()
        }
        ...
    }

### lockObject(gid)

    Btk_FormSessionApi().lockObject(gid)

Установка пользовательской блокировки для документа, `gid` которого передан в параметр.
В системное поле `idlockunit_dz` документа будет установлен номер блокировки, выданный сессии пользователя, с которой вызван метод.
Сессия пользователя представляет запись в таблице `Btk_FormSession`.
В случае, если документ заблокирован какой-то пользовательской сессией и выполняется попытка заблокировать с другой сессии, то метод вызовет исключение с описанием блокировки.
При попытке редактировать коллекцию заблокированного документа также сработает пользовательская блокировка, потому что при изменении коллекции `beforeEdit()` вызывает пользовательскую блокировку для мастер-документа, который заблокирован.
Таким образом осуществляется блокировка всего бизнес-объекта.

### lockObjectMulti(gida)

    Btk_FormSessionApi().lockObjectMulti(Seq[NGid], riseError = true)

Позволяет одной пользовательской сессии заблокировать несколько записей за одну транзакцию в БД.
Принцип такой же, как у метода `lockObject(gid)`, но запрос для блокировки вызывается не отдельно для каждого документа, а собирается для всех документов и выполняется 1 раз, т.е. за одну транзакцию в БД.
Можно передавать `gid` различных классов.
Поле `riseError` — нужно ли поднимать исключение при наличии заблокированных записей другой сессией. Выдаст исключение, если хоть один из переданных документов уже заблокирован.

    Btk_FormSessionApi().lockObjectMulti(Seq[NGid]) // выдаст исключение
    // проигнорирует уже заблокированные документы
    Btk_FormSessionApi().lockObjectMulti(Seq[NGid], riseError = false)

### lockObjectMultiWithInfErr(gida)

    Btk_FormSessionApi().lockObjectMultiWithInfErr(Seq[NGid])

Данный метод отличается от `Btk_FormSessionApi().lockObjectMulti(Seq[NGid])` тем, что не вызывает исключение и возвращает информацию о результате метода.
Метод возвращает кортеж:
`(Seq[NGid], NString)`
где:

- `Seq[NGid]` — `gid` уже заблокированных записей другой сессией;

- `NString` — сформированная ошибка.

Данные можно использовать для более сложной логики.