Skip to Content

Some days ago I stumbled upon at least 3 different ways implemented in our SAPUI5-Apps to start a download of a file. All 3 approaches produced different behavior in a desktop browser and the SAP Fiori Client. I was wondering, why this happens and whether there is one best way to initiate a download in all scenarios (<spoiler>, which there is not!</spoiler>).

If you are in a hurry or just not interested in the details of my test results, never mind, skip the next 10 headings and just scroll down to my conclusion.

Preface

First thing I remembered was, that the server can deliver files with 2 different values for the response header “content-disposition”. If it is “inline”, browsers should take it as a hint to display the file if possible. So second thing was to recognize, that in this case, the behavior depends on the file format. If it is a common web format like pdf, jpg or gif (or if the user has installed additional plugins to display uncommon files), the browser will not offer a save dialog to the user. If content-disposition is set to “attachment”, browsers should not display the file, but save it to the file system. Chrome and other browsers follow this rule very strictly, e.g. if you use sap.m.PDFViewer and set the source to a file, which is delivered with content-disposition “attachment”, bowsers will download this file instead of displaying it.

Those findings led me to a 4-dimensional matrix of possible combinations, that I had to check:

(way of download start) × (browser/device) × (file format) × (content-disposition value)

During my research so far, I found 6 desperate algorithms to start a download. Three of them can be parameterized to load in a new window/tab or not. So we have at least 9 ways to do this. If you are aware of even more possibilities, please feel free to share it in the comment section.

I wrote a little SAPUI5-App to test my implementations against Chrome 68, Firefox 61 and Safari 11 on macOS X 10.13.6, Safari and SAP Fiori Client 1.11.4 on iOS 11.4.1 and IE 11 on Win 8.1 (yes, some of our customers still use it!). That makes 6 different browsers/devices. I deployed the app to a Fiori Launchpad hosted on SAP Cloud Platform running SAPUI5 version 1.54. You may download the app from GitHub, but you have to add your own URLs to the different download files at the bottom of Downloadtest.controller.js because I cannot give access to my test system.

I tested with jpg, pdf, txt (I call them common web formats from now on), docx, xcf (as an example of not so common file formats), zip and an incorrect URL. That counts to 7.

9 × 6 × 7 × 2 = 756

That would be a lot of clicks to test 😉 I decided to test with Chrome on macOS and SAP Fiori Client on iOS in the first round (252 clicks left) and only test my high rated implementations with the other browsers and devices.

So here is my first approach:

1) Download with HTML a-element

var oA = document.createElement("a");
oA.href = sURL;
oA.style.display = "none";
document.body.appendChild(oA);
oA.click();
document.body.removeChild(oA);

What happens here is, that we create an a-element, set its href-attribute to the URL, define that it should not be displayed, add it to the document body (which gives you an ESLint warning, because we should not manipulate the document object model this way), call its click-function and remove it from the body again (ESLint warning again). This could be easily done with jQuery, too. But for legibility, I chose this variant.

When testing with SAP Fiori Client on iOS pdf, jpg, txt, and docx are displayed regardless of the value of content-disposition. The SAPUI5-App has been left, there are no actions displayed and you cannot swipe in any direction to navigate back. You have to open the Fiori Client menu by double touching the surface and choose “Back”.

txt file in SAP Fiori Client on iOS downloaded with approach 1)
preview with no displayed actions, no back swipe double touch for SAP Fiori Client menu, after choosing “Back” the SAPUI5-app is loaded from scratch

This loads the previously left SAPUI5-App from scratch, which takes some time. The strange thing is, that by clicking on the xcf or zip link a “Page Cannot Be Found” error message appears, which is shown for incorrect URLs, too.

zip file in SAP Fiori Client on iOS downloaded with approach 1)
error message, SAP Fiori Client menu is displayed immediately

This is definitely nothing I want to give to any customer.

When testing with Chrome on macOS we experience different reactions for the two varying content-disposition values. While “attachment” leads to a download to the file system for every document, “inline” displays common web formats and downloads all other formats. For incorrect URLs, Chrome shows an error page in the same tab and you have to reload the SAPUI5-App ba navigating back. This is not a perfect user experience.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙁 🙁

2) Download with HTML a-element and target = “_blank”

var oA = document.createElement("a");
oA.href = sURL;
oA.target = "_blank";
oA.style.display = "none";
document.body.appendChild(oA);
oA.click();
document.body.removeChild(oA);

What happens here is nearly the same as in 1), except that we tell the client in line 3 to open the link in a new window/tab.

When testing with SAP Fiori Client on iOS all files are shown (the content or a document icon if not displayable) in a kind of preview app regardless of the value of content-disposition. There are buttons presented for closing the preview and to call available actions on the device like sharing or saving.

