Практические сценарии настройки универсального фильтра#
Настройка атрибутов класса#
Управление через odm.xml#
Тег uniFilter управляет:
isActive- определяет, отображается ли атрибут в списке фильтрации (включено по умолчанию);refClass– перекрывает настройку ссылочности самого атрибута (указанного в тегеattr);тег
refAnyObject– задает список допустимых классов для переменной ссылочности.
Пример настройки атрибута класса в odm.xml:
<attr name="bError" attribute-type="Number" caption="С ошибками" order="70" type="basic" editorType="check" defaultValue="0">
<uniFilter isActive="false" refClass="Bs_Goods">
<refAnyObject>
<ref name="Bs_Goods"/>
</refAnyObject>
</uniFilter>
<booleanColumn/>
</attr>
Расширение через afterBuildFltEntityMeta#
Для управления атрибутами используйте точку расширения Btk_Ext.afterBuildFltEntityMeta. Она позволяет изменять стандартные атрибуты и добавлять собственные.
Для управления атрибутами используются методы пакета ru.bitec.app.btk.flt.meta.Btk_FltMetaAttributePkg.
Примеры применения точки расширения приведены в разделе Реализация кастомной фильтрации в прикладном коде.
Настройка коллекций фильтрации#
Настройка коллекций фильтрации определяет, какие коллекции будут доступны при фильтрации объектов класса.
Настраивается в карточке класса на вкладке «Коллекции универсального фильтра».
Можно подключать дополнительные коллекции или переопределять стандартные коллекции класса.
Отключение (
isActive = false) скрывает коллекцию из универсального фильтра.
Коллекция считается переопределённой, если существует запись с тем же ссылочным классом и атрибутами соединения.
Для коллекций атрибут —
id.Для V-коллекций —
gid.
Для программного управления используется метод ru.bitec.app.btk.class_.flt.Btk_ClassFltCollectionApi#register.
Настройка атрибутов выборки#
Тег uniFilter в AVM отвечает за то как фильтр отображается и работает в UI-выборке и имеет свойства:
isActive– отображать атрибут в фильтре (по умолчанию — включено);isSelectionAttr– фильтровать как атрибут класса (черезjoinс таблицей класса поid), если у выборки нет класса, то атрибут не доступен для фильтрации (по умолчанию включено)refSelection– имя выборки, которая будет открываться при выборе значения в фильтре. Имеет приоритет перед свойствомrefSelectionтегаref.refRepresentation– имя отображения, которое будет открываться при выборе значения в фильтре. Имеет приоритет перед свойствомrefRepresentationтегаref.headlineType— режим отображения значения ссылочного атрибута в универсальном фильтре.
Пример настройки режима отображения для ссылочного атрибута в avm.xml:
<condition id="idaAccFlt" isExpression="false">
<filterAttr name="flt_idaAcc" attribute-type="Varchar" caption="Счет" editorType="buttonsEdit" isRequired="true">
<editor>
<editButton isResetButtonVisible="true"/>
</editor>
<uniFilter attribute-type="Varchar" type="refObject" conditionType="InList"
ref.class="Bs_Acc" ref.selection="Bs_AccAvi" ref.representation="TreeForChoose"
expression="af.idParent" macros="idaAccMacros" headlineType="MC"/>
</filterAttr>
</condition>
Режим отображения значения ссылочного атрибута можно настроить на нескольких уровнях:
на уровне системы;
на уровне класса;
на уровне атрибута выборки;
программно через событие универсального фильтра.
Системная настройка задает режим отображения по умолчанию для ссылочных атрибутов универсального фильтра.
Путь к настройке: Приложение «Настройка системы» > Настройки и сервисы > Настройки модулей системы > Общие настройки модулей > btk > Детализация > Универсальный фильтр > Режим отображения значения ссылочных атрибутов.
На уровне класса режим отображения можно задать для ссылочных атрибутов через настройку:
uniFilter.RefObjectAttrsHeadlineType = "MCHL"
В этом случае для ссылочных атрибутов класса будет отображаться мнемокод и заголовок.
На уровне атрибута выборки режим отображения задается в avm.xml через свойство headlineType тега uniFilter.
Для динамической настройки в прикладной выборке можно обработать событие FltAfterCreateAttrEvent в методе handleFltEvents.
Обработка событий фильтра#
Отвечает за динамическое изменение поведения фильтра при взаимодействии пользователя. Например, к событиям относится взаимодействие пользователя с объектами фильтра, такие как установка значения ссылочного атрибута через интерфейс и т.п.
Универсальный фильтр генерирует события в ключевые моменты своего жизненного цикла, которые позволяют переопределять его поведение в прикладных классах. Событие определяется одним из наследников интерфейса FltEvent.
Доступные типы событий#
Внимание
Все используемые типы событий должны быть добавлены в перечисление FltEvent.Type.
Событие |
Описание |
|---|---|
|
Срабатывает перед установкой значения ссылочного атрибута из интерфейса. Позволяет изменить |
|
Срабатывает после создания атрибута фильтра. Позволяет изменить параметры созданного атрибута, например режим отображения значения ссылочного атрибута. |
Механизм обработки#
Обработчик событий универсального фильтра — это класс, который наследует интерфейс FltEventHandler и определяет handleEvent(event: FltEvent). Экземпляр создаётся на этапе инициализации фильтра EntityAviDefault#onFilterInit.
Стандартный обработчик — FltAviEventHandler:
передаёт объект события через переменную выборки;
вызывает метод
EntityAviDefault#invokeHandleFltEventsв целевой выборке с учётом переопределений в прикладном коде;выполняет обработку события.
Примечание
Можно создать собственный обработчик с изменённой логикой.
Переопределение поведения в прикладном коде#
Целевая выборка, в которой определен универсальный фильтр, может переопределять функцию обработки событий EntityAviDefault#handleFltEvents. Это основной способ взаимодействия с событиями УФ.
Особенности:
Работают с мультивыбором: переопределённые параметры корректно передаются и обрабатываются для атрибутов фильтра с мультивыбором.
Переопределённые параметры имеют высший приоритет: параметры ивента имеют приоритет над всеми остальными, поэтому могут перезаписывать ранее заданные ключи (даже ключи, передающиеся по умолчанию).
Это позволяет гибко настраивать поведение фильтра и связанного с ним стандартного фильтра. Пример использования можно найти в разделе Практические советы.
Как переопределить параметры события#
Для передачи параметров в выборку сеттера ссылочного атрибута необходимо переопределить обработку событий в прикладной выборке, а именно функцию EntityAviDefault#handleFltEvents. Параметры, которые могут быть переопределены, представлены изменяемыми коллекциями для удобства работы:
setterParams: mutable.Map[String, Any]- список параметров, которые передаются в выборку клиентского сеттера без изменений.availableValues: mutable.ArrayBuffer[Any]- список допустимых id или gid, определяющий возможные значения атрибута фильтра. Ограничивает список, открывающийся «по трём точкам».
override def handleFltEvents(event: FltEvent): Unit = {
event match {
// событие перед установкой значения ссылочного атрибута из интерфейса
case event: FltBeforeClientSetRefObjectEvent =>
event.setterParams.update("FLT_IDDEPOWNER", 47606.nl)
// добавление к полученному от атрибута списку разрешённых идентификаторов дополнительных значений
event.availableValues += 132021.nl
// очистка и составление НОВОГО списка идентификаторов
event.availableValues.clear()
event.availableValues ++= Array(1.nl, 2.nl)
case _ =>
}
}
Как изменить режим отображения ссылочного атрибута#
Событие FltAfterCreateAttrEvent можно использовать, если нужно изменить созданный атрибут фильтра после его инициализации.
Например, для ссылочного атрибута можно изменить режим отображения значения: показывать не заголовок, а мнемокод или мнемокод вместе с заголовком.
override def handleFltEvents(event: FltEvent): Unit = {
event match {
// Обработка события создания атрибута фильтра
case event: FltAfterCreateAttrEvent =>
// Проверяем, что событие относится к нужному атрибуту фильтра
if (event.attrName === "flt_idaAcc".ns) {
event.fltAttr match {
case fltAttr: FltAttrRefObject =>
// Устанавливаем режим отображения значения в виде мнемокода
fltAttr.headlineType = FltRefAttrDisplayMode.MnemoCode
case _ =>
}
}
case _ =>
}
}
Использование в бизнес-логике#
Пример получения
where-условияпо сохраненной настройке:val rop = thisRop() //создаем атрибут, ссылочный на ТМЦ, и загружаем в него текущие настройки из БД val standaloneAttribute = FltStandaloneAttribute.refAttr( sRefClass = "Bs_Goods", sData = rop.get(_.jData) ) //сформировать макрос, где все условия будут накладываться на колоноку t.id val macros = Btk_FltStandalonePkg().generateMacro(standaloneAttribute, "t.id") if (macros.hasFilter) { dialogs.showMessage(macros.where) } else { dialogs.showMessage("Условий не наложено") }
Пример получения перечня значений по сохраненной настройке:
val rop = thisRop() //создаем атрибут, ссылочный на ТМЦ, и загружаем в него текущие настройки из БД val standaloneAttribute = FltStandaloneAttribute.refAttr( sRefClass = "Bs_Goods", sData = rop.get(_.jData) ) //получить перечень Gid-ов, удовлетворяющих условию val valuesOpt = Btk_FltStandalonePkg().getRefAttrValues(standaloneAttribute) if (valuesOpt.isDefined) { dialogs.showMessage( s"""Кол-во: ${valuesOpt.get.size} |Первые 20 значений: ${valuesOpt.get.take(20).mkString(";")} |""".stripMargin ) } else { dialogs.showMessage("Условий не наложено") }
Реализация кастомной фильтрации в прикладном коде#
Создание дополнительной группы фильтрации по классу#
На операции
lazyInitFilterинициализируйте дополнительную группу:override def lazyInitFilter(): Unit = { if (!fltManager.isPopulatedRootGroup) { Btk_FltPkg().createRootGroupByClass(fltManager, "Bs_Goods", "ТМЦ") } super.lazyInitFilter() }
В
onApplyFilterполучите условия фильтрации по группе:override def onApplyFilter(): Unit = { super.onApplyFilter() //проверяем, что группы инициализированы, и фильтр активен в выборке if (fltManager.isPopulatedRootGroup && fltManager.isActive) { val alias = "tt" val macros = Btk_FltPkg().generateMacroByGroup(fltManager, "Bs_Goods", alias) selection.setMacro("GdsMacro", macros.where) } }
В
onRefreshиспользуйте установленный вonApplyFilterмакрос:Через метод
prepareSelectStatement:override protected def onRefresh: Recs = { prepareSelectStatement("&GdsMacro") }
В тексте SQL-запроса:
override protected def onRefresh: Recs = { """ select ..... from ..... where .... and &GdsMacro """ }
Создание дополнительной группы фильтрации с произвольными атрибутами#
На операции
lazyInitFilterинициализируйте дополнительную группу:override def lazyInitFilter(): Unit = { if (!fltManager.isPopulatedRootGroup) { Btk_FltPkg().createCustomRootGroup(fltManager, "Some_CustomGroup", "Произвольная группа", (builder) => { builder.addRefObjectAttr("idGds", "Тмц", "Bs_Goods") builder.addBasicAttr("sCode", "Код", AttributeTypes.Varchar, false) }) } super.lazyInitFilter() }
В
onApplyFilterполучите условия фильтрации по группе:override def onApplyFilter(): Unit = { super.onApplyFilter() //проверяем, что группы инициализированы и фильтр активен в выборке if (fltManager.isPopulatedRootGroup && fltManager.isActive) { val macros = Btk_FltPkg().generateMacroByGroup(fltManager, "Some_CustomGroup", "tt") selection.setMacro("SomeMacro", macros.where) } }
В
onRefreshиспользуйте установленный вonApplyFilterмакрос:Через метод
prepareSelectStatement.override protected def onRefresh: Recs = { prepareSelectStatement("&SomeMacro") }
В тексте sql-запроса.
override protected def onRefresh: Recs = { """ select ..... from ..... where .... and &SomeMacro """ }
Добавление JSON-атрибуту признака раскрываемости в дереве атрибутов#
def afterBuildFltEntityMeta(fltMetaEntity: FltMetaEntity, fltManager: FltManager): Unit = {
//проверка, что переданный класс - класс, для которого нужно добавить логику
if (fltMetaEntity.name == "Bs_Goods") {
//определяем, что JSON-атрибут доступен по умолчанию.
val typeSizeAttrOpt = fltMetaEntity.attrMap.get("jTypeSizeAttrs".toLowerCase)
if (typeSizeAttrOpt.isDefined) {
val typeSizeAttr = typeSizeAttrOpt.get
//ставим признак, что он может быть раскрыт в дереве
Btk_FltMetaAttributePkg().setCanExpand(typeSizeAttr, true)
//формируем атрибуты-потомки
for (rvx <- new OQuery(Gds_TypeSizeCharacteristicAta.Type) {
}) {
//создаем значимые атрибуты, которые хранятся в JSON-контейнере
val fltAttr = Btk_FltMetaAttributePkg().buildBasicJObjectAttr(
systemName = rvx.get(_.sCode),
caption = rvx.get(_.sCaption),
dbType = AttributeTypes.Number,
isBoolean = false,
jsonColumnName = "jTypeSizeAttrs"
)
//устанавливаем созданному атрибуту потомка.
Btk_FltMetaAttributePkg().setParentTree(fltAttr, typeSizeAttr)
//добавляем атрибут в общий перечень атрибутов.
fltMetaEntity.attrMap(fltAttr.systemName.toLowerCase) = fltAttr
}
}
}
}
Примечание
Каждый из таких атрибутов формирует условие, которое получает значение из JSON-контейнера.
Добавление значимому атрибуту признака ссылочности и раскрываемости#
Сценарий позволяет фильтровать по данным, которые не являются ссылками в БД, но логически ими являются.
Такой подход используется, когда значимый атрибут нужно обрабатывать как ссылочный. Например, атрибуты sCreateUser_dz и sModifyUser_dz могут хранить имя пользователя как строковое значение, но логически соответствуют объекту класса Btk_User.
После настройки признака ссылочности такой атрибут может раскрываться в дереве универсального фильтра как ссылочный. За счет этого для него становятся доступны атрибуты и коллекции класса Btk_User, в том числе коллекции, подключенные через вкладку «Коллекции универсального фильтра» в карточке класса.
Например, если для класса Btk_User подключена коллекция сотрудников, то в фильтре для поля «Создавший пользователь» или «Изменивший пользователь» можно раскрыть пользователя и отфильтровать данные по связанному сотруднику.
Связь между значимым атрибутом и ссылочным классом задается через параметры соединения:
selfAttrName— атрибут текущего объекта или выборки;selfJoinExpression— выражение для значения текущего объекта;childAttrName— атрибут ссылочного класса;childJoinExpression— выражение для значения ссылочного класса;refClass— класс, к которому выполняется логическая ссылочность.
Точка расширения#
Для управления атрибутами в бизнес-логике используется точка расширения Btk_Ext.afterBuildFltEntityMeta. Она позволяет изменять существующие атрибуты, формируемые по умолчанию, а также добавлять собственные.
Подробнее о применении точки расширения для универсального фильтра см. в разделе Точка расширения.
Шаг 1. Создание метода в API#
В Api-классе, например Bs_GoodsApi, создаётся метод afterBuildFltEntityMeta, в котором реализуется логика модификации атрибутов:
def afterBuildFltEntityMeta(fltMetaEntity: FltMetaEntity, fltManager: FltManager): Unit = {
// Проверка, что переданный класс — класс, для которого нужно добавить логику.
if (fltMetaEntity.name == "Bs_Goods") {
// Создаём значимый атрибут.
val fltAttr = Btk_FltMetaAttributePkg().buildBasicAttr(
systemName = "screateuser_dz", // системное имя атрибута. Уникально в пределах одного класса
caption = "Создавший пользователь", // отображаемое наименование
dbType = AttributeTypes.Varchar, // тип данных
isBoolean = false // признак булевого атрибута
)
// Устанавливаем параметры преобразования атрибута в ссылочный.
Btk_FltMetaAttributePkg().setExpandableParams(
fltAttr = fltAttr,
selfAttrName = "screateuser_dz",
selfJoinExpression = "lower(${column})",
childAttrName = "susername",
childJoinExpression = "lower(${column})",
refClass = "Btk_User"
)
// Здесь формируется join по атрибутам с приведением значений к нижнему регистру.
// ${column} подставляет алиас таблицы и имя поля, указанное в selfAttrName или childAttrName.
//
// Пример результата:
// join Btk_User t94 on lower(t94.susername) = lower(t93.screateuser_dz)
// Устанавливаем признак, что атрибут может быть раскрыт в дереве.
Btk_FltMetaAttributePkg().setCanExpand(fltAttr, true)
// Добавляем атрибут в общий перечень атрибутов.
fltMetaEntity.attrMap(fltAttr.systemName.toLowerCase) = fltAttr
}
}
Шаг 2. Подключение точки расширения#
После реализации метода необходимо проверить, существует ли точка расширения от текущего модуля до Btk, например Bs_BtkExt. Если точки расширения нет, её необходимо создать.
Подробнее см. в разделе Как создать точку расширения.
Подключение точки расширения выполняется через subFunc после формирования метаданных класса для универсального фильтра:
subFunc("afterBuildFltEntityMeta") { (sf: SuperFunc[Unit], fltMetaEntity: FltMetaEntity, fltManager: FltManager) =>
fltMetaEntity.name match {
case sEntityName if sEntityName == Bs_GoodsApi().entityName =>
Bs_GoodsApi().afterBuildFltEntityMeta(fltMetaEntity, fltManager)
case _ =>
}
sf(fltMetaEntity, fltManager)
}
Скрытие группы «Отбор»#
При разработке специализированных фильтров, не требующих стандартных общих условий, скройте группу «Отбор», установив флаг ru.bitec.app.btk.flt.FltAviManager#isAvailableMainGroup в значение false в методе lazyInitFilter.
Пример кода:
override def lazyInitFilter(): Unit = {
if (!fltManager.isPopulatedRootGroup) {
fltManager.isAvailableMainGroup = false
}
super.lazyInitFilter()
}