Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
MioYasutake
SAP Champion
SAP Champion
2,175

Introduction

In Fiori elements, custom filters are used when standard filter fields are not sufficient. This is typically the case when you need to use a different control than the standard filter control, or when the entered value must be transformed before it is converted into OData filter conditions.

Custom filters can be created automatically from the Page Map. It generates a fragment, an event handler (if needed), and manifest.json configurations.

MioYasutake_0-1769802956687.png

Figure: Adding a custom filter using Page Map

The generated fragment looks like the following:

<!-- FilterCustomer.fragment.xml -->
<ComboBox
    id="customer"
    core:require="{handler: 'ns/orders/ext/fragment/FilterCustomer'}"
    selectedKey="{path: 'filterValues>',
        type: 'sap.fe.macros.filter.type.Value',
        formatOptions: { operator: 'ns.orders.ext.fragment.FilterCustomer.filterItems' }}"
>
    <items>
        <core:Item key="0" text="Item1"/>
        <core:Item key="1" text="Item2"/>
        <core:Item key="2" text="Item3"/>
    </items>
</ComboBox>

At this point, you might wonder: What is filterValues? Or why is the value handled via a formatter?

In fact, this automatically generated pattern is not the only way to implement custom filters. There are several approaches to building custom filters, depending on your requirements.

Although the official documentation explains these options in great detail, I personally found it difficult to grasp the overall picture.
In this blog post, therefore, I will start from the absolute basics and gradually explain how custom filters work, focusing on the minimum concepts you need to understand.

All examples used in this blog post are available in the following repository:
https://github.com/miyasuta/fiori-custom-filter-blog/tree/main/app/orders/webapp

The examples were implemented and tested using SAPUI5 version 1.143.2.

 

How Custom Filters Work: The Basics You Should Know

In Fiori elements, the input of a custom filter is bound to an internal model called filterValues. This model is used by Fiori elements to support features such as variant management and app state handling for custom filters. By default, the values stored in filterValues are automatically converted into OData filter conditions, based on the configuration defined in the manifest.json file. 

In the following sections, I will introduce the common implementation patterns for custom filters.

 

Custom Filter.png

Figure: Flow of custom filter values in Fiori elements

 

When No Custom Logic Is Required

When the values entered in a custom filter can be used directly for filtering without any additional processing, no event handler is required. This applies both to single-value inputs and to multi-value inputs.

In this case, you only need to bind the value of the control to the filterValues model. Fiori elements automatically generates the corresponding OData filter based on this value.

Example: Static Filter Without Custom Logic

<!-- Single Value -->
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
        <ComboBox id="customer"
            selectedKey="{path: 'filterValues>'}"
            items="{path: 'partnerService>/Partners',
                parameters: {$filter: 'type eq \'Customer\''}}">
        <items>
            <core:Item key="{partnerService>ID}" text="{partnerService>ID}-{partnerService>name}"/>
        </items>
        </ComboBox>
</core:FragmentDefinition>

 <!-- Multiple Values -->
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
        <MultiComboBox
            id="customer2"
            selectedKeys="{path: 'filterValues>'}"
            width="100%"
            items="{path: 'partnerService>/Partners', 
                parameters: {$filter: 'type eq \'Customer\''}}"
            >
            <items>
                <core:Item key="{partnerService>ID}" text="{partnerService>ID}-{partnerService>name}"/>
            </items>
        </MultiComboBox>        
</core:FragmentDefinition>

If an operator other than EQ is required, it can be specified directly in the XML fragment using formatOptions.

selectedKey="{path: 'filterValues>', formatOptions: { operator: 'GT' }}"

You might wonder, “Where is the mapping to the OData property defined?”

This mapping is defined in the SelectionFields configuration in the manifest.json file.
Specifically, the key of filterFields ("customer" in this case) determines which OData property the filter is associated with.

Somewhat surprisingly, the property attribute itself does not play any role here.
In the following example, the value of property is changed to "banana", yet the filter continues to work as expected.

"@com.sap.vocabularies.UI.v1.SelectionFields": {
  "filterFields": {
    "customer": {
      "label": "Customer",
      "property": "banana",
      "template": "ns.orders.ext.fragment.FilterCustomer",
      "required": false
    }
  }
}

 

When Custom Logic Is Required

