Introduction
The sap.m.plugins.UploadSetwithTable control is the successor of the sap.m.upload.UploadSet control.
It provides a table-based user interface to upload, display, and download files, making it perfect for document management scenarios.
By default, the UploadSetwithTable control performs a POST request to upload attachments.
However, in certain scenarios, the upload process requires a two-step approach:
A POST request to create the attachment record.A PUT request to upload the actual file content to the record created in the previous step.
This two-step process is required when working with CAP (Cloud Application Programming Model), which is the focus of this blog post. (Note: This approach may differ when using an ABAP backend).
The Problem and the Motivation for the Blog Post
This blog post was written during our Proof of Concept (POC), where we have a requirement to create a reusable attachment component. After some research, I couldn’t find any blog post or example demonstrating how to use the UploadSetwithTable control with a real backend. All available examples, including those on the SAPUI5 documentation site, use a mock server and do not provide a real backend integration.
Thus, I decided to document this solution and share it with the community, as this could potentially save a lot of time for developers facing similar challenges.
Who Is This Blog Post For?
This solution is intended for scenarios where you have a non-draft enabled application or a freestyle application. If you are working with a Fiori application that supports draft functionality, this blog post may not be relevant for you. In draft-enabled applications, you get the complete attachment handling functionality out of the box by implementing the standard CAP attachment plugin.
We initially referred to the following blog post, which provides a good starting point for working with draft-enabled applications.
Implementation Breakdown
1- Backend Design
For our backend design, we utilize the ManagedAttachment entity from cap-js/attachments. This is the same entity used in the central attachment plugin that comes with CAP. We define the following in our CDS model:
using {Attachments as ManagedAttachment} from ‘@cap-js/attachments’;
entity Attachments : ManagedAttachment {
ParentId : UUID;
}
To add the Attachment plugin, execute the following command
npm add @cap-js/attachments
ParentId references the parent record to which the attachment belongs. For example the header or item entity in your list report application.
The ManagedAttachment entity comes with essential properties like filename, mimeType, and content, which are used to manage the attachment’s data.
2- XML Cod
<core:FragmentDefinition
xmlns:core=”sap.ui.core”
xmlns=”sap.m”
xmlns:upload=”sap.m.upload”>
<Table
id=”attachmentsTable”
core:require=”{handler: ‘namespace of your JS handler/controller’}”
items=”{path: ‘Attachments’, templateShareable: false}”
mode=”MultiSelect”
selectionChange=”handler.onSelectionChange”>
<headerToolbar>
<OverflowToolbar>
<ToolbarSpacer />
<upload.ActionsPlaceholder id=”uploadButton” placeholderFor=”UploadButtonPlaceholder” />
<Button
id=”downloadSelectedAttachments”
text=”Download”
enabled=”false”
press=”handler.onDownloadFiles”
/>
<Button
id=”deleteSelectedAttachments”
text=”Delete”
enabled=”false”
press=”handler.onDeleteFiles”
/>
</OverflowToolbar>
</headerToolbar>
<dependents>
<plugins.UploadSetwithTable
beforeUploadStarts=”handler.onBeforeUploadStart”
beforeInitiatingItemUpload=”handler.onBeforeUploadInit”
actions=”uploadButton”>
<rowConfiguration>
<upload.UploadItemConfiguration
fileNamePath=”filename”
urlPath=”url”
mediaTypePath=”mimeType”/>
</rowConfiguration>
<uploader>
<upload.UploaderTableItem httpRequestMethod=”Put”/>
</uploader>
</plugins.UploadSetwithTable>
</dependents>
<columns>
<Column id=”fileName” importance=”High”>
<header>
<Label text=”Name/Type” />
</header>
</Column>
<Column id=”url” importance=”High” visible=”false”>
<header>
<Label text=”Url” />
</header>
</Column>
</columns>
<items>
<ColumnListItem>
<HBox>
<Link
text=”{filename}”
class=”sapUiTinyMarginBottom”/>
</HBox>
<Text text=”{url}”/>
</ColumnListItem>
</items>
</Table>
</core:FragmentDefinition>
It’s important to highlight that we are defining our uploader under the aggregation uploader. This means we will skip the default implementation. if you removed the uploader aggregation, it means you will use the default uploader from the control which might still work for you.
3- JS Code
Before the Upload Start
Creating a New Attachment: A new record is created in the backend with a Post request. Note that in the response to the POST request, the content property is still null.Uploading the Content: Once the new attachment record is created and the ID is retrieved, a Put request is sent to upload the file content to the created record.Update the url property: To enable the download functionality using the UploadSetwithTable control, the context of the selected attachment must have the url property filled.
The url should point to the content property of the created attachment record. This is a crucial step that is not clearly mentioned in the SAPUI5 documentation .
onBeforeUploadStart: function (oEvent) {
const oTable = oEvent.getSource().getParent();
const oModel = oTable.getModel();
const oItem = oEvent.getParameter(“item”);
const oBinding = oTable.getBinding(“items”);
const oFile = oItem.getFileObject();
const sServiceUrl = ‘your service url as in the manifest file’;
oBinding.create({
“filename”: oFile.name,
“mimeType”: oFile.type
});
// Trigger the POst request
oModel.submitBatch(“attachmentsGroup”);
oBinding.attachEventOnce(“createCompleted”, function (oEvent) {
const oContext = oEvent.getParameter(“context”);
const oUploadSetwithTable = oTable.getDependents()[0];
const oUploader = oUploadSetwithTable.getUploader(); // Retrieve our uploader defined in the XML
const sUploadUrlUrl = sServiceUrl + oContext.getPath() + ‘/content’;
oUploader.setUploadUrl(sUploadUrlUrl);
oUploader.uploadItem(oItem); // Trigger the Put request
MessageToast.show(“Attachments Added”);
// Update the URL property so it points to the content property
oContext.setProperty(“url”, sUploadUrlUrl).then(function () {
oTable.getBindingContext().requestSideEffects([{ $NavigationPropertyPath: “Attachments” }]);
});
});
},
Example of how the sUploadUrl looks like in our scenario:
<SERVICE_URL>/<ENTITY>(<PARENT_ID>)/Attachments(<ATTACHMENT_ID>)/content
Before The Upload Init
It is important to clear the upload URL from the previous action before initiating a new upload If not, the new content which you are uploading will be pushed to the previous file that you have already attached.
onBeforeUploadInit: function (oEvent) {
const oUploader = oEvent.getSource().getUploader();
oUploader.setUploadUrl(“”);
}
Now we have covered the whole upload logic. The rest is to handle the selection change, download, and delete actions.
onSelectionChange: function(oEvent) {
const oTable = oEvent.getSource();
const aSelectedItems = oTable?.getSelectedContexts();
const oDownloadBtn = this.byId(“downloadSelectedAttachments”);
const oDeleteBtn = this.byId(“deleteSelectedAttachments”);
if (aSelectedItems.length > 0) {
oDownloadBtn.setEnabled(true);
oDeleteBtn.setEnabled(true);
} else {
oDownloadBtn.setEnabled(false);
oDeleteBtn.setEnabled(false);
}
},
onDownloadFiles: function(oEvent) {
const oTable = this.byId(“attachmentsTable”);
const oUploadSet = oTable.getDependents()[0];
const aContexts = oTable.getSelectedContexts();
aContexts.forEach(oContext => {
oUploadSet.download(oContext, true);
});
},
onDeleteFiles: function () {
const oTable = this.byId(“attachmentsTable”);
const aContexts = oTable.getSelectedContexts();
aContexts.forEach(oContext => {
oContext.delete().then(()=> {
MessageToast.show(“Attachments Deleted”);
});
});
}
Below is a simplified view of the attachments table.
Issue We Faced with the Control:
Even though we are using a custom uploader, which should ideally suppress the default upload behavior of the control, the control still attempts to execute the upload, leading to a UI dump. We plan to discuss this with our CoE, and if it turns out to be a bug in the control, we will work on addressing it accordingly.
I hope this blog post helps the community understand how to integrate the UploadSetwithTable control with real backend systems.
All the best
IntroductionThe sap.m.plugins.UploadSetwithTable control is the successor of the sap.m.upload.UploadSet control.It provides a table-based user interface to upload, display, and download files, making it perfect for document management scenarios.By default, the UploadSetwithTable control performs a POST request to upload attachments.However, in certain scenarios, the upload process requires a two-step approach:A POST request to create the attachment record.A PUT request to upload the actual file content to the record created in the previous step.This two-step process is required when working with CAP (Cloud Application Programming Model), which is the focus of this blog post. (Note: This approach may differ when using an ABAP backend).The Problem and the Motivation for the Blog PostThis blog post was written during our Proof of Concept (POC), where we have a requirement to create a reusable attachment component. After some research, I couldn’t find any blog post or example demonstrating how to use the UploadSetwithTable control with a real backend. All available examples, including those on the SAPUI5 documentation site, use a mock server and do not provide a real backend integration.Thus, I decided to document this solution and share it with the community, as this could potentially save a lot of time for developers facing similar challenges.Who Is This Blog Post For?This solution is intended for scenarios where you have a non-draft enabled application or a freestyle application. If you are working with a Fiori application that supports draft functionality, this blog post may not be relevant for you. In draft-enabled applications, you get the complete attachment handling functionality out of the box by implementing the standard CAP attachment plugin.We initially referred to the following blog post, which provides a good starting point for working with draft-enabled applications.Implementation Breakdown1- Backend DesignFor our backend design, we utilize the ManagedAttachment entity from cap-js/attachments. This is the same entity used in the central attachment plugin that comes with CAP. We define the following in our CDS model: using {Attachments as ManagedAttachment} from ‘@cap-js/attachments’;
entity Attachments : ManagedAttachment {
ParentId : UUID;
} To add the Attachment plugin, execute the following command npm add @cap-js/attachments ParentId references the parent record to which the attachment belongs. For example the header or item entity in your list report application.The ManagedAttachment entity comes with essential properties like filename, mimeType, and content, which are used to manage the attachment’s data.2- XML Cod <core:FragmentDefinition
xmlns:core=”sap.ui.core”
xmlns=”sap.m”
xmlns:upload=”sap.m.upload”>
<Table
id=”attachmentsTable”
core:require=”{handler: ‘namespace of your JS handler/controller’}”
items=”{path: ‘Attachments’, templateShareable: false}”
mode=”MultiSelect”
selectionChange=”handler.onSelectionChange”>
<headerToolbar>
<OverflowToolbar>
<ToolbarSpacer />
<upload.ActionsPlaceholder id=”uploadButton” placeholderFor=”UploadButtonPlaceholder” />
<Button
id=”downloadSelectedAttachments”
text=”Download”
enabled=”false”
press=”handler.onDownloadFiles”
/>
<Button
id=”deleteSelectedAttachments”
text=”Delete”
enabled=”false”
press=”handler.onDeleteFiles”
/>
</OverflowToolbar>
</headerToolbar>
<dependents>
<plugins.UploadSetwithTable
beforeUploadStarts=”handler.onBeforeUploadStart”
beforeInitiatingItemUpload=”handler.onBeforeUploadInit”
actions=”uploadButton”>
<rowConfiguration>
<upload.UploadItemConfiguration
fileNamePath=”filename”
urlPath=”url”
mediaTypePath=”mimeType”/>
</rowConfiguration>
<uploader>
<upload.UploaderTableItem httpRequestMethod=”Put”/>
</uploader>
</plugins.UploadSetwithTable>
</dependents>
<columns>
<Column id=”fileName” importance=”High”>
<header>
<Label text=”Name/Type” />
</header>
</Column>
<Column id=”url” importance=”High” visible=”false”>
<header>
<Label text=”Url” />
</header>
</Column>
</columns>
<items>
<ColumnListItem>
<HBox>
<Link
text=”{filename}”
class=”sapUiTinyMarginBottom”/>
</HBox>
<Text text=”{url}”/>
</ColumnListItem>
</items>
</Table>
</core:FragmentDefinition> It’s important to highlight that we are defining our uploader under the aggregation uploader. This means we will skip the default implementation. if you removed the uploader aggregation, it means you will use the default uploader from the control which might still work for you.3- JS CodeBefore the Upload StartCreating a New Attachment: A new record is created in the backend with a Post request. Note that in the response to the POST request, the content property is still null.Uploading the Content: Once the new attachment record is created and the ID is retrieved, a Put request is sent to upload the file content to the created record.Update the url property: To enable the download functionality using the UploadSetwithTable control, the context of the selected attachment must have the url property filled.The url should point to the content property of the created attachment record. This is a crucial step that is not clearly mentioned in the SAPUI5 documentation . onBeforeUploadStart: function (oEvent) {
const oTable = oEvent.getSource().getParent();
const oModel = oTable.getModel();
const oItem = oEvent.getParameter(“item”);
const oBinding = oTable.getBinding(“items”);
const oFile = oItem.getFileObject();
const sServiceUrl = ‘your service url as in the manifest file’;
oBinding.create({
“filename”: oFile.name,
“mimeType”: oFile.type
});
// Trigger the POst request
oModel.submitBatch(“attachmentsGroup”);
oBinding.attachEventOnce(“createCompleted”, function (oEvent) {
const oContext = oEvent.getParameter(“context”);
const oUploadSetwithTable = oTable.getDependents()[0];
const oUploader = oUploadSetwithTable.getUploader(); // Retrieve our uploader defined in the XML
const sUploadUrlUrl = sServiceUrl + oContext.getPath() + ‘/content’;
oUploader.setUploadUrl(sUploadUrlUrl);
oUploader.uploadItem(oItem); // Trigger the Put request
MessageToast.show(“Attachments Added”);
// Update the URL property so it points to the content property
oContext.setProperty(“url”, sUploadUrlUrl).then(function () {
oTable.getBindingContext().requestSideEffects([{ $NavigationPropertyPath: “Attachments” }]);
});
});
}, Example of how the sUploadUrl looks like in our scenario:<SERVICE_URL>/<ENTITY>(<PARENT_ID>)/Attachments(<ATTACHMENT_ID>)/content Before The Upload InitIt is important to clear the upload URL from the previous action before initiating a new upload If not, the new content which you are uploading will be pushed to the previous file that you have already attached. onBeforeUploadInit: function (oEvent) {
const oUploader = oEvent.getSource().getUploader();
oUploader.setUploadUrl(“”);
} Now we have covered the whole upload logic. The rest is to handle the selection change, download, and delete actions. onSelectionChange: function(oEvent) {
const oTable = oEvent.getSource();
const aSelectedItems = oTable?.getSelectedContexts();
const oDownloadBtn = this.byId(“downloadSelectedAttachments”);
const oDeleteBtn = this.byId(“deleteSelectedAttachments”);
if (aSelectedItems.length > 0) {
oDownloadBtn.setEnabled(true);
oDeleteBtn.setEnabled(true);
} else {
oDownloadBtn.setEnabled(false);
oDeleteBtn.setEnabled(false);
}
},
onDownloadFiles: function(oEvent) {
const oTable = this.byId(“attachmentsTable”);
const oUploadSet = oTable.getDependents()[0];
const aContexts = oTable.getSelectedContexts();
aContexts.forEach(oContext => {
oUploadSet.download(oContext, true);
});
},
onDeleteFiles: function () {
const oTable = this.byId(“attachmentsTable”);
const aContexts = oTable.getSelectedContexts();
aContexts.forEach(oContext => {
oContext.delete().then(()=> {
MessageToast.show(“Attachments Deleted”);
});
});
} Below is a simplified view of the attachments table.Issue We Faced with the Control:Even though we are using a custom uploader, which should ideally suppress the default upload behavior of the control, the control still attempts to execute the upload, leading to a UI dump. We plan to discuss this with our CoE, and if it turns out to be a bug in the control, we will work on addressing it accordingly.I hope this blog post helps the community understand how to integrate the UploadSetwithTable control with real backend systems.All the best Read More Technology Blogs by SAP articles
#SAP
#SAPTechnologyblog