[SAPUI5] Get and set properties of a binded model and submit changes

Get:

const oModel = this.getView().getModel()
const sPath = this.getView().getBindingContext().sPath
const sID = oModel.getProperty(sPath+"/ID")

Set:

const newID = "12345"
oModel.setProperty(sPath + "/ID", newID)

When using the set property function, you can now submit your changes this way:

         // First check if there are any changes    
         if (!oModel.hasPendingChanges()) {
           MessageToast.show("Nothing to do.")
           return
        }
        
        // Now submit your changes
        oModel.submitChanges({
          success: () => MessageToast.show("Success!"),
          error: (err) => alert(err)
        })

This way is much more comfortable, than using oModel.update().

[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)
    })