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

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

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

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

1. Отключить полную загрузку дерева в AVM-разметке.
2. Указать атрибуты идентификатора, родительской записи и наличия дочерних записей.
3. Обработать системные параметры раскрытых узлов в операции `refresh`.
4. Ограничить результат запроса корневыми записями и содержимым раскрытых узлов.

## Настройка отображения

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

```xml
fetchAllTree="false"
```

В настройках фрейма укажите:

- атрибут идентификатора записи;
- атрибут идентификатора родительской записи;
- атрибут, содержащий признак наличия дочерних записей.

Пример:

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

В примере:

- `gid` — идентификатор записи;
- `gidParent` — идентификатор родительской записи;
- `bHasChild` — признак наличия дочерних записей.

Значение атрибута, указанного в `hasChildrenAttr`, определяет, может ли пользователь раскрыть соответствующий узел.

## Системные параметры дерева

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

- `OPENNODEIDARRAY` — список идентификаторов раскрытых узлов, разделённых запятыми, в формате `ftString`;
- `OPENNODEID` — значение `idAttr` раскрываемого узла в формате `ftString`.

Параметр `OPENNODEIDARRAY` используется при обновлении дерева. Параметр `OPENNODEID` передаётся при раскрытии отдельного узла.

`OPENNODEIDARRAY` и `OPENNODEID` являются системными параметрами. Устанавливать их значения вручную нельзя.

## Формирование запроса

Запрос операции `refresh` должен возвращать:

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

Для загрузки данных используются значения `idAttr` и `idParentAttr`, переданные через системные параметры дерева.

Ниже приведён пример запроса для `Mct_StructureAvi#StructureTree`, расположенного в пакете `Mct_StructurePkg.getOnRefreshBs`.

```sql
-- п. 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
         )

      union all

      -- п. 3. Корневые записи
      select t.gid,
             null as gidParent
        from mct_structure t
        join mct_structurelink l
          on l.gidstructure = t.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. Дочерние записи раскрываемого узла
      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 — возвращает атрибуты, необходимые для формирования строк дерева.

## Признак наличия дочерних записей

Основной запрос должен возвращать атрибут, указанный в свойстве `hasChildrenAttr`.

В примере используется атрибут `bHasChild`:

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

Значение атрибута показывает, существуют ли у записи дочерние элементы.

Без этого признака дерево не может определить, требуется ли отображать для записи элемент раскрытия узла.

## Ограничение списка записей

Результат основного запроса ограничивается содержимым `gidFlt`:

```sql
join gidFlt tt
  on t.gid = tt.gid
```

В `gidFlt` входят:

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

За счёт этого выборка не загружает полное дерево при каждом выполнении `refresh`.

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

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

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

```{note}
Изменение параметров, используемых в запросе, вызывает операцию `refresh`.
```

## Изменение параметров без refresh

Чтобы установить параметры без вызова `refresh`, на время их изменения установите `selection.ignoreParamChange` в значение `true`.

После установки параметров необходимо вернуть `selection.ignoreParamChange` в значение `false`.

Пример:

```scala
try {
  selection.ignoreParamChange = true

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

Конструкция `try` и `finally` обеспечивает восстановление значения `selection.ignoreParamChange`, даже если при установке параметров возникнет ошибка.
