nocin.eu

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

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

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

[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 = () => {
          pdfViewer.contentWindow.PDFViewerApplicationOptions.set(
            "defaultUrl",
            ""
          );
}; 

[CAP] Using a Tree in SAPUI5 Freestyle app

The following Links helped me implementing the tree functionality:

https://sapui5.hana.ondemand.com/#/entity/sap.m.Tree/sample/sap.m.sample.TreeOData

https://answers.sap.com/questions/13192367/sap-cds-how-to-add-hierarchy-annotations-saphierar.html

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

NodeID;HierarchyLevel;ParentNodeID;drillState;Description  
1;0;null;"expanded";"1"
2;0;null;"expanded";"2"
3;0;null;"expanded";"3"
4;1;1;"leaf";"1.1"
5;1;1;"expanded";"1.2"
6;2;5;"leaf";"1.2.1"
7;2;5;"leaf";"1.2.2"

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

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

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

https://nodejs.dev/learn/reading-files-with-nodejs

https://nodejs.dev/learn/writing-files-with-nodejs

        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) {
            console.error(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) {
            console.error(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');
        reader.pipe(writer)

[SAPUI5] Binding with filter on XML View

https://sapui5.hana.ondemand.com/sdk/#/topic/5338bd1f9afb45fb8b2af957c3530e8f.html

There are two ways to use a filter.

Option 1:

items="{
				path: '/myItems',
				parameters : {
				  $filter : 'itemName eq \'myItemName\'',
				  $orderby : 'createdAt desc'
				},
}">

Option 2:

items="{
				path: '/myItems',
				parameters : {
				  $orderby : 'createdAt desc'
				},
				filters : {                                     
				  path : 'itemName ',
				  operator : 'EQ',
				  value1 : 'myItemName'
				},
}">