9

Im working on a web app, and one feature it needs is to be able to donwload a fairly large file interactively - this file does not exist on the server - and consists entirely of data dynamically loaded from a database.

Currently im using the following code (wont run for you but you can get the idea) in which i add a text box with a filename, then a hidden text area contaning all the text needed for the json style download, and then that is linked to a function which attempts a URI download.

Interesetingly, when run in chrome, i get a page saying the URI is too long and its not going to work etc, but the file still gets downloaded.

"Submitted URI too large! The length of the requested URL exceeds the capacity limit for this server. The request cannot be processed. If you think this is a server error, please contact the webmaster."

Anyway, the annoying thing is this: The page that allows these downloads uses a post/get from a previous page - so the back button is not usable, as it gives us the:

"Confirm Form Resubmission This webpage requires data that you entered earlier in order to be properly displayed. You can send this data again, but by doing so you will repeat any action this page previously performed."

page - What I would love to do is have these URI downloads spawn into a new tab so the back button is not necessary, though adding the target blank did not help

Also intereseting - as seen above i do have a function for "download all" as well - which works for me running things locally on a xampp server, on google chrome - however those im building the app for report the button not working for them (they are on macs using safari, havent had a chance to see this for myself and gather info yet - so though im not EXPECTING an answer on this with my limited info, im hoping someone may have an idea!)

CODE:

< script >
  function download(filename, text) {
    var element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    // I tried addin this but no new tab appeared!
    //element.target = "_blank:";
    element.setAttribute('download', filename);

    element.style.display = 'none';
    document.body.appendChild(element);

    element.click();

    document.body.removeChild(element);
  }

function download_all() {
  var nameElements = document.getElementsByName("name");
  var valueElements = document.getElementsByName("text");

  for (i = 0; i < nameElements.length; i++) {
    console.log(nameElements[i].value);
    console.log(valueElements[i].value);

    download(nameElements[i].value, valueElements[i].value);
  }
} <
/script>

echo "
<form onsubmit=\ "download(this['name'].value, this['text'].value)\" class=\ "form-group\">"; echo "<label for=\ "name\">Download Title</label>"; echo "<input type=\ "text\" name=\ "name\" size=\ "40\" value=\ "" . $m[ 'name'] . ".json" . "\" class=\ "form-inline\">"; //hidden=\"hidden\"> after text echo "<textarea name=\ "text\" hidden=\
    "hidden\">" . $json_meal_data . "</textarea>"; echo "<input type=\ "submit\" value=\ "Download\" class=\ "btn-primary\">"; echo "</form>"; echo "<br>"; echo "<br>";

Also definitely worth noting, that I have included the Download All function in the above snippets. Strangely, running that download all in Chrome downloads all the files, yet running it in Safari only downloads 1 file.

