How can I prevent $window.location.href from stopping ongoing requests? - javascript

In my angularjs (angularjs 1.3) app there is a place where the user can download a pdf file.
This is done in a controller using:
$window.location.href = 'pdf/123456';
which saves the file on the users computer. The url in the broswer is never really changed, the user is still on the same page in the angular app.
When I set the locations this way however, ongoing requests get cancelled when using Firefox. Using Chrome there is no problem. The only solution I've come up with is to wait with other requests until the pdf is downloaded but since that can take some time I would like to start them before the download is complete.
Is there any way of fixing this? Can I download the file in any other way? I don't want to open a popup window.

Another way to download a file is to create an <a> tag with the file as target and simulate a click on it. Like this:
var a = document.createElement('a');
a.href = 'pdf/123456';
a.download = 'document_name';
a.target = '_blank';
a.click();

Related

How can I add a mechanism on a web page to download an html file to their mobile device? (The link opens page rather than downloading the html file)

I have an html file that I would like to make available for offline use. This can be achieved easily by simply right clicking on the link on a desktop browser, then choosing "save as".
However, on a mobile device, I have tried using the download attribute on the anchor tag like so:
<a href="index.html" download>Download the page here.</a>
This seems to just take me to the page instead of downloading it.
My main goal is just to allow the user to download an html file to their mobile device.
There really isn't a right-click on mobile and holding down on the link shows a menu, but download isn't among them. The mobile browser itself may have a mechanism for saving a page once opened, but this would be sort of hard to walk the user through, and of course I'll have no idea what the mobile browser the user is using.
A special note here, the webpage I am trying to download does not have assets like images or style script files that need loaded in, all the assets are self-contained in the html file itself.
I actually came up with a solution to the problem, so I'll share it here, but I'm finding it hard to believe that there is not an easier way to do this.
My solution was essentially this, make an asynchronous request for the html file and read its text as a string. Then use that string to make a text blob and download it. Furthermore, to make sure the asset could be obtained from a local machine I served the data with a php file containing a header to ignore the cross origin restriction. (I didn't use fetch because I wanted to use settimeout).
To request an index.html file from the location https://www.mypage.com/:
Here is the downloader.php file located in the base directory:
header('Access-Control-Allow-Origin: *');
?>
<?php include_once 'index.html';?>
The html file the user clicks:
<!DOCTYPE html>
<html>
<body>
<button onclick="downloadPageAsText('https://www.mypage.com/downloader.php', 'index', '.html');">Download</button>
</body>
</html>
The functions to allow downloading:
function downloadPageAsText(url, basename, suffix){
let xhttp = new XMLHttpRequest();
xhttp.timeout = 1000;
xhttp.ontimeout = function(e) {
alert("Request timed out. Try again later");
};
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
saveStringToTextFile(xhttp.responseText, basename, suffix);
}
};
xhttp.open("GET", url, true);
xhttp.send();
}
function saveStringToTextFile(str1, basename = "myfile", fileType = ".txt") {
let filename = basename + fileType;
let blobVersionOfText = new Blob([str1], {
type: "text/plain"
});
let urlToBlob = window.URL.createObjectURL(blobVersionOfText);
let downloadLink = document.createElement("a");
downloadLink.style.display = "none";
downloadLink.download = filename;
downloadLink.href = urlToBlob;
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.parentElement.removeChild(downloadLink);
}
Is there an easier way to allow the user to download an html file to their mobile device?
After help in the comments I found that adding the Content-Disposition header in the PHP file prompts the mobile browsers to use their download mechanisms.
It looks like there are 2 relatively simple ways to cause the html file to download.
You can use the anchor tag's download attribute which is supposed to prompt the browser to download the file instead of displaying it. Here is an example of its use:
Click to download
However, this only works for same-origin URLs, so although it may fit your use-case but not mine as I need the file to download from cross-origin URLS.
A second simpler way of making the html file download rather than display (using PHP), is to use the Content-Dispostion header which tells the users browser it should be downloading the file.
Here is an example of a PHP file called download.php, which will cause desiredpage.html to download with a suggested name of suggestname.html, using just an anchor tag from the client side.
<?php
$contents=file_get_contents("desiredpage.html");
$filename="suggestedname.html";
header('Access-Control-Allow-Origin: *');
header("Content-disposition:attachment;filename=".$filename);
echo $contents;
?>
And here is the anchor tag on the client-side:
Click here to download
Reference:
Download Link not working in html
With useful comments from:
Anirban and Christopher.

