Skip to Content
Technical Articles
Author's profile photo Arindam Seth

Extending Upload Collection and File Uploader to call PUT method during file upload

Let me first explain the requirement. In many SAP document management system, the document content is uploaded against an SAP object. For example, a notification might have 5 attachments associated with the Notification document number.

The well established way of uploading files in SAP Netweaver Gateway through ODATA is to implement the CREATE_STREAM method of the DPC_EXT class. Please see it here. CREATE_STREAM is called when we set HTTP method as POST in XHR call from frontend.

As we know, POST method call in ODATA does not support key parameters. However, PUT method can be called with key parameters since it is the method to update. So, the solution would be to call UPDATE_STREAM from the DPC_EXT class of the generated project. This can be achieved by using HTTP method PUT in the XHR call.

Also, in a scenario where we are creating a document and attaching the files to it, the document number against which we need to save the files uploaded is not available before saving the document. The way to do this at once from frontend(UI5) would be to wait for the document creation to complete, get the document number returned and then upload the files to the URI that has the document number. This means we would not have the upload URI before document save.


SAP has a standard control with useful UI to upload files. It is sap.m.UploadCollenction. Please see API reference here.

Standard sap.m.UploadCollection control uses sap.ui.unified.FileUploader internally to upload files using the HTTP POST method via XHR.

As I started working with sap.m.UploadCollection, I realized it had a quirk. The URL to upload the files need to be set before files are added to it. I cannot change the URL after files are added.

The challenge is to call the HTTP PUT method of the upload URI which is generated after save by setting upload URI at runtime; all the while using SAP provided controls for uploading the files because a good UI is already developed in these controls.

This required extending the standard controls to allow adding URLs on the fly and call HTTP PUT method.

For this I referred the open counterpart of SAP UI5, OpenUi5. The source code is hosted on Github here. On looking through the code and following this github issue , I found the way to enhance the standard control. Before you panic, please see this text from SAP. Specifically this part:

Most importantly, the core containing all central functionality and the most commonly used control libraries is identical in both deliveries. (For example, sap.msap.ui.layoutsap.ui.unified.)

So, the code in SAP UI5 libraries is identical to the open source counter part. We could also technically download the SAP UI5 runtime and go through the code. We would not need to refer OpenUi5 in this case. The choice is yours.

What we need to do is enhance sap.m.UploadCollection to let it dynamically set the URI of the file upload. This can be achieved by enhancing the upload() method. However, it internally calls the POST method of the URI you would supply during run time. That means you could not pass upload URI such as “/sap/opu/odata/Attachment(‘12345’)/$value”. To allow such URLs to function, another enhancement of the sap.ui.unified.FileUploader control is needed. During XHR requests, we would switch the methods to PUT instead of POST.

Please see the codes of both below:

sap.m.UploadCollection enhanced to CustomUploadCollection.js