In some cases, the entered value cannot be applied directly as simple filter conditions. The input may need to be mapped to a single filter condition (1:1) with custom logic, or expanded into multiple filter conditions (1:n) .

There are two main approaches to implementing such logic, which I will explain in the following sections.

Option 1: Using a Formatter-Based Operator

This approach is used in situations such as the following:

  • You want to apply filters to arbitrary properties, regardless of the filterFields configuration defined in the manifest.json

  • You need filter logic that is more complex than a simple EQ condition, for example when multiple conditions need to be combined

The mechanism works as follows: by specifying the binding type sap.fe.macros.filter.type.Value or sap.fe.macros.filter.type.MultiValue, a custom operator is triggered during filter generation. The custom operator receives the input values and converts them into one or more Filter objects (sap.ui.model.Filter). The resulting filters are then applied to the OData query.

 

Custom Filter 3.png

Figure: Flow of formatter-based operator converting input values to Filter objects

Key limitation: A custom operator can only access the values passed through the binding and cannot access other properties of the control or the Extension API.
Therefore, this option is suitable when the filter logic can be expressed solely based on the values provided via the binding.

Example: Custom Operator with Single Input

<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
        <ComboBox
            id="supplier"
            core:require="{handler: 'ns/orders/ext/fragment/SingleInputWithFormatterOperator'}"
            selectedKey="{path: 'filterValues>', type: 'sap.fe.macros.filter.type.Value', formatOptions: { operator: 'ns.orders.ext.fragment.SingleInputWithFormatterOperator.filterItems' }}"
            items="{path: 'partnerService>/Partners'}">
        <items>
            <core:Item key="{partnerService>ID}" text="{partnerService>ID}-{partnerService>name}"/>
        </items>
        </ComboBox>
</core:FragmentDefinition>
export function filterItems(value: string) {
    const inputSupplier = parseInt(value, 10);
    return new Filter({ path: "supplier", operator: FilterOperator.EQ, value1: inputSupplier });

    // Alternatively, you can return multiple filters
    // return new Filter({
    //     filters: [
    //         new Filter({ path: "supplier", operator: FilterOperator.GE, value1: 2 }),
    //         new Filter({ path: "supplier", operator: FilterOperator.LE, value1: 4 })
    //     ],
    //     and: true
    // });
}

Example: Custom Operator with Multiple Inputs

<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
        <MultiComboBox
            id="supplier2"
            core:require="{handler: 'ns/orders/ext/fragment/MultiInputWithFormetterOperator'}"
            selectedKeys="{path: 'filterValues>', type: 'sap.fe.macros.filter.type.MultiValue', formatOptions: { operator: 'ns.orders.ext.fragment.MultiInputWithFormetterOperator.filterItems' }}"
            items="{path: 'partnerService>/Partners', 
                parameters: {$filter: 'type eq \'Supplier\''}}"
            >
            <items>
                <core:Item key="{partnerService>ID}" text="{partnerService>ID}-{partnerService>name}"/>
            </items>
        </MultiComboBox>
</core:FragmentDefinition>

In the case of multiple inputs, the custom operator receives an array of values. You need to iterate over this array and convert each value into the corresponding Filter objects.

export function filterItems(values: string[]) {
    const filters = values.map((value) => {
        const inputSupplier = parseInt(value, 10);
        return new Filter({ path: "supplier2", operator: FilterOperator.EQ, value1: inputSupplier });
    });

    return new Filter({
        filters: filters,
        and: false
    });
}

 

Option 2: Using an Event Handler and the Extension API

This approach is used in situations such as the following:

  • When you need to access additional properties of a control and use them for filtering, for example using the text value of a ComboBox instead of only the selected key

  • When you need to perform additional processing, such as calling an OData service, to convert the input value into the appropriate filter values

In this case, the filter logic is implemented inside a control event handler. The filters are then set explicitly by calling the Extension API method setFilterValues().

 

Event Handler.png

Figure: Flow of event handler approach using setFilterValues() via Extension API

Key limitation: The setFilterValues() method can only be used for properties of the main entity of the List Report, for keys defined in the filterFields section of the manifest.json, or for properties of associated entities specified in navigationProperties. The next section explains how to handle this limitation with sample code.

Example: Setting Filters with Event Handler

In the following example, an event handler is assigned to the change event of a ComboBox.