Is there any way of listening to download complete event after download starts using anchor tag?

The anchor tag click automates the download. Are there events attached to anchor tags that I can listen on ?
downloadMyFile(){
const link = document.createElement('a');
link.setAttribute('href', 'abc.net/files/test.ino');
link.setAttribute('download', `products.csv`);
document.body.appendChild(link);
link.click();
link.remove();
}
You will not be able to be notified by the client when the download completes this way.
You have 2 possible solutions, which mostly depends on the size of the file.
Option 1: Use an ajax call instead, you can stream the whole file in memory and then make the browser download it to a file (which will be instant). This means you have full view on the different download events.
// Get the content somehow
// and then make the browser download
window.location.href = window.URL.createObjectURL(content);
Option 2: Monitor it with the server. I'd suggest you add a UID to the request like:
link.setAttribute('href', 'abc.net/files/test.ino?uid='+myUID);
Have the server keep track of that request and store details with the UID, you can then have another route report the status of the request when provided the UID. The problem with this is that you'd have to poll every now and then to know if the download is finished.
Since we do not know exactly the use for your request it is hard to tell if there are other possibilities. But IMHO there is no real use for you to know if the file has completed downloading. I cannot figure out why you'd want that in the beggining. I see it is a CSV file, they usually are not that big and the download should be real quick... Unless it is because it takes a lot of time to start since it has to be generated before? In this case I suggest you see a popular question/answer I made a while back. How to display a loading animation while file is generated for download?
Edit: Since the file may be big and not fit in memory, you could write the file on disk using the FileSystem API. This way you will be able to pipe the stream coming from your request directly to the filesystem. Since a stream has a close event, you'll be able to know when it is all done.

How can I download text from an HTML text area as a .txt file using JS?

I'm working on a project that randomly generates a set of characters intended to be used for a password and want the user to be able to download the results as a text file. I've read some things on BLOBs and a few other methods that haven't worked. It'd be a big help if someone could offer a method that works.
TL;DR: Need a way to download text from an HTML textarea as a .txt file using JS
You should be able to programmatically create a link that uses a data url with your text, add a download attribute so the browser downloads instead of navigating (and to specify a filename), and click it.
This doesn't run as a SO snippet, presumably for security reasons, but it should work out in the world (and you can copy/paste this in your dev tools console to test it):
const generatedText = 'some generated blob of text';
// create an anchor element that we can programmatically click
const a = document.createElement('a');
// set up a data uri with the text
a.href = `data:text/plain,${generatedText}`;
// set the download attribute so it downloads and uses this as a filename
a.download = 'the-name-of-the-file.txt';
// stick it in the document
document.body.appendChild(a);
// click it
a.click();

Browser is cancelling multiple file download requests

