0

Given html

<a download="file.txt" href="data:application/octet-stream,abc">download</a>

can bytes be appended to data URI at href of <a> element after click on <a> element, before resulting file is downloaded to user filesystem?

guest271314
  • 1
  • 15
  • 104
  • 177
  • If you're generating this data dynamically anyway, why not make a Blob out of it and link to that? Far more efficient. – Brad Sep 25 '16 at 04:08
  • @Brad Yes. Will try `Blob` and `ArrayBuffer` as well. The attempted stems from [this discussion](http://chat.stackoverflow.com/rooms/124131/discussion-between-hephaestious-and-guest271314) about possible approaches for downloading a stream. – guest271314 Sep 25 '16 at 04:11
  • @Brad That is dynamically adding bytes to a downloading file after user action starts download procedure, see [JavaScript: Writing to download stream](http://stackoverflow.com/questions/39682465/javascript-writing-to-download-stream) – guest271314 Sep 25 '16 at 04:18
  • Oh, well you won't be able to modify it after the download has started, unfortunately. You can only set that URL when the link is clicked, and that's what's used. And, Blobs are immutable. You will have to save chunks in memory and make a Blob when they're all done. – Brad Sep 25 '16 at 04:21
  • @Brad Yes. Though have not tried yet. It is possible to use `.slice()` to add to existing `Blob`, though not sure it that will affect `Blob URL`. An `ArrayBuffer` could possibly be used. – guest271314 Sep 25 '16 at 04:24
  • `.slice()` doesn't add to an existing Blob. It only allows you to reference memory in a new Blob... effectively making a second Blob out of a subset of data from the first. Again, Blobs are immutable, full stop. And, you can't link to an ArrayBuffer... you also can't modify an ArrayBuffer or add to its length. – Brad Sep 25 '16 at 04:26
  • Yes, aware using `.slice()` existing `Blob` is not added to, but new `Blob` is created. Used incorrect term to describe actual procedure at previous comment. – guest271314 Sep 25 '16 at 04:30
  • @Brad Would need to create multiple `ArrayBuffer`s, similar to `Blob`, use `.map()` or `.reduce()` to fill new array buffer continues to increase byte length, with previous buffers – guest271314 Sep 25 '16 at 04:44
  • You're still creating a new entity though. You can't extend something that the browser is in the process of "download"/saving. There is no API for that anymore. (There was a really nice file API, and then Google and others got rid of it because nobody used it. It's unfortunate.) – Brad Sep 25 '16 at 04:47
  • http://www.html5rocks.com/en/tutorials/file/filesystem/ https://www.w3.org/TR/streams-api/ – Brad Sep 25 '16 at 04:49
  • @Brad `data URI` only viable current option? What was name of API you mention? – guest271314 Sep 25 '16 at 04:49
  • Data URI isn't an option for you. You have no option. That is, if I'm understanding you correctly. You do want to dynamically add to something while it's being "downloaded"/saved, correct? If so, that's not possible today client-side. If you just need the user to be able to save a snapshot of data at a point in time, keep buffers around and make a Blob only when needed. Don't use Data URI. – Brad Sep 25 '16 at 04:51
  • _"Data URI isn't an option for you. You have no option. That is, if I'm understanding you correctly. You do want to dynamically add to something while it's being "downloaded"/saved, correct? If so, that's not possible today client-side."_ What is occurring here https://jsfiddle.net/43dt6fp3/ ? – guest271314 Sep 25 '16 at 04:53
  • 1
    In that case, all of the data is generated **before** the download action is initiated. It isn't being added on to later. – Brad Sep 25 '16 at 04:53
  • @Brad Should probably browse the file download algorithm, here. Are you suggesting that it is currently not possible to download a stream of data at browser? – guest271314 Sep 25 '16 at 04:56
  • You can download a stream of data from a server to a browser, sure. You can assemble it client-side in-memory all you'd like. What you can't do is write that stream to disk as it's being assembled in your code. It can only be written to disk (via user clicking on link to blob) once fully assembled. – Brad Sep 25 '16 at 04:58
  • _"There was a really nice file API, and then Google and others got rid of it because nobody used it. It's unfortunate"_ What is name of that API? – guest271314 Sep 25 '16 at 04:58
  • _"What you can't do is write that stream to disk as it's being assembled in your code. It can only be written to disk (via user clicking on link to blob) once fully assembled."_ Using `nodejs`s `.pipe()` it would be possible, yes? – guest271314 Sep 25 '16 at 04:59
  • Oh sure absolutely, but Node.js isn't running in your browser. If you're suggesting sending the data to the server just to be turned around as a downloading file... that's a real nasty hack and a waste of bandwidth but will work. – Brad Sep 25 '16 at 05:02
  • @Brad No. Finding a workaround in browser. Perhaps using `browserify`? Though `File API` is probably best suited for this, where files can be read using both `requestFileSystem` and native shell commands http://stackoverflow.com/questions/36098129/how-to-write-in-file-user-directory-using-javascript/ – guest271314 Sep 25 '16 at 05:02
  • @Brad https://github.com/jimmywarting/StreamSaver.js looks promising – guest271314 Sep 25 '16 at 05:16
  • Browserify doesn't solve the problem... you're just building JS modules with some polyfills. Your first link is using the Filesystem APIs I linked to earlier. Your second link... now that's a hack. :-) Very creative. In any case, it requires streams support and that's only in Chrome right now, so you might as well use the File APIs since they're also in Chrome. – Brad Sep 25 '16 at 05:59

1 Answers1

2

Certainly! Add an onclick handler that modifies the href attribute, and it will be modified before the download starts.

var a = document.querySelector("a");
a.onclick = function() {
  let data = "";
  for (let i = 0;i < 200000;i++){
    data += i;
  }
  a.href+=data;
}
<a download="file.txt" href="data:application/octet-stream,abc">download</a>

Also, it's important to note that you should not modify the href for every byte, as the DOM reloads each time that it is edited. Instead, modify an intermediate variable and then add it to the href as one operation.

Micah
  • 56
  • 6
  • Virtually an unlimited amount. You used a recursive function, which is why it hangs and errors out. If you were to load bytes from somewhere (like a file on your server), or write the bytes non-recursively (ex: a for loop), it wouldn't have this problem. – Micah Sep 25 '16 at 03:33
  • _"Virtually an unlimited amount."_ Can you demonstrate this? _"@guest271314 used an unnecessary recursive function, which is why it hangs and errors out."_ The approach at http://stackoverflow.com/a/39683055/ was intended to append bytes without loading the bytes from another file, but rather, generate and append the bytes, one at a time, dynamically, after the click event occurs. – guest271314 Sep 25 '16 at 03:36
  • That is less than the 20000 iterations which append a number at each iteration at Answer below and https://jsfiddle.net/pj0hrbd2/. If _"Virtually an unlimited amount."_ is accurate, what occurs when 100000 bytes are appended to `data URI`? – guest271314 Sep 25 '16 at 03:42
  • That works too, naturally. https://jsfiddle.net/az1dn0sw/ The problem with your solution is that you recursively called the function, causing the Javascript call stack to get too large. – Micah Sep 25 '16 at 03:44
  • That is the same amount as appears at [this Answer](http://stackoverflow.com/a/39683055/2801559). Yes,when `20000` is used expected result is returned; as demonstrated at that Answer. You stated _"Virtually an unlimited amount."_ Whether using loop or recursion does not matter, here. What occurs when condition is `i < 100000`? – guest271314 Sep 25 '16 at 03:48
  • Moving `a.href+=data;` to after `for` loop returned expected result; that is, total bytes of file `200003`. – guest271314 Sep 25 '16 at 04:20