<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
        <ComboBox
            id="contact"
            core:require="{handler: 'ns/orders/ext/fragment/SingleInputWithEventHandler'}"
            selectedKey="{path: 'filterValues>'}"
            width="100%"
            items="{path: 'partnerService>/Partners', 
                parameters: {$filter: 'type eq \'Contact\''}}"
            change='handler.onContactChange'>
        <items>
            <core:Item key="{partnerService>ID}" text="{partnerService>ID}-{partnerService>name}"/>
        </items>
        </ComboBox>
</core:FragmentDefinition>

The following is a simple example of controller logic. The value set in the control is applied to the filter by calling setFilterValues(). If the user clears a previously entered value and no value is set in the control, no additional handling is required. The Fiori elements framework automatically excludes the filter in this case.

export function onContactChange(this: ExtensionAPI, oEvent: ComboBox$ChangeEvent) {
    const selectedKey = oEvent.getSource().getSelectedKey();

    if (selectedKey) {
        this.setFilterValues("contact", selectedKey);
        // Alternatively, you can use a filter operator
        //this.setFilterValues("contact", 'GT', selectedKey);
    }
}

 

Specifying Properties of Navigation Targets in an Event Handler

When specifying properties of navigation targets using the setFilterValues() method, there are several important points to consider:

  • The keys in the filterFields section of the manifest.json must be written in the <navigationProperty>/<field> format.
    Even for key fields, you must specify them in this format, for example as contact/ID instead of contact_ID.

  • When setting filters for multiple properties of navigation targets, any properties that are not defined as keys in filterFields must be specified in navigationProperties.

Example

In the following example, two properties of the navigation target, contact2/ID and contact2/firstName, are set as filters.

The key contact2/ID is defined in the filterFields section. The property contact2/firstName is specified in navigationProperties.

"@com.sap.vocabularies.UI.v1.SelectionFields": {
  "navigationProperties": [
    "contact2/firstName"
  ],
  "filterFields": {
    "contact2/ID": {
      "label": "Contact2 (NavigationPropertyWithEventHandler)",
      "property": "contact2/ID",
      "template": "ns.orders.ext.fragment.NavigationPropertyWithEventHandler",
      "required": false
    }
  }
}

In the event handler, these two properties are explicitly set as filters.
Please note that the value for firstName is hard-coded in this example solely to demonstrate how multiple navigation properties can be set.

export function onContactChange(this: ExtensionAPI, oEvent: ComboBox$ChangeEvent) {
    const selectedKey = oEvent.getSource().getSelectedKey();

    if (selectedKey) {
        this.setFilterValues("contact2/ID", selectedKey);
        this.setFilterValues("contact2/firstName", "Taro");
    }
}

 

Conclusion: Formatter-Based Operator or Event Handler?

In most cases, the recommended approach is to use a formatter-based operator. This option allows you to define filters without being restricted by the configuration specified in manifest.json, and it also supports more complex filter logic, such as combining multiple conditions using OR.

A custom event handler should be used only when access to additional control properties, external APIs, or the Extension API is required. In such scenarios, the event handler approach provides the necessary flexibility.

To summarize the decision process, the following diagram shows how to choose the appropriate implementation approach:

Decision Tree.png

Decision tree for selecting the appropriate custom filter implementation approach

Use this flow as a quick guideline when deciding which pattern to adopt in your own applications.

7 Comments
Marian_Zeis
Active Contributor

I am particularly grateful for such good blog posts, which are unfortunately becoming increasingly rare here. Thank you very much!

MioYasutake
SAP Champion
SAP Champion

Thanks @Marian_Zeis for your warm comment! This makes my day. 😊

I just try to write the kind of blog posts that I would like to read on SAP Community. I always read your blog as well and learn a lot from it!

AbhishekSharma
Active Contributor

Thank you so much @MioYasutake  There is always learning from your posts... Thanks again...

Thanks-

 

MioYasutake
SAP Champion
SAP Champion

@AbhishekSharma 

Thank you for your comment! I'm glad to hear that it's helpful.

Jelena_Perfiljeva
Active Contributor

Thank you for sharing and +100 to @Marian_Zeis  - posts like this truly stand out and provide value for the readers for years to come. Well done!

MioYasutake
SAP Champion
SAP Champion

@Jelena_Perfiljeva 
Thank you so much, Jelena! Your words mean a lot to me. I’m really happy to hear that you found the post valuable.

morteninge
Explorer

Very nice write up. Thanks mio.