Динамические столбцы#
Концепция динамических столбцов#
Динамические столбцы — это колонки, формируемые при отображении данных, без изменения физической структуры базы данных. Они позволяют гибко адаптировать интерфейс под текущий контекст (тип объекта, период, права пользователя) без изменения физической структуры базы данных.
Существует два основных способа реализации динамических столбцов:
Реляционная реализация — для данных только на чтение (Read-Only), не зависящих от кэша.
DynMetaBuilderиDynRecBuilder— для интерактивных интерфейсов, редактируемых данных или сложной логики.
Реализация динамических столбцов через SQL#
Этот подход используется, когда данные можно получить напрямую через SQL-запрос. Основная задача — сформировать текст запроса selectStatement, включающий подзапросы для динамических колонок.
Пример:
s"""
,(select string_agg(cast(t.id as varchar), ', ')
from Oil_Task t
where t.idResource = ${rv.idResource()}
and (t.dBegin < $sMainFromTab.dEndTime and t.dExec > $sMainFromTab.dBegTime)
and t.idObjectType = :${Oil_TaskMonitorPkg().sfltidObjectType}
and t.idStateMC >= 200
and t.idDepOwner = :super$$idGlobalDepOwner
group by t.idResource
) as \"$sNameFieldForGST[${rv.id()}]\"
"""
Результат после подстановки параметров:
(select string_agg(cast(t.id as varchar), ', ')
from Oil_Task t
where t.idResource = 115
and (t.dBegin < ‘2023-08-25 12:00:00’ and t.dExec > ‘2023-08-25 14:00:00’)
and t.idObjectType = 225
and t.idStateMC >= 200
and t.idDepOwner = 336
group by t.idResource
) as “idFlyOverWay[13]"
Мы получим столбец с названием idFlyOverWay[13], результатом будет значение подзапроса.
Примечание
В примере выше у атрибута указывается его название, по которому в avm для всех атрибутов с таким названием можно будет задать параметры, и индекс в квадратных скобках, указывающий на конкретный атрибут.
Пример задания параметров для всех атрибутов idFlyOverWay:
<attr name="idFlyOverWay">
<style attr="sAvailStyle"/>
<grid columnWidth="6" isColumnWidthFixed="true"/>
</attr>
Пример установки параметров атрибута по его индексу:
for ((svCaption, nvIdx) <- Pro_ResLoadChartPkg().getPeriodCaptions(svExpandTypeMC, dvPeriodBegin, dvPeriodEnd, periods).zipWithIndex) {
selection.attrs(s"""nWreQty[$nvIdx]""").caption = s"Плановая|${svCaption.get}"
selection.attrs(s"""nWreQty[$nvIdx]""").order = 100 + 100 * nvIdx
selection.attrs(s"""nResAvail[$nvIdx]""").isVisible = getVar("super$filter$Flt_bShowResourceAvailability").asNNumber.nvl(0.nn).toBoolean
}
Примечание
Символ | в заголовке (caption) разделяет имя бенд-группы и имя атрибута. В примере: группа «Плановая», атрибут — динамическое название периода.
Реализация через DynMetaBuilder и DynRecBuilder#
Используется, когда требуется интерактивность (редактирование), работа с кэшем или сложная логика вычислений.
Процесс состоит из двух этапов:
Описание метаданных (
DynMetaBuilder): какие колонки добавляем (имя, тип, заголовок).Заполнение данных (
DynRecBuilder): какие значения писать в эти колонки для каждой строки.
Структура формирования:
Примечание
Recs(<объекты, представляющие строку (список rop или case class)>)
.extend(
Пример использования
.extend()- метод расширяет исходный набор данных, добавляя к ним данные о динамических столбцах:val dynMetaBuilder = getObjAttrDynMetaBuilder() thisApi().refreshByParent(getIdMaster) .extend(dynMetaBuilder.build()) .foreach{(rop, builder) => val dynamicInfo = getDynamicObjAttr(rop, builder) (rop, dynamicInfo.build) }
В примере
foreachиспользуется нестандартный обходчик по коллекции, который ничего не возвращает, а отдельный метод для динамического формирования столбцов, который должен вернуть результат выборки!В данном методе столбец получает построчно значения.
Пример
getObjAttrDynMetaBuilder- здесь формируются метаданные о добавляемых столбцах: название столбца, тип данных, отображаемый заголовок колонки.def getObjAttrDynMetaBuilder(): DynMetaBuilder = { val dynMetaBuilder = DynMetaBuilder() thisApi().jsonAttrsWithPrefix.foreach { case (rvAttr, sAttrPrefix) => val svAttr = getJsonAttrName(rvAttr.id, sAttrPrefix) val attrClass = Btk_AttributeApi().getAttrValueClassById(rvAttr.id) // Добавляем метаданные об атрибуте dynMetaBuilder.add(svAttr, attrClass, rvAttr.sCaption) // для ссылочных атрибутов отображаем HL поля if (rvAttr.sType === AttrTypes.RefObject.toString.ns) { dynMetaBuilder.add(s"idValueHL[${rvAttr.id}]", classOf[String], rvAttr.sCaption) } // И добавляем скрытый столбец для редактора dynMetaBuilder.add(Btk_AttributeLib().getEditorAttrName(rvAttr.id, rvAttr.id.isNull), classOf[String], s"Редактор ${rvAttr.sCaption}") } dynMetaBuilder }
Пример
getDynamicObjAttr- здесь формируются данные строк для каждого добавленного атрибута:def getDynamicObjAttr(rop: Rop[_, _], builder: DynRecBuilder): DynRecBuilder = { val svLetterInRuleCaption = thisApi().getLetterInProcessRule(rop).sCaption builder.set(A.sLetterInProcessRule.name + "HL", svLetterInRuleCaption.get) builder }
В данном примере для атрибута
sLetterInProcessRuleбудет выведен дополнительный столбец с заголовком.
В некоторых случаях требуется использовать методы DynMetaBuilder и DynRecBuilder вместе с классом AdditionalInfo. В таком случае вместо extend используется метод extend2.
Пример использования
.extend2()- метод расширяет исходный набор данных, добавляя к ним данные о динамических столбцах и данные из классаAdditionalInfo:thisApi().refreshByParent(getIdMaster) .extend2(classOf[AdditionalInfo], dynMetaBuilder.build()) .foreach{(rop, builder) => val dynamicInfo = getDynamicObjAttr(rop, builder) (rop, getAdditionalInfo(rop.asInstanceOf[SomeApi#ApiRop]), dynamicInfo.build) }
,где
SomeApi- какая-либо Api класса.
Упрощённая реализация через хуки карточки#
Использование специальных хуков внутри класса карточки доступно только для карточек объектных характеристик (наследники Btk_ObjectAttrLibCard_ObjectAttr) и позволяет упростить работу с коллекциями, избавляя от необходимости вручную писать длинные цепочки методов, таких как .extend() и .foreach().
Динамические колонки, добавленные через onRefreshExtendMeta, поддерживают полную настройку отображения, аналогичную статическим атрибутам. Метаданные привязываются к базовому имени атрибута, игнорируя технические суффиксы или индексы.
Методы хуков, позволяющие динамически расширять отображение данных, без изменения структуры базы данных:
onRefreshExtendMeta(dynMetaBuilder: DynMetaBuilder)
Вызывается один раз при построении структуры.
Служит для объявления новых колонок:имя;
тип;
заголовок;
группировка.
onRefreshExtendValues(dynRecBuilder: DynRecBuilder)
Вызывается для каждой записи выборки.
Служит для заполнения значений в колонках, объявленных вonRefreshExtendMeta.
Пример задачи: отобразить год выпуска (dManufYear), преобразовав полную дату в строку года, и поместить колонку в группу «Основные данные»:
override def onRefreshExtendMeta(dynMetaBuilder: DynMetaBuilder): Unit = {
super.onRefreshExtendMeta(dynMetaBuilder)
// Получаем группу характеристик "Основные данные"
val basicDataGroup = Btk_ObjectAttrGroupByObjectTypeApi()
.findByMnemoCode(Btk_ObjectTypeApi().load(getCurIdObjectType), "Attr_groupBasicData")
val sGroupAttr = if (basicDataGroup.isNotNull) {
Btk_ObjectAttrGroupByObjectTypeApi().load(basicDataGroup).get(_.sCaption)
} else "Основные данные"
// Добавляем новую колонку "Год выпуска" в указанную группу
dynMetaBuilder.add("dManufYearDisplay", classOf[String], s"Год выпуска|$sGroupAttr", 3.nn)
}
override def onRefreshExtendValues(dynRecBuilder: DynRecBuilder): Unit = {
super.onRefreshExtendValues(dynRecBuilder)
// Преобразуем дату dManufYear в год (например, 2025-03-15 → "2025")
val convertedManufYear = if (thisRop().get(_.dManufYear).isNotNull) {
thisRop().get(_.dManufYear).toNLocalDateTime.get.getYear.toString
} else ""
dynRecBuilder.set("dManufYearDisplay", convertedManufYear)
}
Как обращаться к динамической колонке в настройках
При конфигурировании через selection.attrs(...) или XML-конфигурацию AVM используется базовое имя, переданное первым параметром в dynMetaBuilder.add().
Параметр в коде (add) |
Имя для настройки ( |
Пояснение |
|---|---|---|
|
|
Простое имя, используется как есть. |
|
|
Настройка применяется ко всем колонкам с этим базовым именем. |
|
|
Индекс в квадратных скобках в настройках не указывается. |
Практические примеры настройки
Вариант А: Программная настройка (в коде)
Выполняйте настройку динамических колонок непосредственно внутри метода onRefreshExtendMeta:
override def onRefreshExtendMeta(dynMetaBuilder: DynMetaBuilder): Unit = {
super.onRefreshExtendMeta(dynMetaBuilder)
// 1. Объявляем колонку (базовое имя: "dManufYearDisplay")
dynMetaBuilder.add("dManufYearDisplay", classOf[String], "Год выпуска|Основные данные", 3.nn)
// 2. Сразу настраиваем её отображение через selection.attrs
selection.attrs("dManufYearDisplay").order = 150
selection.attrs("dManufYearDisplay").columnWidth = 4
selection.attrs("dManufYearDisplay").isColumnWidthFixed = true
selection.attrs("dManufYearDisplay").isVisible = getVar("bShowYear").asBoolean
}
Вариант Б: Настройка через AVM / XML
Если имя динамического атрибута известно заранее, прописывайте в конфигурации интерфейса:
<attr name="dManufYearDisplay">
<style attr="sAvailStyle"/>
<grid columnWidth="5" isColumnWidthFixed="true"/>
<caption value="Год выпуска"/>
</attr>