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

[SAPUI5] Adding Lanes to a Process Flow

Manually adding Lanes to a Process Flow Control:

https://sapui5.hana.ondemand.com/#/api/sap.suite.ui.commons.ProcessFlow
https://sapui5.hana.ondemand.com/#/api/sap.suite.ui.commons.ProcessFlowLaneHeader
https://sapui5.hana.ondemand.com/#/entity/sap.suite.ui.commons.ProcessFlow/sample/sap.suite.ui.commons.sample.ProcessFlowUpdateLanes/code

In my case, there was no way to bind the model to the view, so I did the mapping for each ProcessFlowLaneHeader in the callback function after reading the oData entity.

view.xml

<flow:ProcessFlow id="process-flow"/>

controller.js

var oProcessFlow = this.getView().byId("process-flow")

var oRequestFilter = new sap.ui.model.Filter({
    path: "myId",
    operator: sap.ui.model.FilterOperator.EQ,
    value1: myId
})

this.getView().getModel().read("/WorkflowSet", {
    filters: [oFormularIdFilter],
    success: (oData, response) => {
        for (var i = 0; i < oData.results.length; i++) {
            var oLaneHeader = new ProcessFlowLaneHeader({
                laneId: oData.results[i].LaneId,
                iconSrc: oData.results[i].IconSrc,
                text: oData.results[i].Text,
                position: oData.results[i].Position,
                state: [{state: oData.results[i].State, value: "100"}]
            });
            oProcessFlow.addLane(oLaneHeader)
        }
    },
    error: oError => {
        sap.m.MessageToast.show("An error occured while reading entity /WorkflowSet.")
    }
});

[SAPUI5] Place fields horizontally opposite each other

Because I always forget how to place a field at the beginning of a line and one opposite at the end, I create this post…

The easiest way to do this is using a Flex Box and the property justifyContent like in this sample: https://sapui5.hana.ondemand.com/#/entity/sap.m.FlexBox/sample/sap.m.sample.FlexBoxOpposingAlignment

<FlexBox alignItems="Center" width="100%" justifyContent="SpaceBetween">
    <Text text="Text1" textAlign="Begin"/>
    <Text text="Text2" textAlign="End"/>
</FlexBox>

Another way is a using a Toolbar and a ToolbarSpacer. But of course a Toolbar should only be used, when it makes sense to use a Toolbar.

<Toolbar>
    <Text text="Text1" textAlign="Begin"/>
    <ToolbarSpacer/>
    <Text text="Text2" textAlign="End"/>
</Toolbar>

[CAP] UploadSet http test file

How to create an uploadSet in combination with a CAP backend: https://blogs.sap.com/2021/08/18/my-journey-towards-using-ui5-uploadset-with-cap-backend/

You can test the uploadSet CAP backend part using these http calls:

### Create file
# @name file
POST http://localhost:4004/v2/admin/Files
Authorization: Basic admin:
Content-Type: application/json

{
    "mediaType": "image/png",
    "fileName": "picture.png",
    "size": 1000
}

### Fill Variable from Response
@ID = {{file.response.body.$.d.ID}}

### Upload Binary PNG content
PUT http://localhost:4004/v2/admin/Files({{ID}})/content
Authorization: Basic admin:
Content-Type: image/png

< ./picture.png

### Get uploaded png
GET http://localhost:4004/v2/admin/Files({{ID}})/content
Authorization: Basic admin:

The picture.png file must be in the same folder as the http test file.

[ABAP] OData – GET_STREAM implementation to return a PDF

  METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream.

