66

I have an HTML5/javscript app which uses

<input type="file" accept="image/*;capture=camera" onchange="gotPhoto(this)">

to capture a camera image. Because my app wants to be runnable offline, how do I save the File (https://developer.mozilla.org/en-US/docs/Web/API/File) object in local storage, such that it can be retrieved later for an ajax upload?

I'm grabbing the file object from the using ...

function gotPhoto(element) { 
     var file = element.files[0];
     //I want to save 'file' to local storage here :-(
}

I can Stringify the object and save it, but when I restore it, it is no longer recognised as a File object, and thus can't be used to grab the file content.

I have a feeling it can't be done, but am open to suggestions.

FWIW My workaround is to read the file contents at store time and save the full contents to local storage. This works, but quickly consumes local storage since each file is a 1MB plus photograph.

Philipp Kyeck
  • 18,402
  • 15
  • 86
  • 123
pinoyyid
  • 21,499
  • 14
  • 64
  • 115
  • 9
    Since local storage only supports strings, you'll have to convert the file to base 64 (or some form of a string) – Ian Oct 01 '13 at 14:37
  • For example: https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL%28%29 – Ian Oct 01 '13 at 14:39
  • I guess that's what Lawnchair is trying to do for me. The problem is that the file object contains a bunch of prototype methods which can't be serialised. I'm guessing I need to pick the file object apart and extract it's properties, ... but that's a guess and I don't know how consistent the file object is across browsers. – pinoyyid Oct 01 '13 at 14:41
  • just saw the link. I'm not trying to save the file media content, since that's already saved. I'm just trying to save the file object that refers to the saved media content. ie. I want to save this https://developer.mozilla.org/en-US/docs/Web/API/File – pinoyyid Oct 01 '13 at 14:43
  • Your only option is to store the full image data in localStorage. I also wonder why you would allow your application to store references to images. If they are meant to be uploaded just upload when you're online. What can you do offline anyway? – Bart Oct 05 '13 at 14:59
  • @Bart, I think you're right re storing the full image. The reason I'm trying to save the File object is so that it is persisted across browser/page reloads. Your suggestion to "just upload them when online" only works as long as the page remains loaded in the browser. – pinoyyid Oct 05 '13 at 15:23
  • 1
    My suggestion is to add the dataURL to your file object and stringify it after. So you have have the full file object and you can load the image with the dataURL stored. http://jsfiddle.net/bR8Nt/1 – L105 Oct 05 '13 at 18:01
  • @L105 That's pretty much what I'm doing as a workaround. Like I said in the question, I'm looking for a mechanism to save a handle to the file in the gallery (ie. the original File object) so I don't end up storing the media content twice and filling up local storage. – pinoyyid Oct 05 '13 at 18:12
  • 4
    @pinoyyid Since there is literally no way to instantiate a `File` object in JavaScript you are pretty much out of luck. The closest alternative would be, when restoring the string encoded file from local storage, to create a `Blob`, which `File` inherits from. You can use a `Blob` for the majority of things you would otherwise use a `File` for. If you think an example of this would be useful I will write it up. It doesn't really solve your problem though, just gets you a little closer to the kind of object you seem to actually want. – Andrew Hubbs Oct 07 '13 at 23:34
  • @AndrewHubbs thx for confirming the issue. My only reason for wanting to restore the File object is insofar as it acts as a reference to a photo in the gallery, thus avoiding the need to make a second copy in localstorage. – pinoyyid Oct 08 '13 at 03:38
  • use URL.createObjectURL to turn a blob into a url to feed to img.src or ajax for example. use new Blob([str]) to turn a string into a blob. – dandavis Dec 16 '13 at 21:59
  • 1
    @dandavis, thanks, but as with all of the answers, this requires me saving a copy of the image contents. I was trying to simply store the File object as a pointer to the .jpg file in my phone gallery. – pinoyyid Dec 19 '13 at 09:11
  • I think I found a possible answer, but it is specific to Vue/Vuex and not local storage directly. That shouldn't matter, as Vuex just wraps local storage, but it's not good enough as a complete answer. Anyway: Look at https://golb.hplar.ch/2017/02/Uploading-pictures-from-Ionic-2-to-Spring-Boot.html, specifically https://github.com/ralscha/blog/blob/master/uploadsb/client/src/app/home/home.page.ts#L78-L87 . In my app, I'm saving the formData variable to Vuex (Vue thing wrapping localstorage), and I am able to retrieve it later and upload it successfully to a server. – Yngvar Kristiansen Mar 13 '19 at 13:16

7 Answers7

26

You cannot serialize file API object.

Not that it helps with the specific problem, but ... Although I haven't used this, if you look at the article it seems that there are ways (although not supported yet by most browsers) to store the offline image data to some files so as to restore them afterward when the user is online (and not to use localStorage)

deerawan
  • 8,002
  • 5
  • 42
  • 51
George Mavritsakis
  • 6,829
  • 2
  • 35
  • 42
  • Thanks for the link. Once the Filesystem API is better supported it might provide a better alternative than indexeddb. Either way I end up with a second copy of the image (the original in the gallery and a copy I've saved somewhere), which is what I'm trying to avoid. But sadly I think "You cannot serialize file API object" is the right answer. – pinoyyid Oct 05 '13 at 15:27
  • Although I suppose you save it to javascript blob, I have a feeling that using hidden canvas objects could help too, where you could draw the image data and when you are online get the image data from canvas.data. I suppose this canvas could be in an iframe? Which could persist for as long as this is needed (so as to not have problems with localstorage). But again, as long as you have it in javascript, why would you want to have it into a canvas object too? – George Mavritsakis Oct 05 '13 at 15:32
  • 2
    The problem is page/browser reloads. If the user unloads the page I lose everything that has been stored in memory. – pinoyyid Oct 05 '13 at 15:39
  • Yes, I understand, that is why you are trying to store it persistently elsewhere. One more thing that I can mention is that in order to have more space you could consume all available space of the browser in sequence. What I mean is that you could for example use localstorage, flash storage, google gears, ie userdata etc in sequence (like Persistjs does but in parallel) but that would require much of coding. Anyway, hope you find an answer. – George Mavritsakis Oct 05 '13 at 15:45
  • I've accepted this answer, as it seems that saving/restoring a File object is not possible. Sad but true. – pinoyyid Oct 08 '13 at 17:21
  • 2
    it's not possible because of the obvious security risks. if you were allowed to persist a File object as a string in local storage you would also be able to modify that string to get access to other files in the same directory without the user knowing about it. – Martijn Scheffer Feb 24 '17 at 09:13
24

Convert it to base64 and then save it.

function gotPhoto(element) {
   var file = element.files[0];
   var reader = new FileReader()
   reader.onload = function(base64) {
      localStorage["file"] = base64;
   }
   reader.readAsDataURL(file);
}
// Saved to localstorage

function getPhoto() {
   var base64 = localStorage["file"];
   var base64Parts = base64.split(",");
   var fileFormat = base64Parts[0].split(";")[1];
   var fileContent = base64Parts[1];
   var file = new File([fileContent], "file name here", {type: fileFormat});
   return file;
}
// Retreived file object
danesh
  • 39
  • 6
  • 3
    thx, but please read the question again. I don't want to save the file contents, I want to save the File object. – pinoyyid Jan 06 '17 at 09:05
  • The file object is a reference to a real file in the device. You can save in the same way you would do for every object, using JSON.stringify and JSON.parse. In this way you would reference a real browser file but it won't work after a page refresh. What you can do is encoding and decoding as @danesh suggested – Damiano Mazzara Jan 05 '23 at 14:28
14

Here is a workaround that I got working with the code below. I'm aware with your edit you talked about localStorage but I wanted to share how I actually implemented that workaround. I like to put the functions on body so that even if the class is added afterwards via AJAX the "change" command will still trigger the event.

See my example here: http://jsfiddle.net/x11joex11/9g8NN/

If you run the JSFiddle example twice you will see it remembers the image.

My approach does use jQuery. This approach also demonstrates the image is actually there to prove it worked.

HTML:

<input class="classhere" type="file" name="logo" id="logo" />
<div class="imagearea"></div>

JS:

$(document).ready(function(){
  //You might want to do if check to see if localstorage set for theImage here
  var img = new Image();                
  img.src = localStorage.theImage;

  $('.imagearea').html(img);

  $("body").on("change",".classhere",function(){
      //Equivalent of getElementById
      var fileInput = $(this)[0];//returns a HTML DOM object by putting the [0] since it's really an associative array.
      var file = fileInput.files[0]; //there is only '1' file since they are not multiple type.

      var reader = new FileReader();
      reader.onload = function(e) {
           // Create a new image.
           var img = new Image();

           img.src = reader.result;
           localStorage.theImage = reader.result; //stores the image to localStorage
           $(".imagearea").html(img);
       }

       reader.readAsDataURL(file);//attempts to read the file in question.
    });
});

This approach uses the HTML5 File System API's to read the image and put it into a new javascript img object. The key here is readAsDataURL. If you use chrome inspector you will notice the images are stored in base64 encoding.

The reader is Asynchronous, this is why it uses the callback function onload. So make sure any important code that requires the image is inside the onLoad or else you may get unexpected results.

pinoyyid
  • 21,499
  • 14
  • 64
  • 115
Joseph Astrahan
  • 8,659
  • 12
  • 83
  • 154
  • 2
    Thanks for posting the workaround which I'm sure will help people. I hope it's OK that I edited your answer to emphasise that it *is* a workaround, and not the answer to the question. I was looking for a way to save the File object *without* making a second copy of the image data. – pinoyyid Dec 12 '13 at 05:59
  • Yeah I was looking for what you were looking for also, I wish it could be done, I put a bounty to see if someone could figure it out. – Joseph Astrahan Dec 13 '13 at 01:32
  • 2
    @pinoyyid How is this a workaround? What's wrong with it? – Michał Perłakowski Apr 11 '16 at 21:08
  • 2
    I was specifically looking to persist the File *object*, not the file *content*. My app is targeting mobile, so the image has already been saved in the gallery. I wanted to store a reference to the gallery image, rather than make a second copy using localstorage with all of the size limitations that come with it. – pinoyyid Apr 12 '16 at 12:25
  • @pinoyyid So maybe you just want the name of the file, instead of the whole File object? – Michał Perłakowski Apr 14 '16 at 06:31
  • 1
    No. I want the whole File object – pinoyyid Apr 14 '16 at 06:48
  • @pinoyyid if I was using the code above for a multi-page form (to locally store an image while the user then goes on to fill out the remainder of the form page(s)) is it possible to call or get the uploaded `img` at the end of the form and upload to `AJAX`? – thesayhey Mar 15 '17 at 15:44
  • Yeah you should be able to, the image is just stored in a text like format that should be able to push up. I can create a sample later if you need help of that, maybe for another question is you create it on here. – Joseph Astrahan Mar 23 '17 at 20:08
1

You could use this lib:

https://github.com/carlo/jquery-base64

then do something similar to this:

//Set file
var baseFile = $.base64.encode(fileObject);
window.localStorage.setItem("file",basefile);

//get file
var outFile = window.localStorage.getItem("file");

an other solution would be using json (I prefer this method) using: http://code.google.com/p/jquery-json/

//Set file
window.localStorage.setItem("file",$.toJSON(fileobject));

//get file
var outFile = $.evalJSON(window.localStorage.getItem("file"));
Philip G
  • 4,098
  • 2
  • 22
  • 41
  • 7
    Thanks, but you've missed the point of the question. I'm not asking how do I save an arbitrary, generic object. I'm asking how do I save a File object such that upon restore it is usable as a File object. – pinoyyid Oct 05 '13 at 15:20
1

I don't think that there is a direct way to Stringify and then deserialize the string object into the object of your interest. But as a work around you can store the image paths in your local storage and load the images by retrieving the URL for the images. Advantages would be, you will never run out of storage space and you can store 1000 times more files there.. Saving an image or any other file as a string in local storage is never a wise decision..

  • "you can store the image paths in your local storage and load the images by retrieving the URL for the images" . No you can't – pinoyyid Dec 19 '13 at 02:47
  • You can.. You can store the path of the image and then load it from the local storage? What made you think that you cant? Keep a key as an alias and store the url of the image file as the value.. I have done that in my project.. – Jashobanta Chakraborty Dec 22 '13 at 12:59
  • 10
    The Javascript security model doesn't allow reading files from the local drive. – pinoyyid Dec 22 '13 at 15:27
1

create an object on the global scope

exp: var attmap = new Object();

after you are done with file selection, put your files in attmap variable as below,

attmap[file.name] = attachmentBody;
JSON.stringify(attmap)

Then you can send it to controller via input hidden or etc. and use it after deserializing.

(Map<String, String>)JSON.deserialize(attachments, Map<String,String>.class);

You can create your files with those values in a for loop or etc.

EncodingUtil.base64Decode(CurrentMapValue);

FYI:This solution will also cover multiple file selection

umithuckan
  • 111
  • 2
0

You could do something like this:

// fileObj = new File(); from file input
const buffer = Buffer.from(await new Response(fileObj).arrayBuffer());
const dataUrl = `data:${fileObj.type};base64,${buffer.toString("base64")}`;

localStorage.setItem('dataUrl', dataUrl);

then you can do:

document.getElementById('image').src = localStorage.getItem('dataUrl');
Dryden Williams
  • 1,175
  • 14
  • 22