Technical Articles
Fiori Elements List Report – display and upload images (final part)
In the last blog post I discussed about how we can display images in a list report column in an app created using Fiori elements. In this blog, we shall discuss about how to upload image.
Before going through this blog post, it is recommended to read the previous one.
Sample source code for frontend app is here.
Scenario:
There will be a button in a column for each article record of the list and user will upload the image from local desktop/client system.
Yes, you guessed it right. The action column is added as an extension to the list report app. Please refer here for more details on how to add a column. We are using responsive table in this blog, but if your app uses a grid table, refer here.
Steps:
- Note: Please ensure the project type is smart template in project settings. This is automatically done when the project is created using ‘List report’ template wizard in SAP Web IDE.
Right click on the project -> New -> Extension
Choose ‘List Report Extension’ in ‘Template Selection’ tab and choose ‘Next’:
Choose Entity set from drop down and provide a name for the fragment to be generated.
Choose ‘Next’.
Choose Finish.
This will create a folder ‘ext’ and a fragment upload.fragment.xml within.
Create another file in the folder ‘ext’ : uploadCell.fragment.xml. Refer SAP documentation to understand what is happening.
So two fragments will be created :
a. sos.uploadimage.ext.view.upload.fragment.xml,
b. sos.uploadimage.ext.view.uploadCell.fragment.xml
Alos after the list report app is extended, manifest.json file should look like below.
Below are the sample code.
manifest.json
"sap.ui5": {
...
"extends": {
"extensions": {
"sap.ui.viewExtensions": {
"sap.suite.ui.generic.template.ListReport.view.ListReport": {
"ResponsiveTableColumnsExtension|ZCDS_C_SKUIMAGE": {
"type": "XML",
"className": "sap.ui.core.Fragment",
"fragmentName": "sos.uploadimage.ext.view.upload"
},
"ResponsiveTableCellsExtension|ZCDS_C_SKUIMAGE": {
"type": "XML",
"className": "sap.ui.core.Fragment",
"fragmentName": "sos.uploadimage.ext.view.uploadCell"
}
}
},
"sap.ui.controllerExtensions": {
"sap.suite.ui.generic.template.ListReport.view.ListReport": {
"controllerName": "sos.uploadimage.ext.controller.ListReportExt",
"sap.ui.generic.app": {
"ZCDS_C_SKUIMAGE": {
"EntitySet": "ZCDS_C_SKUIMAGE"
}
}
}
}
}
},
...
}
sos.uploadimage.ext.view.upload.frgament.xml
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
<Column id="ExtensionWizard::ColumnBreakout">
<Text text="{i18next>action}"/>
<customData>
<core:CustomData key="p13nData" value='\{"columnKey": "Test", "columnIndex" : "101"}'/>
</customData>
</Column>
</core:FragmentDefinition>
sos.uploadimage.ext.view.uploadCell.frgament.xml
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:u="sap.ui.unified">
<VBox>
<u:FileUploader id="fileUploader" name="myFileUpload" tooltip="Upload your file to the local server" uploadComplete="handleUploadComplete"
uploadAborted="handleUploadAbort" buttonOnly="true" buttonText="Upload Image" sendXHR="true" uploadOnChange="true" useMultipart="false"
sameFilenameAllowed="true" change="handleValueChange" style="Emphasized" placeholder="Choose a file for Upload...">
<u:headerParameters>
<u:FileUploaderParameter name="slug" value="{CatalogSKU}"/>
</u:headerParameters>
</u:FileUploader>
</VBox>
</core:FragmentDefinition>
Note: In the sample code, we have used property sendXHR=”true” above.This property is not supported by Internet Explorer 9. Please adjust code accordingly.
2. We also need to add code to handle the events raised by the control sap.ui.unified.FileUploader introduced in fragment uploadCell.frgament.xml.
A quick way to add extension controller is again to extend using wizard as shown in step 1 but this time choosing ‘Action’ radio button in ‘Extension Settings’ tab of the wizard.
Delete the ‘Action’ part from manifest.json as we are not really using action button feature. [See the source code from github]
Extension wizard will also add another folder ‘controller’ and file ‘ListReportExt.controller.js’ within.
Overwrite the code in this file with the content below. [See the source code from github]
ListReportExt.controller.js
sap.ui.controller("sos.uploadimage.ext.controller.ListReportExt", {
onInit: function () {
var oSmartTable = this.byId("listReport");
oSmartTable.setEnableAutoBinding(true);
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleName: "sos.uploadimage.i18n.i18n"
});
this.getView().setModel(i18nModel, "i18next");
this.oBundle = this.getView().getModel("i18next").getResourceBundle();
},
handleValueChange: function (oEvent) {
this.getView().setBusy(true);
var oSource = oEvent.getSource();
if (oSource.getBindingContext().getProperty("CatalogSKU")) {
oSource.setUploadUrl(oSource.getModel().sServiceUrl +
oSource.getModel().createKey("/ZCDS_C_SKUIMAGE", {
CatalogSKU: oSource.getBindingContext().getProperty("CatalogSKU")
}) + "/to_SOSImage");
}
oSource.getModel().refreshSecurityToken();
oSource.addHeaderParameter(
new sap.ui.unified.FileUploaderParameter({
name: "x-csrf-token",
value: oSource.getModel().getHeaders()["x-csrf-token"]
}));
},
handleUploadComplete: function (oEvent) {
var sResponse = oEvent.getParameter("response");
this.getView().setBusy(false);
if (sResponse) {
var sMsg = "";
var sStatus = oEvent.getParameter("status");
if (sStatus.toString() === "201" || sStatus.toString() === "202") {
sMsg = this.oBundle.getText("uploadSuccess", [oEvent.getParameter("fileName")]);
oEvent.getSource().setValue("");
this.getView().byId("listReport").rebindTable();
} else {
sMsg = this.oBundle.getText("uploadError", [oEvent.getParameter("fileName")]);
oEvent.getSource().setValue("");
}
sap.m.MessageToast.show(sMsg);
}
},
handleUploadAbort: function (oEvent) {
this.getView().setBusy(false);
sap.m.MessageToast.show(this.oBundle.getText("uploadAborted"));
},
handleFileSize:function (oEvent) {
//this.getView().getModel(["i18next]").
var allMessage = this.getView().getModel("i18next").getResourceBundle().getText("fileSize",[oEvent.getParameter("fileName")]);
sap.m.MessageBox.show(allMessage, {
icon: sap.m.MessageBox.Icon.ERROR, // default sap-icon://message-success
title: this.getView().getModel("i18next").getResourceBundle().getText("sizeError"), // default
actions: sap.m.MessageBox.Action.OK, // default
onClose: null, // default
styleClass: "", // default
initialFocus: null, // default
textDirection: sap.ui.core.TextDirection.Inherit // default
});
},
handleTypeMismatch: function(oEvent) {
var allMessage = this.getView().getModel("i18next").getResourceBundle().getText("fileType",[oEvent.getParameter("fileName")]);
sap.m.MessageBox.show(allMessage, {
icon: sap.m.MessageBox.Icon.ERROR, // default sap-icon://message-success
title: this.getView().getModel("i18next").getResourceBundle().getText("typeError"), // default
actions: sap.m.MessageBox.Action.OK, // default
onClose: null, // default
styleClass: "", // default
initialFocus: null, // default
textDirection: sap.ui.core.TextDirection.Inherit // default
});
}
});
3. Function handleValueChange: This hander is triggered when a file is uploaded from local device. We are setting the uploadURl property of FIleUploader control appropriately to the navigation property to_SOSImage which is actually the media entity type.
4. Functions handleUploadComplete and handleUploadAbort issues appropriate messages if upload succeeds or fails respectively.
5. Now that frontend coding is done, in S/4HANA or backend system, add below code in method /iwbep/if_mgw_appl_srv_runtime~create_stream of DPC_EXT class.
METHOD /iwbep/if_mgw_appl_srv_runtime~create_stream.
* Local data declaration
DATA :
lv_matnr TYPE matnr,
lt_tab TYPE solix_tab,
lv_buffer TYPE xstring,
lv_imagekey TYPE char10,
lv_filesize TYPE i,
lv_url TYPE DIRNAME,
ls_entity TYPE zcl_z_images_mpc=>ts_zcds_i_imagetype.
FIELD-SYMBOLS : <lfs_cat_sku> TYPE zfiap_cat_sku.
IF iv_entity_name = 'ZCDS_I_IMAGEType'. " Check the Image entity
READ TABLE it_key_tab INTO DATA(ls_key_tab) WITH KEY name = 'CatalogSKU'.
IF sy-subrc = 0.
CALL FUNCTION 'CONVERSION_EXIT_MATN1_INPUT'
EXPORTING
input = ls_key_tab-value
IMPORTING
output = lv_matnr
EXCEPTIONS
length_error = 1
OTHERS = 2.
IF sy-subrc = 0.
ENDIF.
* Select Catalog details for the article
SELECT SINGLE * FROM zfiap_cat_sku
INTO @DATA(ls_cat_sku)
WHERE matnr = @lv_matnr.
IF sy-subrc = 0.
ASSIGN ls_cat_sku TO <lfs_cat_sku>.
ls_entity-catalogsku = <lfs_cat_sku>-matnr = lv_matnr.
ls_entity-catalogitmkey = <lfs_cat_sku>-ctlg_itm_key.
ls_entity-mime_type = <lfs_cat_sku>-mime_type = is_media_resource-mime_type.
SHIFT lv_matnr LEFT DELETING LEADING '0'.
* Pass the image name
<lfs_cat_sku>-image_name = ls_entity-image_name = iv_slug.
******************* IMPORTANT READING ******************************************************
**Generate a unique key for the image_key field here and assihm to <lfs_cat_sku>-image_key
**Below code saves the file in application directory, but adjust the code according
**to your own content repositiry needs
********************************************************************************************
* Convert rawstring to binary format
CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
EXPORTING
buffer = is_media_resource-value
IMPORTING
output_length = lv_filesize
TABLES
binary_tab = lt_tab[].
lv_imagekey = <lfs_cat_sku>-image_key.
* Get AL11 directory path
CALL FUNCTION 'ZDIR_INTERFACE_PATH_GET'
EXPORTING
im_dirname = 'DIR_INTERFACE'
IMPORTING
ex_path = lv_url.
* Prepare image URL of AL11 to save in catalog SKU table
CONCATENATE lv_url
'/SKU_IMAGES/'(001)
lv_matnr
'_'(002)
lv_imagekey
INTO <lfs_cat_sku>-image_url.
ls_entity-imageurl = <lfs_cat_sku>-image_url.
<lfs_cat_sku>-file_size = lv_filesize.
IF lt_tab[] IS NOT INITIAL.
* Upload the image to AL11
OPEN DATASET <lfs_cat_sku>-image_url FOR OUTPUT IN BINARY MODE.
IF sy-subrc = 0.
LOOP AT lt_tab INTO DATA(ls_tab).
TRANSFER ls_tab TO <lfs_cat_sku>-image_url.
ENDLOOP.
CLOSE DATASET <lfs_cat_sku>-image_url.
CALL FUNCTION 'ENQUEUE_EZFIAP_CAT_SKU'
EXPORTING
mode_zfiap_cat_sku = 'E'
mandt = sy-mandt
matnr = <lfs_cat_sku>-matnr
_scope = '2'
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
* Modify the catalog AKU table with image URL
MODIFY zfiap_cat_sku FROM <lfs_cat_sku>.
ENDIF.
CALL FUNCTION 'DEQUEUE_EZFIAP_CAT_SKU'
EXPORTING
mode_zfiap_cat_sku = 'E'
mandt = sy-mandt
matnr = <lfs_cat_sku>-matnr
_scope = '3'.
ENDIF.
ENDIF.
ENDIF.
ENDIF.
* Copy entity data to entity reference
copy_data_to_ref( EXPORTING is_data = ls_entity
CHANGING cr_data = er_entity ).
ENDIF.
ENDMETHOD.
As mentioned in the previous blog, for simplicity, we are storing the image file in application server directory, but adapt the code according to your own content repository.
6. Test the app by uploading an image from local device.
Conclusion
In this blog series, we saw how to display and upload image for each record in a list report created with Fiori Elements List Report Floorplan.
Great ! but I am still wondering if they will bring some kind of annotation to handle this upload stuff too 🙂 Any ways on a serious note I really do not like the CDS with embedded BOPF because that will be phased out with new REST based ABAP programming model already available for PaaS.
Also adding annotations is like a mashed up junk- the simple this is not good, annotations with CDS
and smart templates is too nasty and ugly from a developer perspective just my personal opinion.
Thanks,
Prasenjit
Dear Sumit,
what UI5 version have you used in your implementation? I'm following the documentation Adding Custom Actions Using Extension Points to add an upload button to the Table header. The fragment is loaded without any issue when I click this button. But the handleUploadPress function that I've included in the controller of my extension is never called.
Best regards
Gregor
Hello Gregor,
I had tested this app in S/4HANA on-premise 1709 FPS02, UI5 version 1.52.9.
I follow your content regularly so can't help more as I know it might be repeating what you might have tried already 🙂 but one thing I always try are the good old tips by DJ.
Regards,
Sumit
Hi Sumit,
thank you for your quick response. I've solved the issue now by binding the button in code to my handler instead of defining the handler only in the fragment XML.
Best regards
Gregor
That's nice. All power to js! 🙂