* This method get's called when a media file is queried with $value. A binary stream will be returned.

    TRY.
        DATA(file_id) = VALUE zfile_id( it_key_tab[ name = 'file_id' ]-value ).
      CATCH cx_sy_itab_line_not_found.
        RETURN. " leave here when no file_id provided
    ENDTRY.
  
    DATA(ls_file) = get_file( file_id ) " read your file you want to return (if it's not yet a binary stream, convert it)

    DATA(ls_stream) = VALUE ty_s_media_resource( value     = ls_file-value
                                                 mime_type = ls_file-mimetype ). " in my case it's 'application/pdf'

    " necessary to display the filename instead of $value in the viewer title
    TRY.
        " create pdf object
        DATA(lo_fp)     = cl_fp=>get_reference( ).
        DATA(lo_pdfobj) = lo_fp->create_pdf_object( connection = 'ADC' ).
        lo_pdfobj->set_document( pdfdata = ls_stream-value ).
        " set title
        lo_pdfobj->set_metadata( VALUE #( title = ls_file-filename ) ).
        lo_pdfobj->execute( ).
        " get pdf with title
        lo_pdfobj->get_document( IMPORTING pdfdata = ls_stream-value ).

      CATCH cx_fp_runtime_internal
            cx_fp_runtime_system
            cx_fp_runtime_usage INTO DATA(lo_fpex).
    ENDTRY.

    copy_data_to_ref( EXPORTING is_data = ls_stream
                      CHANGING  cr_data = er_stream ).

    " necessary for the pdf to be opened inline instead of a download (also sets the filename when downloaded)
    /iwbep/if_mgw_conv_srv_runtime~set_header( VALUE #( name  = 'content-disposition'
                                                        value = |inline; filename={ ls_file-filename }| ) ).

  ENDMETHOD

Quick way to open a PDFViewer in your UI5 App:

			const pdfViewer = new PDFViewer()
			pdfViewer.setSource("/sap/opu/odata/ZMY_SEVICE" + my_path + "/$value")  // my_path could be something like this "/PdfSet('file_id')"
			pdfViewer.setTitle("My PDFViewer Title") // title of the popup, not the viewer
			pdfViewer.open()

[SAPUI5] Get data of an Item of a List or Table

All options have in common that you first try to get the binding context from the list/table element via the event. Having the right context, you can either use the getProperty() function to get a specific property, or use the getObject() function to get all data.

onClick: function (oEvent) {
    // Option 1
    oEvent.getParameters().item.getBindingContext().getProperty("ID") 
    // Option 2
    oEvent.getParameters().item.getBindingContext().getObject().ID
    // Option 3
    oEvent.getParameter("item").getBindingContext().getObject().ID 
    // Option 4
    oEvent.getSource().getBindingContext().getObject().ID 
}

Note: When using a List, it’s oEvent.getParameters().listItem instead of oEvent.getParameters().item.

Or you could also use the sPath property from the binding context and directly get the data from the model.

onClick: function (oEvent) {
    // Option 5
    const sPath = oEvent.getSource().getBindingContext().sPath 
    // 5a
    this.getView().getModel().getProperty(sPath).ID 
    // 5b
    this.getView().getModel().getProperty(sPath + "/ID") 
}

[SAPUI5] Get Icon for MimeType

API Reference IconPool: https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.core.IconPool

In my case I used a custom formatter and the getIconForMimeType() function from the IconPool to get the required Icons for my list items.

myView.xml

icon="{path: 'mimeType', formatter: '.formatter.getIconForMimeType'}">

formatter.js

        getIconForMimeType: function(sMimeType) {
           return sap.ui.core.IconPool.getIconForMimeType(sMimeType)
        }

[SAPUI5] Get i18n texts

To simply get access to i18n texts, I useally add this helper function to my BaseController.js

// helper for direct access to the ResourceBundle getText() function
getText : function (sTextKey, aParamter) {
    return this.getOwnerComponent().getModel("i18n").getResourceBundle().getText(sTextKey, aParamter)
}

Texts can then be read in every controller with

// i18n: objects=Amount of objects: {0}
this.getText("objects", [iLength])

[SAPUI5] Parse error message

If the error response is type json:

        oModel.callFunction("/myFunction", {
          method: "GET",
          urlParameters: {
            ID: myID,
          },
          success: oData => console.log(oData),
          error: oError => MessageBox.error(JSON.parse(oError.responseText).error.message.value, { title: "An error occurred" })
        });

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

 MessageBox.error(jQuery.parseXML(oError.response.body).querySelector("message").textContent)

[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().