Практические сценарии настройки универсального фильтра

Практические сценарии настройки универсального фильтра#

Настройка атрибутов класса#

Управление через 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.

Событие

Описание

FltBeforeClientSetRefObjectEvent

Срабатывает перед установкой значения ссылочного атрибута из интерфейса. Позволяет изменить setterParams (инициализируется пустым) и availableValues (создаётся от availableValues атрибута фильтра).

FltAfterCreateAttrEvent

Срабатывает после создания атрибута фильтра. Позволяет изменить параметры созданного атрибута, например режим отображения значения ссылочного атрибута.

Механизм обработки#

Обработчик событий универсального фильтра — это класс, который наследует интерфейс 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 _ =>
  }
}

Использование в бизнес-логике#

  1. Пример получения 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("Условий не наложено")
    }
    
  2. Пример получения перечня значений по сохраненной настройке:

    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("Условий не наложено")
    }
    

Реализация кастомной фильтрации в прикладном коде#

Создание дополнительной группы фильтрации по классу#

  1. На операции lazyInitFilter инициализируйте дополнительную группу:

    override def lazyInitFilter(): Unit = {
      if (!fltManager.isPopulatedRootGroup) {
        Btk_FltPkg().createRootGroupByClass(fltManager, "Bs_Goods", "ТМЦ")
      }
      super.lazyInitFilter()
    }
    
  2. В 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)   
      }
    }
    
  3. В onRefresh используйте установленный в onApplyFilter макрос:

    • Через метод prepareSelectStatement:

      override protected def onRefresh: Recs = {
      prepareSelectStatement("&GdsMacro")
      }
      
    • В тексте SQL-запроса:

      override protected def onRefresh: Recs = {
      """
        select .....
          from .....
         where ....
           and &GdsMacro
      """
      }
      

Создание дополнительной группы фильтрации с произвольными атрибутами#

  1. На операции 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()
    }
    
  2. В 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)   
      }
    }
    
  3. В 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()
}