I am trying to download multiple files that the user has selected for download. However, the browser is cancelling all but last download request. If I am increasing the delay between the requests to say about 1 second, then those files get downloaded, but even in this case, sometimes some files are missed. The files are being downloaded from amazon s3 urls(i.e. these are CORS).
I am doing this by creating an anchor element with the url and then calling the click event on it using javascript.
downloadFile(url) {
let a = document.createElement('a');
a.id = url;
// a.setAttribute('target', 'blank');
a.download = '';
a.href = url;
// firefox doesn't support `a.click()`...
// console.log('dowmloading ' + url);
a.dispatchEvent(new MouseEvent('click'));
a.remove();
}
Then on download button click event I'm doing this:
let delay = 0;
urlList.forEach(url => {
return setTimeout(downloadFile.bind(null, url), 100 * ++delay);
});
Is there a way to accomplish this?
Why is the browser cancelling the requests?
Why is the browser cancelling the requests?
Because you are essentially "canceling navigation" by clicking the next link, before the browser is fully done with what it has to do when you clicked the previous one.
If this weren’t downloads, but regular links to pages, then this is the behavior you want - if the user clicks a link to page A first, but then looses patience and clicks on another link to page B, then the request for page A gets cancelled at that point - no need in loading two resources, when only one can be displayed at the same time anyway.
I don’t think there is much you can do about this, if you do not want to figure out a "magic number" of a timeout that somehow makes it "work" - especially since you won’t know if that works in general, or maybe just on your machine, with your internet connection, etc.
I am trying to download multiple files that the user has selected for download.
You could have those selected files wrapped into a container format - like for example ZIP (more than just a "container", strictly speaking) - dynamically on the server, so that the user just has to download one file only (which they will then have to unpack again on their own machine.)
Or you change your file selection process to begin with. Instead of having the user mark the files they want using checkboxes or something like that, present them direct links to the files instead maybe? Then they can click each one they want one after another, and the "normal" download functionality will take place.
#misorude is right about why they're getting canceled but a workaround is to use an iframe instead of an anchor tag to download a file.
a typescript implementation of a download file below:
export function downloadFile(downloadLink: string): void {
const iframe = document.createElement("iframe");
iframe.setAttribute("sandbox", "allow-downloads allow-scripts");
iframe.src = downloadLink;
iframe.setAttribute("style", "display: none");
document.body.appendChild(iframe);
}
So it's similar to your implementation - only thing is, you'll have stray (non-displayed) iframes on you DOM. There's no way to tell when the iframe is done downloading the file so there isn't an adequate way to know when to remove them. But at least you wouldn't get your network requests canceled.

How to Download multiple files from javascript

I am trying to use window.location.href in a loop to download multiple files
I have a table in which i can select file's, then i run a loop of selected and
try navigate to the file path to download the files.
I keep only getting the last file to download.
I think it's due to the location herf only taking action after my javascript finishes and not as the code runs.
When i have a break point on the window.location.herf it still only downloads the last file and only when i let the code run through.
Is there a better way to initiate multiple downloads from a javascript loop.
$("#btnDownload").click(function () {
var table = $('#DocuTable').DataTable();
var rows_selected = table.rows('.selected').data();
$.each(rows_selected, function (i, v) {
window.location.href = v.FilePath;
});
});
In some browsers (at least Google Chrome) support the follow:
$("<a download/>").attr("href", "https://code.jquery.com/jquery-3.1.0.min.js").get(0).click();
$("<a download/>").attr("href", "https://code.jquery.com/jquery-3.1.0.min.js").get(0).click();
$("<a download/>").attr("href", "https://code.jquery.com/jquery-3.1.0.min.js").get(0).click();
JSFiddle: https://jsfiddle.net/padk08zc/
I would make use of iframes and a script to force the download of the files as Joe Enos and cmizzi have suggested.
The answer here will help with JavaScript for opening multiple iframes for each file:
Download multiple files with a single action
The answers for popular languages will help with forcing downloads if the URL is actually something that can be served correctly over the web:
PHP: How to force file download with PHP
.Net: Force download of a file on web server - ASP .NET C#
NodeJS: Download a file from NodeJS Server using Express
Ruby: Force browser to download file instead of opening it
Ensure you change the links to point to your download script and also make sure you add the appropriate security checks. You wouldn't want to allow anyone to abuse your script.
Though this looks like an old post and I stumbled on this while trying to solve a similar issue. So, just giving a solution which might help. I was able to download the files but not in the same tab. You can just replace the event handler with download which is provided below. The urls is an array of presigned S3 URLs.
The entire code looks like below:
download(urls: any) {
var self = this;
var url = urls.pop();
setTimeout(function(){
var a = document.createElement('a');
a.setAttribute('href', url);
document.body.appendChild(a);
a.setAttribute('download', '');
a.setAttribute('target', '_blank');
a.click();
// a.remove();
}, 1000)
}

Categories