Homelab, Linux & ABAP (~˘▾˘)~

[JavaScript] map an array of objects to return an array of objects

Recently I hat to create an array of objects from another array of objects to change some property names and to enrich it with some values. To iterate over the array I used the map() function. But when trying to return the new object using the brackets {}, the map function instead expects me trying to write a function… Therefore you need to wrap the return object in ()

const aPersons = [{
  id: 1,
  name: 'max'
}, {
  id: 2,
  name: 'peter'

const aResult = aPersons.map(person => ({ value: person.id, text: person.name, someboolean: true }))

[CAP] Set credentials in package.json via .env


These three entries in a .env file


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",

[Mint] Keyboard not recognized after suspend

In January 2020 I bought a Sharkoon PureWriter Keyboard and since then I had the problem that the keyboard got not recognized after my PC (which runs on Linux Mint) was coming back from suspend mode. Back then I couldn’t find a solution and was just hoping that a newer kernel release will fix this problem in the future. But it did not. So today I was searching again and stumbled again across this post, but now I noticed the new answer from April this year. And it finally solved it!


First check with usbreset for the device name and then create the script under the following path:

sudo micro /lib/systemd/system-sleep/reset-keyboard 

case $1 in
    usbreset "USB-HID Keyboard"

And as a last step we make it executable:

sudo chmod +x /lib/systemd/system-sleep/reset-keyboard

[SAPUI5] Parse error message

If the error response is type json:

        oModel.callFunction("/getData", {
          method: "GET",
          urlParameters: {
            ID: myID,
          success: oData => {
          error: oError => MessageBox.error(JSON.parse(oError.responseText).error.message.value)

If the error response is coming from a Gateway and has an XML body (link):


[Postman] Visualize base64 image

If you have a service which returns a payload like the following (including a base64 endcoded jpeg) you can view it directly in postman.

        "photo": "/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK\r\nCwsND..............",
        "photoId": "192",
        "mimeType": "image/jpeg"

This can be done with a few lines of code. In Postman navigate to the “Tests” tab:

and insert the following lines:

//output to postman console
console.log("PhotoId: " + pm.response.json()["photoId"]);
console.log("Base64: " + pm.response.json()["photo"]);

//output in visualize tab
let template = `<img src='{{img}}'/>`;

pm.visualizer.set(template, { 
    img: `data:image/jpeg;base64,${pm.response.json()["photo"]}`

In the “Visualize” tab you should now find your image

[SAPUI5] Call function of another controller using EventBus


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", () => {

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", {});

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


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


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!")
        // Now submit your changes
          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) {

			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

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(
                        .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"] = []

                    { 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)

[PDF.js] Prevent loading default pdf

This can be done by appending ?file= to the src path, like it is mentionend here.

<!-- "?file=" prevents loading the default document -->
<iframe id="pdf-js-viewer" src="/pdf/web/viewer.html?file=" title="webviewer" frameborder="0" width="100%" height="700" allowfullscreen="" webkitallowfullscreen=""/>

Or by setting the variable defaultUrl to blank using the onload event.

pdfViewer = document.getElementById("pdf-js-viewer");

pdfViewer.onload = () => {