sap.ui.define(
	[
		"sap/m/UploadCollection",
		"./CustomFileUploader"
	],
	function (UploadCollection, CustomFileUploader) {
		return UploadCollection.extend("CustomUploadCollection", {
			metadata: {},
			setUploadUrl: function (value) {
				this.setProperty("instantUpload", true, true); // disables the default check
				if (UploadCollection.prototype.setUploadUrl) {
					UploadCollection.prototype.setUploadUrl.apply(this, arguments); // ensure that the default setter is called. Doing so ensures that every extension or change will be executed as well.
					// Because before we call the original function we override the instantUpload property for short time, to disable the check
				}
				this.setProperty("instantUpload", false, true); // Afterwards we set back the instantUpload property to be back in a save and consistent state
			},

			upload: function (_url) {
				this.setUploadUrl(_url); //Custom URL setting at the time of upload

				if (this.getInstantUpload()) {
					jQuery.sap.log.error("Not a valid API call. 'instantUpload' should be set to 'false'.");
				}
				var iFileUploadersCounter = this._aFileUploadersForPendingUpload.length;
				// upload files that are selected through popup
				for (var i = 0; i < iFileUploadersCounter; i++) {
					this._iUploadStartCallCounter = 0;
					// if the FU comes from drag and drop (without files), ignore it
					if (this._aFileUploadersForPendingUpload[i].getValue()) {
						this._aFileUploadersForPendingUpload[i].setUploadUrl(_url); //Custom URL setting at the time of upload
						this._aFileUploadersForPendingUpload[i].upload();
					}
				}
				// upload files that are pushed through drag and drop
				if (this._aFilesFromDragAndDropForPendingUpload.length > 0) {
					// upload the files that are saved in the array
					this._oFileUploader._sendFilesFromDragAndDrop(this._aFilesFromDragAndDropForPendingUpload);
					// clean up the array
					this._aFilesFromDragAndDropForPendingUpload = [];
				}
			},

			_getFileUploader: function () {
				var bUploadOnChange = this.getInstantUpload();
				if (!bUploadOnChange || !this._oFileUploader) { // In case of instantUpload = false always create a new FU instance. In case of instantUpload = true only create a new FU instance if no FU instance exists yet
					var sTooltip = this.getInstantUpload() ? this._oRb.getText("UPLOADCOLLECTION_UPLOAD") : this._oRb.getText("UPLOADCOLLECTION_ADD");
					this._iFUCounter = this._iFUCounter + 1; // counter for FileUploader instances
					this._oFileUploader = new CustomFileUploader(this.getId() + "-" + this._iFUCounter + "-uploader", { //Use custom file uploader here
						buttonOnly: true,
						buttonText: sTooltip,
						tooltip: sTooltip,
						iconOnly: true,
						enabled: this.getUploadEnabled(),
						fileType: this.getFileType(),
						icon: "sap-icon://add",
						iconFirst: false,
						style: "Transparent",
						maximumFilenameLength: this.getMaximumFilenameLength(),
						maximumFileSize: this.getMaximumFileSize(),
						mimeType: this.getMimeType(),
						multiple: this.getMultiple(),
						name: "uploadCollection",
						uploadOnChange: bUploadOnChange,
						sameFilenameAllowed: true,
						uploadUrl: this.getUploadUrl(),
						useMultipart: false,
						sendXHR: true,
						change: [this._onChange, this],
						filenameLengthExceed: [this._onFilenameLengthExceed, this],
						fileSizeExceed: [this._onFileSizeExceed, this],
						typeMissmatch: [this._onTypeMissmatch, this],
						uploadAborted: [this._onUploadTerminated, this],
						uploadComplete: [this._onUploadComplete, this],
						uploadProgress: [this._onUploadProgress, this],
						uploadStart: [this._onUploadStart, this],
						visible: !this.getUploadButtonInvisible()
					});
				}
				return this._oFileUploader;
			},
			renderer: "sap.m.UploadCollectionRenderer"
		});
	}
);

 

sap.ui.unified.FileUploader enhanced to CustomFileUploader.js

 

