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

[SAPUI5] Suppress default oData error message / display meaningful error messages

Create a separate ErrorHandler.js file, like it is described here and either do your own error handler implementation, or take the sample from here. To avoid displaying multiple errors at once, follow this chapter.

Thanks to the provided dsag sample, this is takes only a few minutes and improves the user experience a lot!

[SAPUI5] Create date object in UTC YYYY-MM-ddTHH:mm:ss

https://sapui5.hana.ondemand.com/#/api/sap.ui.core.format.DateFormat%23methods/format

            const today = new Date()
            const oDateTimeFormat = sap.ui.core.format.DateFormat.getDateTimeInstance({
                pattern: "yyyy-MM-ddTHH:mm:ss",
                UTC: true
            })
            const todayISO = oDateTimeFormat.format(today)

The UTC flag can also be set, when calling the format function.

            const today = new Date()
            const oDateTimeFormat = sap.ui.core.format.DateFormat.getDateTimeInstance({
                pattern: "yyyy-MM-ddTHH:mm:ss",
                //UTC: true
            })
            const todayISO = oDateTimeFormat.format(today, true)

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