aescript
  • 1,775
  • 4
  • 20
  • 30
  • 2
    Have you tried putting the data into a blob and downloading that? Creating the url from the blob with [`URL.createObjectURL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL). As for the download all, some browsers like Chrome block multiple consecutive downloads, the user needs to allow that in order for it to work (in chrome you access it by clicking the icon at the end of the address bar) – Patrick Evans Jan 11 '18 at 05:08
  • You can offer a `.zip` file for download, which could include multiple files and folders, see [Multiple download links to one zip file before download javascript](https://stackoverflow.com/q/37176397/) – guest271314 Jan 11 '18 at 05:32
  • @PatrickEvans Ill have to check regarding the browser and donwload all - as mentioned i havent seen it fail in person yet, as it works for me in chrome in my dev env. I have not tried putting it to a blob and using that method - ive never heard of that before. I can give it a try - but keep in mind these files are about 25mb each - not sure if that will work or not? – aescript Jan 12 '18 at 05:32
  • @guest271314 Looking at that, based on a few glances it looks like the files need to exist? There are no source files to be downloaded,the file is generated when the page is loaded and is just a bunch of text held in a textarea – aescript Jan 12 '18 at 05:32
  • Not certain what the issue is? What are you trying to achieve? You can read the `.textContent` of ` – guest271314 Jan 12 '18 at 05:36
  • @guest271314 Getting a single file to download isnt the problem - like I mentioned above, that is working (more or less,though i will investigate this method you posted and see if it makes it any better) The problem is that the download changes the current page - and you cannot go back since the page wont reload the search paramters posted. I want the donwload to open in a newtab-but adding a target _blank doesnt make that happen - the other issue is donwloading multiple files at once (which works for me in chrome,but not the user on safari) which i havent seen for myself yet so it may be easy – aescript Jan 12 '18 at 06:34
  • The files do not need to exist on a filesystem, you can generate them as i mentioned by putting them in a Blob, ie `new Blob([YourContentVariable],{type:'MimeTypeGoesHere like text/plain'});` then use createObjectURL on it to get a url that you use for `href` – Patrick Evans Jan 12 '18 at 11:20
  • @PatrickEvans yeah my other responses were at guest :) As mentioned ill give this a test this weekend! My current method of downloading a file (not using blob, but a URI) is working though it seems to refuse to start the download in a new tab as I want. Hopefully your method here will make that work! – aescript Jan 12 '18 at 23:10
  • @PatrickEvans Tried this, still having the same issue. Using this, in some cases it just doesnt download a file at all. In others I can get it download but i have the same scenario as before - it changes my current page to URI Too long warning, which screws the search results i was on. using window.open DOES open a new tab, but a file doesnt download (it just shows the json text on the webpage) and STILL changes my original page/tabs location =( – aescript Jan 14 '18 at 00:52
  • [You arent preventing your form from submitting](https://stackoverflow.com/questions/3350247/how-to-prevent-form-from-being-submitted) hence your page changing. As for the non-download, then you probably didnt implement it correctly or your browser doesnt support the download attribute, see this example for implementation: https://jsfiddle.net/23ztyo6c/ – Patrick Evans Jan 14 '18 at 07:55
  • Ah i thought your first link there was absolutely going to be the solution - but even preventing the submission, it still changes my page to a Request-URI too long error 414 page. And yea RE the fiddle, when i set it ujp that way is when it will Download - but changes the page on me to the uri issue. Its quite frustrating hah – aescript Jan 15 '18 at 18:03

3 Answers3

5

You passed too long URL in XAMPP. XAMPP stands for Apache. In Apache, maximum URL length is about 4,000 characters, after which Apache produces a "413 Entity Too Large" error.

I agree with @PatrickEvans it's better to use URL.createObjectURL. URL.createObjectURL() can be used to construct and parse URLs. URL.createObjectURL() specifically, can be used to create a reference to a File or a Blob. As opposed to a base64-encoded data URL, it doesn’t contain the actual data of the object – instead it holds a reference.

The nice thing about this is that it’s really fast. Previously, we’ve had to instantiate a FileReader instance and read the whole file as a base64 data URL, which takes time and a lot of memory. With createObjectURL(), the result is available straight away, allowing us to do things like reading image data to a canvas.

As you can see in following demo. Two link are same.But if you inspect on Without createObjectURL link href attribute has too large to edit but in With createObjectURL link you can edit it because for creating it I used URL.createObjectURL().

Online demo (jsFiddle)

Ali Soltani
  • 9,589
  • 5
  • 30
  • 55
  • I must have tried every other combination of suggestions ive gotten - from prevent defaults on links, to making a blob, to my json etc - but this is the first time i was handed a fiddle example that worked to build off of. In my combination attempts i tried: var blob = new Blob([text]);saveAs(blob, filename); with filesaver - but i dont think i created my blob as well as i should - and i would still change pages. With your fiddle I got it going with a tweak here and there to fit my setup. so THANK YOU - finally it works :) – aescript Jan 20 '18 at 08:10
  • @aescript I'm glad to be able to help you :) – Ali Soltani Jan 20 '18 at 08:25
  • Enjoy your bounty! – aescript Jan 20 '18 at 08:29
4

Please avoid using base64, because it's larger by 37% than Blob.

function download(filename, text) {
  var element = document.createElement('a');
  var blob = new Blob([text], {type: "octet/stream"})
  var url = URL.createObjectURL(blob);
  element.setAttribute('href', url);
  element.setAttribute('download', filename);
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}
Alex Nikulin
  • 8,194
  • 4
  • 35
  • 37
  • 2
    Won't this trigger browser protections a download that is not user initiated? I think that tends to ring warning bells in browsers. you should update to cover that this might be protected against and perhaps give an example of using onMouseDown to set this up on the element so when mouse up and click are triggered by the browser it then begins the download. – Barkermn01 Jan 18 '18 at 13:17
1

Likely what's happening is you're actually populating a browser URL and submitting a GET request to the sever.

GET requests limit the amount of data they can transmit to the server, hence the URI is too long. (POST, by contrast, allow for much larger payloads and are limited only by your server settings)

You can find more information on browser anchor length limitations here:

What is the maximum length of a URL in different browsers?

https://weblogs.asp.net/mschwarz/post-vs-get

Adam Hess
  • 1,396
  • 1
  • 13
  • 28
  • Why would someone use GET with a large payload in the first place? Wouldn't it make more sense to use a POST if they are submitting something with a payload anyway? – cr1pto Jan 19 '18 at 01:59
  • Because some developers are inexperienced at designing their APIs? Some GET request could include filter terms. Think google.com they have "q" query string parameter for your get request when you run your search. You could put a huge search term there or large number of requirements. – Adam Hess Jan 26 '18 at 15:48