sap.ui.define(
	["sap/ui/unified/FileUploader"],
	function (FileUploader) {
		return FileUploader.extend("CustomFileUploader", {
			metadata: {},

			_sendFilesWithXHR: function (aFiles) {
				var iFiles,
					sHeader,
					sValue,
					oXhrEntry,
					oXHRSettings = this.getXhrSettings();

				if (aFiles.length > 0) {
					if (this.getUseMultipart()) {
						//one xhr request for all files
						iFiles = 1;
					} else {
						//several xhr requests for every file
						iFiles = aFiles.length;
					}
					// Save references to already uploading files if a new upload comes between upload and complete or abort
					this._aXhr = this._aXhr || [];
					for (var j = 0; j < iFiles; j++) {
						//keep a reference on the current upload xhr
						this._uploadXHR = new window.XMLHttpRequest();

						oXhrEntry = {
							xhr: this._uploadXHR,
							requestHeaders: []
						};
						this._aXhr.push(oXhrEntry);
						oXhrEntry.xhr.open("PUT", this.getUploadUrl(), true); //Changed to PUT method here
						if (oXHRSettings) {
							oXhrEntry.xhr.withCredentials = oXHRSettings.getWithCredentials();
						}
						if (this.getHeaderParameters()) {
							var aHeaderParams = this.getHeaderParameters();
							for (var i = 0; i < aHeaderParams.length; i++) {
								sHeader = aHeaderParams[i].getName();
								sValue = aHeaderParams[i].getValue();
								oXhrEntry.requestHeaders.push({
									name: sHeader,
									value: sValue
								});
							}
						}
						var sFilename = aFiles[j].name;
						var aRequestHeaders = oXhrEntry.requestHeaders;
						oXhrEntry.fileName = sFilename;
						oXhrEntry.file = aFiles[j];
						this.fireUploadStart({
							"fileName": sFilename,
							"requestHeaders": aRequestHeaders
						});
						for (var k = 0; k < aRequestHeaders.length; k++) {
							// Check if request is still open in case abort() was called.
							if (oXhrEntry.xhr.readyState === 0) {
								break;
							}
							sHeader = aRequestHeaders[k].name;
							sValue = aRequestHeaders[k].value;
							oXhrEntry.xhr.setRequestHeader(sHeader, sValue);
						}
					}
					if (this.getUseMultipart()) {
						var formData = new window.FormData();
						var name = this.FUEl.name;
						for (var l = 0; l < aFiles.length; l++) {
							this._appendFileToFormData(formData, name, aFiles[l]);
						}
						formData.append("_charset_", "UTF-8"); // eslint-disable-line sap-no-dom-insertion
						var data = this.FUDataEl.name;
						if (this.getAdditionalData()) {
							var sData = this.getAdditionalData();
							formData.append(data, sData); // eslint-disable-line sap-no-dom-insertion
						} else {
							formData.append(data, ""); // eslint-disable-line sap-no-dom-insertion
						}
						if (this.getParameters()) {
							var oParams = this.getParameters();
							for (var m = 0; m < oParams.length; m++) {
								var sName = oParams[m].getName();
								sValue = oParams[m].getValue();
								formData.append(sName, sValue); // eslint-disable-line sap-no-dom-insertion
							}
						}
						oXhrEntry.file = formData;
						this.sendFiles(this._aXhr, 0);
					} else {
						this.sendFiles(this._aXhr, 0);
					}
					this._bUploading = false;
					this._resetValueAfterUploadStart();
				}

				return this;
			},
			renderer: "sap.ui.unified.FileUploaderRenderer"
		});
	}
);

 

As you can see, I am referring the CustomFileUploader.js in CustomUploadCollection.js. This ensures I can set upload URL of the file on the fly and call method PUT of the URI passed. So, I can pass files along with the document number against which to upload.

The way to use this in a UI5 application would look something like this:

<core:FragmentDefinition xmlns="my.test.project.control" xmlns:core="sap.ui.core">
	<CustomUploadCollection id="uploadCollection" maximumFilenameLength="55" maximumFileSize="10" multiple="true" sameFilenameAllowed="true"
		instantUpload="false" noDataText="{i18n>upload.noFiles}" change="onChange" fileDeleted="onFileDeleted"
		filenameLengthExceed="onFilenameLengthExceed" fileSizeExceed="onFileSizeExceed" typeMissmatch="onTypeMissmatch"
		uploadComplete="onUploadComplete" beforeUploadStarts="onBeforeUploadStarts"/>
</core:FragmentDefinition>

 

In your project, you might create a folder named “control”, where you can keep these two files and refer them from the XML view using XML namespace setting.

Thanks for reading.

 

Assigned Tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Deian Petrov
      Deian Petrov

      Hey,

      Did the exact steps, got

      "Cannot read property 'format' of undefined
      at f.t._onChange (UploadCollection-dbg.js:2551)"

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Arindam,

      thank you for this great post. Please be aware that at some point after SAPUI5 1.71 SAP introducted the setHttpRequestMethod. That can replace your approach when you have the matching SAPUI5 Version.

      Best regards
      Gregor

      Author's profile photo Nic Botha
      Nic Botha

      Hi,

      I used the setHttpRequestMethod as mentioned by Gregor. But this did not work without also setting

      setSendXHR(true). Else it is a form request and only POST / GET allowed. At least that is my understanding.
      Regards,
      Nic