Somehow I always forget the syntax for this….
TYPES: BEGIN OF ty_test.
INCLUDE TYPE z_table.
TYPES: my_new_field TYPE string,
END OF ty_test.
SAP
Somehow I always forget the syntax for this….
TYPES: BEGIN OF ty_test.
INCLUDE TYPE z_table.
TYPES: my_new_field TYPE string,
END OF ty_test.
https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destination
const { getDestination } = require("@sap-cloud-sdk/connectivity")
const myDestination= await getDestination("myDestination")
if (myDestination === null) throw Error(`Destination "myDestination" not found`)
for (let key in myDestination) {
console.log(key, myDestination[key])
}
If the default port 4004 is already open and you want to see what is bound to it, select View -> Find Command -> Ports Preview
Solution to kill another “watch.js” process using/blocking the port: https://answers.sap.com/questions/13016130/sap-business-application-studio-stop-running-serve.html
To archive the same in a single command use:
lwctl -c basic-tools kill -9 $(lwctl -c basic-tools ps aux | grep watch.js | awk '{print $2}')
As alternative, change the default port by adding a new port in package.json to the start script, for example: “start”: “cds run –port 4003” and use npm run start instead of cds watch.
Update 20.02.2023: Just had the problem again due to a VPN disconnect. But this time I had an application running using cds run
. Therefore, I had to change the command from watch.js
to cds.js
:
lwctl -c basic-tools kill -9 $(lwctl -c basic-tools ps aux | grep cds.js | awk '{print $2}')
Update 10.05.2023: A better approach seems to be killing the node process. This should work in both situation.
lwctl -c basic-tools kill -9 $(lwctl -c basic-tools ps aux | grep node | awk '{print $2}')
Update 29.02.2024: With the BAS migration to Code – OSS the previous commands were not working anymore, but this new command seems to work:
kill -9 $(ps aux | grep cds.js | awk '{print $2}')
# or
kill -9 $(ps aux | grep cds-dk | awk '{print $2}')
Update 15.07.2025:
kill -9 $(ps aux | grep cds | awk '{print $2}')
const sfsfSrv = await cds.connect.to('sfsf')
// Option 1: Query Notation
const response = await sfsfSrv.run(SELECT`teamMembersSize`.from`User`.where`userId = ${req.user.id}`)
console.log("option 1: " + response[0].teamMembersSize)
// Option 2: HTTP method-style
const response2 = await sfsfSrv.get(`/User('${req.user.id}')/teamMembersSize/$value`)
console.log("option 2: " + response2)
An easy way to test your approuter is using the user-api-service. For that add the following route to your xs-app.json
{
"source": "^/user-api(.*)",
"target": "$1",
"service": "sap-approuter-userapi"
}
And after deployment, open the application router URL and add /user-api/currentUser
to it. You should see your Email and other User details. This is testing that the application router is actually getting the security token from the UAA instance.
These three entries in a .env file
cds.requires.ECEmploymentInformation.[development].credentials.authentication=BasicAuthentication
cds.requires.ECEmploymentInformation.[development].credentials.username=myUsername
cds.requires.ECEmploymentInformation.[development].credentials.password=myPassword
are equal to line 12, 13, and 14 in this package.json snippet:
{
"cds": {
"ECEmploymentInformation": {
"kind": "odata-v2",
"model": "srv/external/ECEmploymentInformation",
"credentials": {
"[production]": {
"destination": "sfsf"
},
"[development]": {
"url": "https://apisalesdemo2.successfactors.eu/odata/v2",
"authentication": "BasicAuthentication",
"username": "myUsername",
"password": "myPassword",
}
}
}
}
}
https://sapui5.hana.ondemand.com/#/api/sap.ui.core.EventBus%23overview
In the receiving controller you need to subscribe your eventId and function you want to call from the second controller:
// Attaches an event handler to the event with the given identifier on the given event channel
this.getOwnerComponent().getEventBus().subscribe("Default", "myEventId", () => {
this._myFunctionIWantToCall();
});
The sending controller has to publish the event to trigger the function call:
// Fires an event using the specified settings and notifies all attached event handlers.
this.getOwnerComponent().getEventBus().publish("Default", "myEventId", {});
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()
.
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)
})