txt file in SAP Fiori Client on iOS downloaded with approach 2)
preview with “Done” and share button choosing share shows the share menu of the device

There is even a possibility to take a look inside the zip file.

zip file in SAP Fiori Client on iOS downloaded with approach 2)
preview of a zip file

When you click on “Done”, the preview app is closed and you are instantly back to the SAPUI5-App. By clicking on an incorrect URL SAP Fiori Client shows a reasonable error dialog as a popover, which can be easily closed.

incorrect URL in SAP Fiori Client on iOS downloaded with approach 2)
error dialog

This is definitely something I would ship to customers!

When testing with Chrome on macOS we experience different reactions for the two varying content-disposition values again. While “attachment” leads to a new window/tab for every file, which is closed instantly and a download is started, “inline” displays common web formats in the new window/tab and downloads all other formats. By clicking on an incorrect URL Chrome shows an error message in the new tab.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙂 🙂

3) Download with HTML iFrame-element

<core:HTML id="idIFrameDownload" visible="false"></core:HTML>
var sIFrame = "<iframe src=" + sURL + " style='display:none'></iframe>";
var oHTML = this.getView().byId("idIFrameDownload");
oHTML.setContent(sIFrame);

This technique is a little bit strange in my opinion, but it works for desktop browsers. First, you have to add an HTML-container control to your view. Second, you describe an iFrame-element which should not be displayed and set it as the content to the HTML-container.
I was not able to add the iFrame to the document.body as I did it with the a-element. Maybe browsers do not allow this due to security reasons. This makes this solution very inflexible.

When testing with SAP Fiori Client on iOS nothing happens at all.

When testing with Chrome on macOS every file is downloaded, if it is sent as “attachment”. Sent as “inline” nothing happens for common web formats, other formats are downloaded.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙁 🙁

4) Download with window.open

window.open(sURL);

window.open tells the browser to load the resource into a new window/tab. We are experiencing the same behavior as in 2) for all tested environments.

This could be a nice one-liner for all conditions if it would not open a new tab in desktop browsers and close it immediately for every file send as “attachment”. Although this is a safe way to start a download and even SAPUI5 uses it e.g. for the excel export function in a smart table, it is a very annoying behavior. But I still prefer this solution opposite to the a-element because we do not have to modify the dom and therefore do not violate the SAPUI5 code conventions.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙂 🙂

5) Download with window.open and windowName = _self

window.open(sURL, "_self");

While the default behavior for window.open seems to be “_blank” for the second parameter when its set to “_self” the resource is loaded into the same window/tab. We are experiencing the same behavior as in 1) for all tested environments. I prefer this solution for desktop browsers opposite to the a-element, again.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙁 🙁

6) Download with window.location.href

window.location.href = sURL;

Setting a new URL to the location object triggers a navigation to the new resource. ESLint does not like it when we do this.

It causes the same behavior as in 5) or 1), so we cannot use it for mobile, but it could be an alternative for desktop browsers if we do not care about SAPUI5 code conventions (but we should).

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙁 🙁

7) Download with sap.m.URLHelper.redirect

sap.m.URLHelper.redirect(sURL);

If you take a look into the code, you will see that SAPUI5 does this:

window.location.href = sURL;

So what is the benefit of hiding this code? SAPUI5 is giving us an API, that will (hopefully) work within many different browsers on different platforms and devices. We do not have to care about interoperability in the future. As long as we use sap.m.URLHelper.redirect SAPUI5 will execute code on as many configurations as possible to achieve the same output.

Additionally sap.m.URLHelper provides some more functions for calling a telephone number or open a mailto link. So if we could use one Helper for all URL/Link jobs, this would ease our work, too.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 🙂 🙁 🙁

8) Download with sap.m.URLHelper.redirect and bNewWindow = true

sap.m.URLHelper.redirect(sURL, true);

If you take a look into the code again, you will see that SAPUI5 does this:

var oWindow = window.open(sURL, "_blank");
if (!oWindow) {
	$.sap.log.error(this + "#redirect: Could not open " + sURL);
	// do some workaround stuff
}

This is pretty much the same as I did in 4). But additionally SAPUI5 reacts to the return value of window.open and if it is null or undefined SAPUI5 tries a workaround for known configurations that cause problems. This is a good reason to rate this solution higher than 4).

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙁 😐 🙂 🙂 🙂 🙂

9) Download with XMLHttpRequest

