I've got a FileReader that lets the user upload a file (image) to my site.
Here's the code that does the reading:
$("input[type='file']").change(function(e) {
var buttonClicked = $(this);
for (var i = 0; i < e.originalEvent.srcElement.files.length; i++) {
var file = e.originalEvent.srcElement.files[i];
var img = document.createElement("img");
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
console.log(reader.result);
}
reader.readAsDataURL(file);
}
});
All is good and well, until I tried to print out my result. I used this file for example:
When I console.log() the result, it spits out over 95000 characters.
This image in particular is around the same size as the images I will be accepting into my site.
I was hoping to store these images in a database as well, and so I'm wondering how this is going to be possible with image sources that are so extremely long. Is there a way to shorten this or get the image path a different way?
I'm moreso curious as to why they're so long, but if someone has a tip to store these (100s per user, 500+ users) that'd be nice as well!
Thanks-
Store the Files as ... Files.
There are very little use cases where you need the toDataURL() method of the FileReader, so every time you use it, you should ask yourself why you need it.
In your case :
To display the image in the page. Well don't use a FileReader for this, instead create a direct pointer to the file in the form of an url, available only to this session. This can be achieved with the URL.createObjectURL(File_orBlob) method.
To store this image on your server. Don't store a ~37% bigger base64 version, send and store directly the file as a file (multipart). This can be achieved easily with the FormData API.
inp.onchange = function(){
var file = this.files[0];
if(file.type.indexOf('image/') !== 0){
console.warn('not an image');
}
var img = new Image();
img.src = URL.createObjectURL(file);
// this is not needed in this case but still a good habit
img.onload = function(){
URL.revokeObjectURL(this.src);
};
document.body.appendChild(img);
}
// not active but to give you da codez
function sendToServer(){
var file = inp.files[0];
var form = new FormData();
// here 'image' is the parameter name where you'll retrieve the file from in the request
form.append('image', file);
form.append('otherInfo', 'some other infos');
var xhr = new XMLHttpRequest();
xhr.open('post', 'yourServer/uploadPage')
xhr.onload = function(){
console.log('saved');
};
xhr.send(form);
}
<input type="file" id="inp">
And if you need PHP code to retrieve the File form this request :
if ( isset( $_FILES["image"] ) ){
$dir = 'some/dir/';
$blob = file_get_contents($_FILES["image"]['tmp_name']);
file_put_contents($dir.$_FILES["image"]["name"], $blob);
}
You're going to want to upload the files to a server of some sort (a backend that is serving up your javascript), and then from there you'll want to
Validate the file
Store the file on a physical server (or the cloud) somewhere
Add an entry in a database that relates the file path or ID of that upload to the user who just uploaded it (so you can retrieve it later if needed)
So basically, you don't store the image in your database, you store it on a file share/cloud host somewhere, and instead you only store what is needed to download/retrieve the image later.
Related
This question already has answers here:
Uploading multiple files using formData()
(18 answers)
Closed 5 years ago.
What I want
People click add image button, they select an image, image is added to gallery.
They can delete images by clicking cross sign and re click add image button to add more images.
This all works, I've a reference to all File elements.
However, I can't figure out how to send the files in post request of form.
Problem the problem is that You can't create FileList out array of file, or set array of files as input.files = arrOfFiles.
Input element itself doesn't let you add more files, or remove files... it simply replaces old file with new file(s).
which is not what i want therefore i'm keep reference to file objects in js, and letting user remove images or add more.
I know i can send individual file as XHR, but I want to send them through form that already exists.
I wanted to know a way to send files through form not js, but apparently that's not possible
That's exactly what the FormData API is for: create from scratch a form's data that you will be able to upload to your server as if it were created from a <form> object, except that you can control what goes there or not.
So to append a File or a Blob in a FormData, the code is
var fd = new FormData();
fd.append(field_name, blob, file_name);
To append multiple files, you can call again fd.append, but note that backend often need to have the field_name formatted in such a way they can know multiple values are expected here.
Usually this is done by adding [] after your fieldname.
var fd = new FormData();
fd.append('files[]', blob_1, file_name_1);
fd.append('files[]', blob_2, file_name_2);
And then you can send it through an AJAX request to your server, which won't make the difference between this request and a real one made froma single <input multiple name="files[]">.
Note that in case of File, file_name is optional and will default to the File's name if not set. However, it is needed for Blobs if you don't want a random name to be set.
var file_1 = new File(['foo'], 'file1.txt',{type:'text/plain'});
var file_2 = new File(['bar'], 'file2.txt', {type:'text/plain'});
var fd = new FormData();
fd.append('files[]', file_1);
fd.append('files[]', file_2);
console.log(...fd.entries());
// and to send it to your server
var xhr = new XMLHttpRequest();
xhr.open('POST', 'your_server_url');
xhr.send(fd);
If you are just going to upload images. Consider using base64 encoding. That is what I did. Assign base64 encoded URL to stringify JSON, or whatever you wish that is easier for you.
When the data is POST-ed, do the conversion there, to get back your files for saving them. Decoding base64 will be dependent on your backend language though of which you did not mention in your question.
In JavaScript, you can use this function to load up the image in preview state like you mentioned.
function previewImage (inputId, eleId) {
if (window.FileReader) {
var oPreviewImg = null, oFReader = new window.FileReader(),
rFilter = /^(?:image\/bmp|image\/cis\-cod|image\/gif|image\/ief|image\/jpeg|image\/jpeg|image\/jpeg|image\/pipeg|image\/png|image\/svg\+xml|image\/tiff|image\/x\-cmu\-raster|image\/x\-cmx|image\/x\-icon|image\/x\-portable\-anymap|image\/x\-portable\-bitmap|image\/x\-portable\-graymap|image\/x\-portable\-pixmap|image\/x\-rgb|image\/x\-xbitmap|image\/x\-xpixmap|image\/x\-xwindowdump)$/i;
oFReader.onload = function (oFREvent) {
if (!oPreviewImg) {
var newPreview = document.getElementById(eleId);
oPreviewImg = new Image();
oPreviewImg.style.width = (newPreview.offsetWidth).toString() + "px";
oPreviewImg.style.height = (newPreview.offsetHeight).toString() + "px";
if(newPreview.children.length > 0)
newPreview.replaceChild(oPreviewImg, newPreview.children[0]);
else
newPreview.appendChild(oPreviewImg);
}
oPreviewImg.src = oFREvent.target.result;
// Add Bootstrap's img-thumbnail class to the image frame
oPreviewImg.classList.add("img-thumbnail");
};
return function () {
var aFiles = document.getElementById(inputId).files;
if (aFiles.length === 0) { return; }
if (!rFilter.test(aFiles[0].type)) { alert("You must select a valid image file!"); return; }
oFReader.readAsDataURL(aFiles[0]);
}
}
if (navigator.appName === "Microsoft Internet Explorer") {
return function () {
document.getElementById(eleId).filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = document.getElementById(inputId).value;
}
}
};
Then you can extract the base64 URL from the src attribute of the img element.
You may come out with your own preferred method of "cancelling" your upload by maybe destroying the img element.
I'm using the management API to get all my installed apps and extensions. I want to send this information to server.
Every app/extension has an icon array, that has size and url.
The url is something like chrome://extension-icon/gmgpodcgeocidkeclglljo88jp9kclhhmmdacfo/32/0. I want to send it to my server in base64.
What I'm trying to do is something like:
var reader = new FileReader();
reader.onloadend = function() {
result = reader.result;
}
if (file) {
reader.readAsDataURL(file);
} else {
result = "";
}
Where file is a File object of the icon url. But I cannot find how to create an File object with this url format.
I've tried:
var fileUrlArray = ["chrome://extension-icon/gmgpodcgeocidkeclglljo88jp9kclhhmmdacfo/32/0"];
var file = new File(fileUrlArray, 'icon.png');
And as a result I got an absolutely wrong base64 string.
Any ideas about how to do it?
Thanks.
So I have a website (using AngularJS) that lets users upload files via tag
<input type="file" ng-model="image" id="trigger" onchange="angular.element(this).scope().setFile(this)" accept="image/*">
When I handle the upload in the controller the image gets stored as a File object. This is my method that saves the file and also sets variables to preview the image
$scope.setFile = function (element) {
$scope.image = element.files[0]; // This is my image as a File
var reader = new FileReader();
//This is for previewing the image
reader.onload = function (event) {
$scope.image_source = event.target.result;
$scope.$apply();
}
reader.readAsDataURL(element.files[0]);
}
Now I am trying to compress the image using J-I-C library found here: https://github.com/brunobar79/J-I-C/blob/master/src/JIC.js
But this library accepts an image object as its parameter and returns it as a compressed image object. I can't seem to find a way to convert my $scope.image File object into an Image object. How would I go about doing this?
I would also need to convert the compressed image object back into a File so I can upload it to my Azure storage.
You just need to create an Image instance, and set it's src to your data url. Then pass it to JIC:
var img = new Image();
img.src = $scope.image_source;
jic.compress(img,...)
It then just uses a canvas element to manipulate the image, generate a new data url, and creates a new Image instance, setting its src to the data url. So when you get the compressed image back just take the src and use atob to decode the base64 encoded data back into binary and create a Blob. You can use Blob in most places that you would use File, for instance like uploading through ajax.
var newImg = jic.compress(oldImg,...);
//replace 'image/png' with the proper image mime type
var base64data = newImg.src.replace("data:image/png;base64,","");
var bs = atob(base64data);
var buffer = new ArrayBuffer(bs.length);
var ba = new Uint8Array(buffer);
for (var i = 0; i < bs.length; i++) {
ba[i] = bs.charCodeAt(i);
}
var blob = new Blob([ba],{type:"image/png"});
//now use blob like you would any other File object
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.
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)
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
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.
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"));
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..
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
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');
I have a HTML upload button to send (multiple) files to a server which responds with JSON. Based on that response, my application flow continues.
Now, to test the rest of my code (dependent on the server response), I would like to simulate the file upload so that I do not have to manually select and upload new files on every reload.
Following is a simplified version of my upload method:
uploadToServer: function (file) {
var self = this,
data = new FormData(),
xhr = new XMLHttpRequest();
// When the request has successfully completed.
xhr.onload = function () {
var response = $.parseJSON(this.responseText);
var photo = new App.Models.Photo({
filename: response.filename,
name: response.uuid
});
var photoView = new App.Views.Photo({ model: photo });
$('.photos').append(photoView.render().el);
};
// Send to server, where we can then access it with $_FILES['file].
data.append('file', file);
xhr.open('POST', this.url);
xhr.send(data);
}
The uploadToServer parameter file is a File-object from FileList. And as you can see, my server awaits the file inside of $_FILES['file'].
It would be awesome if a could simulate a real File-object being passed into uploadToServer, since I then do not have to worry about my existing events such as xhr.onload and xhr.onprogress being available.
If that won't be possible, I could create a custom uploadToServer method and send a regular AJAX-request and fake-respond on my server. But how can I then use and bind the code from my events (xhr.onload, xhr.onprogress etc.) to that AJAX-request?
You could simulate a file upload by creating a canvas, converting it to a file and passing the result to your method.
Create a canvas : here, a nice red square
var canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d");
canvas.width = 100;
canvas.height = 100;
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, 100, 100);
document.body.appendChild(canvas);
Convert it to a file and pass it to your method: I used Canvas to Blob to simplify the test but you probably can extract the bare bones to fit your needs.
canvas.toBlob( function (blob) {
m.uploadToServer(blob);
}, 'image/jpeg');
And a Fiddle http://jsfiddle.net/pvWxx/ Be sure to open a console to see the events and the request.
If you want to pass a filename with your blob, you can pass a third argument to formData.append, see How to give a Blob uploaded as FormData a file name? and an updated Fiddle http://jsfiddle.net/pvWxx/1/