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

[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()) {
 "Nothing to do.")
        // Now submit your changes
          success: () =>"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'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
                        .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'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 = () => {

[CAP] Using a Tree in SAPUI5 Freestyle app

The following Links helped me implementing the tree functionality:

Define the data model in data-model.cds

entity Node {
    key NodeID         : Integer;
        HierarchyLevel : Integer;
        ParentNodeID   : Integer;
        Description    : String;
        drillState     : String;

Create testdata in my.test-Node.csv


and deploy the testdata to your local sql db

cds deploy --to sqlite:db/test.db

Service Definition in test-service.cds

using my.test as db from '../db/data-model';

service testService {
     entity Nodes as projection on db.Node;

add the Tree controll to your Fiori UI view Tree.view.xml

		    items="{path: '/Nodes',
				    parameters : {
		                countMode: 'Inline',
                        numberOfExpandedLevels: 3, 
                        treeAnnotationProperties: { hierarchyLevelFor : 'HierarchyLevel', 
                                                    hierarchyNodeFor : 'NodeID', 
                                                    hierarchyParentNodeFor : 'ParentNodeID', 
                                                    hierarchyDrillStateFor : 'drillState' 
			<StandardTreeItem title="{Description}"/>

The output should be similar to this:

[SAPUI5] Filter on Model read

this.getModel().read("Object", {
                filters: [
                    new Filter({
                        path: "firstName",
                        operator: FilterOperator.EQ,
                        value1: "Max"
                    new Filter({
                        path: "lastName",
                        operator: FilterOperator.EQ,
                        value1: "Mustermann"
                success: oData => { }
                error: err => { }

[nodejs] read and write a file

        const fs = require("fs")

        try {
            // read from local folder
            const localPDF = fs.readFileSync('PDFs/myFile.pdf')

            //write back to local folder
            fs.writeFileSync('PDFs/writtenBack.pdf', localPDF )

        } catch (err) {

Converting to Base64

        try {
            // read from local folder
            const localPDF = fs.readFileSync('PDFs/myFile.pdf')
            const localBase64 = localPDF.toString('base64')

            //write back to local folder
            fs.writeFileSync(`PDFs/writtenBack.pdf`, localBase64, {encoding: 'base64'})

        } catch (err) {

Reading and writing using streams with pipe

        //read and write local file
        const reader = fs.createReadStream("PDFs/myFile.pdf")
        const writer = fs.createWriteStream('PDFs/writtenBack.pdf');

[SAPUI5] Binding with filter on XML View

There are two ways to use a filter.

Option 1:

				path: '/myItems',
				parameters : {
				  $filter : 'itemName eq \'myItemName\'',
				  $orderby : 'createdAt desc'

Option 2:

				path: '/myItems',
				parameters : {
				  $orderby : 'createdAt desc'
				filters : {                                     
				  path : 'itemName ',
				  operator : 'EQ',
				  value1 : 'myItemName'