var oXHR = new XMLHttpRequest();
oXHR.open('GET', sURL);
oXHR.responseType = "blob";
oXHR.onload = function() {
	if (oXHR.status < 400 && oXHR.response && oXHR.response.size > 0) {
		var sHeaderContentDisposition = decodeURIComponent(oXHR.getResponseHeader("content-disposition"));
		var aHeaderParts = sHeaderContentDisposition.split("filename=");
		aHeaderParts = aHeaderParts[1].split("\"");
		var sFilenameFromServer = aHeaderParts[1];
		if (sap.ui.Device.browser.msie) {
			window.navigator.msSaveOrOpenBlob(oXHR.response, sFilenameFromServer);
		} else {
			// we have to use an a-element, because we have to set a filename
			// other download options do not allow this
			var oA = document.createElement("a");
			oA.href = window.URL.createObjectURL(oXHR.response);
			oA.style.display = "none";
			//if (sap.ui.Device.system.phone) {
			//  for whatver reason, it's not possible to set this for mobile devices
			//	oA.target = "_blank";
			//}
			oA.download = sFilenameFromServer;
			document.body.appendChild(oA);
			oA.click();
			document.body.removeChild(oA);
			// setTimeout is needed for safari on iOS
			setTimeout(function() {
				window.URL.revokeObjectURL(oA.href);
			}, 250);
		}
	} else {
		MessageToast.show("Something went wrong!");
	}
}.bind(this);
oXHR.send();

This is a little monster! Although I tried to cover all devices and browsers it still does not work in SAP Fiori Client on iOS. (Surprisingly, it runs in Safari on iOS.)

But let’s start at the beginning. We create an XMLHttpRequest-object, sending an AJAX-request to the server to get a binary large object (blob). The great thing is, that this will download any file from the server regardless of its content-disposition value. Disadvantages are, that we have a blob, which we have to save manually, and that we are limited to a file size specific for every browser. To be on the save side, I would never download a file larger than 500 MiBs this way.

In the onload-function, we read the filename from the content-disposition header. If our app is running in Internet Explorer we save the blob by calling window.navigator.msSaveOrOpenBlob (which gives us an ESLint warning). In all other cases we create an internal URL to the blob, create and initialize an a-element and simulate a click on it, similar to 1). Strangely enough, we cannot set the target to “_blank” this time.

Chrome macOS X SAP Fiori Client iOS
inline attachment inline attachment
🙂 🙂 🙁 🙁

10) And maybe a few more

We could import a third-party-library like FileSaver.js to save the blob from 9). But even FileSaver cannot save the blob in all circumstances. So be careful about the client side, if you want (or have) to go this way.

Maybe you stumbled upon sap.ui.core.util.File.save. The API states that it only covers Strings. I did not try to put a blob in here.

As I have already identified my winners and spent a lot of time so far I think it is time to announce my champions.

Conclusion

After all, I go for

7) sap.m.URLHelper.redirect(sURL) for desktop browsers

inline attachment
macOS X Win 8.1 macOS X Win 8.1
Chrome Firefox Safari IE 11 Chrome Firefox Safari IE 11
🙁 🙁 🙁 🙁 🙂 🙂 🙂 🙂

and

8) sap.m.URLHelper.redirect(sURL, true) for mobile devices.

inline attachment
Safari iOS SAP Fiori Client iOS Safari iOS SAP Fiori Client iOS
🙂 🙂 🙂 🙂 🙂 🙂 🙂 🙂

 

  • They are safe for all tested environments.
  • They are used in almost the same way. You just have to remember to pass true as the second parameter for mobile devices.
  • They provide the best user experience.
  • They are not limited to 500 MiB.
  • They are supported by SAPUI5 and SAP will probably react to any new circumstances, that have to be covered.
  • They do not cause any ESLint warnings or errors in SAP Web IDE.
  • And best they are one-liner!

As it is irrelevant for the mobile device whether the server delivers the file as “inline” or “attachment”, you have to make sure for desktop browsers that content-disposition header is set to “attachment”. This is the only caveat.

Maybe you are wondering, why I prefer sap.m.URLHelper.redirect(sURL) opposite sap.m.URLHelper.redirect(sURL, true) for desktop browsers, since the last one is safer for incorrect URLs because the error page is displayed in a new tab, which can be easily closed and there is no need to reload the SAPUI5-App.

I asked myself the following question: How likely is an incorrect URL in my app in comparison to a programming error? Since I have full control over those URLs the probability is pretty much the same. And because I do not put all my code in a try block to catch runtime errors, I do not cover incorrect URLs, too. If an incorrect URL is more likely in your scenario maybe because of user-generated URLs, you should use sap.m.URLHelper.redirect(sURL, true).

To report this post you need to login first.

4 Comments

You must be Logged on to comment or reply to a post.

Leave a Reply