[CAP] SAPUI5 Filter using FilterOperator.Contains with oData V2

In my SAPUI5 Freesstyle frontend I created a search field above a list. In the searchfield handler I’m creating a filter with the provided query.

const sQuery = oEvent.getParameter("query");
new Filter("firstName", FilterOperator.Contains, sQuery);

Afterwards I’m binding the filter to my list to trigger the binding refresh. But when debugging the backend handler I noticed the following…

In my CAP on Read handler, the filter gets converted into a V4 compatible filter expression:

oData V4: $filter=contains(firstName,'Max')

As I’m forwarding the request to an external V2 oData API (SuccessFactors) this would not work, as for V2 the following filter syntax is needed:

oData V2: $filter=substringof('Max',firstName) eq true

As I could not find any solution to this problem, I manually passed my filter as custom property to my CAP Service and did a manual select.

Adding the custom property in the frontend in my searchfield handler:

onSearch: function (oEvent) {
			if (oEvent.getParameters().refreshButtonPressed) {
				this.onRefresh();
				return;
			}

			let oBindingInfo = this._oList.getBindingInfo("items");
			if (!oBindingInfo.parameters) oBindingInfo.parameters = {};
			if (!oBindingInfo.parameters.custom) oBindingInfo.parameters.custom = {};

			if (oEvent.getParameter("query")) {
				oBindingInfo.parameters.custom.filter = "%" + oEvent.getParameter("query") + "%";
			} else {
				oBindingInfo.parameters.custom.filter = undefined
			}
			this._oList.bindItems(oBindingInfo);
}

My CAP handler with the filter handling:

const { Object } = srv.entities
const SF_Srv = await cds.connect.to('SF')

srv.on('READ', Object, async req => {

            if (!req._queryOptions.filter) {
                // share request context with the external service 
                return SF_Srv.tx(req).run(req.query);
            } else {
                //if filter provided, build manually a select statement using a where condition
                let input = req._queryOptions.filter;
                const tx = SF_Srv.transaction(req);
                return await tx.run(
                    SELECT
                        .from(Object)
                        .where`firstName like ${input} or lastName like ${input}`)
            }
    })

As alternative you could also add the where condition directly to the query object:

const { Object } = srv.entities
const SF_Srv = await cds.connect.to('SF')

srv.on('READ', Object, async req => {

            if (req._query.filter) {
                //if filter provided, build manually a select statement using a where condition
                let { query } = req
                let input = req._queryOptions.filter

                if (!query.SELECT.where) query.SELECT["where"] = []

                query.SELECT.where.push(
                    { ref: ['firstName'] }, 'like', { val: input }, "or",
                    { ref: ['lastName'] }, 'like', { val: input }, "or",
                    { ref: ['object'] }, 'like', { val: input })
            }

            // share request context with the external service 
            return SF_Srv.tx(req).run(req.query)
    })

[OpenUI5] SAP Fiori elements add-on for OpenUI5 using an OData V4 service

Recently I found this blog post about the new SAP Fiori elements add-on for OpenUI5.
https://blogs.sap.com/2020/12/21/now-available-sap-fiori-elements-add-on-for-openui5/
It includes a little exercise to try it out for yourself. I wrote down all steps I had to make on my Linux Mint 20 installation.
These two links also helped me a lot.
https://github.com/sap-samples/cloud-cap-samples
https://cap.cloud.sap/docs/get-started/

Prerequisites (Node.js, Visual Studio Code, SAP Fiori tools, Git)

curl -sL https://deb.nodesource.com/setup_15.x | sudo -E bash -
sudo apt-get install -y nodejs
node --version
npm -v 

Change npm’s default directory to prevent permission errors.
https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
mkdir ~/.npm-global/lib
#add the following line to your .bashrc or .profile or .zshrc
export PATH=~/.npm-global/bin:$PATH

Step 1: Provide an OData V4 service

git clone https://github.com/sap-samples/cloud-cap-samples remote-odata-service
cd remote-odata-service
npm i 
npm i -g @sap/cds-dk
cds watch fiori

Step 2: Generate a SAP Fiori elements List Report Object Page (LROP) app with Fiori tools

1. Open VSC, press Ctrl + P and search for > Fiori: Open Application Generator

2. Choose SAP Fiori elements application
In my case there was no default generator, so first I had to install it.

npm install -g @sap/generator-fiori-elements@latest

This can also be done directly in VSC.

3. Select List Report Object Page
4. Select Connect to an OData Service as Data source and enter as URL http://localhost:4004/browse
5. Choose Books as the Main entity and texts as Navigation entity
6. Complete the mandatory information module name (e.g. bookshop) and Project folder path for storing your app. Of course, you can also fill in the optional information.

Step 3: Make changes in package.json and ui5.yaml required for using OpenUI5

package.json

{
	"name": "fiorielements_openui5",
	"version": "0.0.1",
	"private": true,
	"sapux": true,
	"description": "A Fiori application.",
	"keywords": [
		"ui5",
		"openui5",
		"sapui5"
	],
	"main": "webapp/index.html",
	"scripts": {
		"start": "fiori run --open index.html",
		"start-local": "fiori run --config ./ui5-local.yaml --open index.html",
		"build": "ui5 build -a --clean-dest --include-task=generateManifestBundle generateCachebusterInfo",
		"deploy": "fiori add deploy-config"
	},
	"devDependencies": {
		"@sap/ux-specification": "latest",
		"@sap/ux-ui5-tooling": "1",
		"@ui5/cli": "2.5.0",
		"@ui5/fs": "2.0.1",
		"@ui5/logger": "2.0.0"
	},
	"ui5": {
		"dependencies": [
			"@sap/ux-ui5-tooling",
			"@sap/open.fe"
		]
	},
	"dependencies": {
		"@sap/open.fe": "1.85.0"
	}
}

ui5.yaml

specVersion: '2.2'
metadata:
  name: 'fiorielements_openui5'
type: application
framework:  
  name: OpenUI5  
  version: "1.85.0"  
  libraries:   
  - name: sap.m  
  - name: sap.ui.core  
  - name: sap.uxap
  - name: themelib_sap_fiori_3
server:
  customMiddleware:
  - name: fiori-tools-proxy
    afterMiddleware: compression
    configuration:
      ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted
      backend:
      - path: /browse
        url: http://localhost:4004
  - name: fiori-tools-appreload
    afterMiddleware: compression
    configuration:
     port: 35729
     path: webapp

Step 4: Run the V4 application

cd ~/projects/fiorielements_openui5
npm i
npm start

Now http://localhost:8080/index.html should be opened in your browser.
“Note: Clicking on the Go button in List Report application might request  user and password. Please enter user alice, no password.”
Finally I got my list items.