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.
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.
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.
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().
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 no value is provided, 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 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.
IntroductionIn 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.Figure: Adding a custom filter using Page MapThe 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/webappThe examples were implemented and tested using SAPUI5 version 1.143.2. How Custom Filters Work: The Basics You Should KnowIn 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. Figure: Flow of custom filter values in Fiori elements When No Custom Logic Is RequiredWhen 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 RequiredIn 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 OperatorThis 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.jsonYou need filter logic that is more complex than a simple EQ condition, for example when multiple conditions need to be combinedThe 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. Figure: Flow of formatter-based operator converting input values to Filter objectsKey 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 APIThis 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 keyWhen you need to perform additional processing, such as calling an OData service, to convert the input value into the appropriate filter valuesIn 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(). Figure: Flow of event handler approach using setFilterValues() via Extension APIKey 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 HandlerIn 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 no value is provided, 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 HandlerWhen 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.ExampleIn 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 for selecting the appropriate custom filter implementation approachUse this flow as a quick guideline when deciding which pattern to adopt in your own applications. Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog