how to upload separately selected files in form POST request? [duplicate] - javascript

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.

Related

Quickest Way To Send File To String And Back To File In JavaScript

So in a client-side HTML page, a user selects a file and uploads it to the JavaScript code. JavaScript parses the file and sends it to the server and back to everyone else who is on the site. Then every client makes a blob download link for the file. It's easy when I can send the file to server and back like this.
But now, I want to make that file available for future users of the site without saving it to a location. This is in a chat program, so I've been sending messages from users as strings to a database. I'd like to create a program to send the aforementioned File object to the shortest string possible and then recreate the file (including all metadata) at another client from this string.
What is the standard way to convert a Blob to a string and back again without losing anything? If there's multiple ways, what results in the shortest string?
I found the answer to my question, I had to modify some other answers from SO questions that only sorta applied to my question. Here's what I found:
This is on the uploading-client, in the function called when a file is uploaded:
let inp = document.getElementById("file_input");
let reader = new FileReader();
reader.onload = function(){
send_off_to_other_clients(reader.result);
}
reader.readAsBinaryString(inp.files[0]);
On the other clients:
<script>
function get_blob_from_string (string, type, name) {
let array = new Uint8Array(string.length);
for (let i = 0; i < string.length; i++){
array[i] = string.charCodeAt(i);
}
let end_file = new Blob([array], {type: type, name: name});
let a = document.createElement("a");
a.href = URL.createObjectURL(end_file);
a.download = name;
a.target = "_blank";
a.click();
}
</script>
end_file is the returned-to-blob version, and then I create an anchor tag to download it. Probably isn't "proper" but it works.

Reading image into JS to subsequently compress it

I'm a server-side dev who's new to front-end development. I've been tinkering around with vanilla Javascript and need to clarify some concepts.
The usecase I'm experimenting with is handling image upload (and mid-air JS-facilitated compression before said upload to server) via JS.
Currently I'm stuck on step one. Imagine the following simple set up:
<form method="POST" action="some_url">
<input type="file" id="browse_image" onchange="compressImage(event)">
<input type="submit" value="Submit">
</form>
My question is:
At what step do I try to pass the image to a JS function (given my goal is to compress it and send it to the server)? Would this happen at the time of image selection (i.e. pressing the browse button), or at the point of pressing Submit? Where do I put the event and how do I proceed from there? A quick illustrative answer with a an example would be great!
I've been trying to do it at the point of image selection (to no avail):
function compressImage(e){
var reader = new FileReader();
reader.readAsDataURL(e.target.files[0]);
console.log(reader);
}
Would be great to get a conceptual walkthrough, alongwith a quick illustrative example. Vanilla JS only, I'm not going to touch JQuery before I get the hang of JS.
In my mind (but it's a bit subjective), you would do it in both places.
User selects a File from your input
You process the file through js
If your processing failed (e.g the file was not an image / corrupted / who knows) you can let the user know directly.
If the processing succeeded, when user clicks submit, you overwrite the default behavior of your form, and send a FormData containing your new File/Blob instead of the original one.
var toSend = null, // here we will store our processed File/Blob (the one to send)
browse_image = document.getElementById('browse_image');
// when user selects a new File
browse_image.onchange = process_user_file;
// when user decides to send it to server
document.querySelector('form').onsubmit = overwrite_default_submit;
// grab the file from the input and process it
function process_user_file(evt) {
// since we attached the event listener through elem.onevent,
// 'this' refers to the input
var file = this.files[0];
// here do your compression, for demo, we'll just check it's a png file
var reader = new FileReader();
// a FileReader is async, so we pass the actual checking script as the onload handler
reader.onload = checkMagicNumber;
reader.readAsArrayBuffer(file.slice(0,4));
}
// we don't want the real form to be submitted, but our processed File/Blob
function overwrite_default_submit(evt) {
// block the form's submit default behavior
evt.preventDefault();
// create a new form result from scratch
var form = new FormData();
// add our File/Blob
form.append("myfile", toSend, browse_image.files[0].name);
// create a new AJAX request that will do the same as original form's behavior
var xhr = new XMLHttpRequest();
xhr.open('POST', evt.target.action);
// xhr.send(form); // uncomment to really send the request
console.log('sent', toSend);
}
// simply checks if it's really a png file
// for you, it will be your own compression code,
// which implementation can not be discussed in this answer
function checkMagicNumber(evt) {
var PNG = '89504e47';
var arr = new Uint8Array(evt.target.result);
var header = "";
for(var i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}
// some user friendly actions
if(header !== PNG) {
alert('not a png file'); // let the user know it didn't work
browse_image.value = ""; // remove invalid File
sub.disabled = true; // avoid the form's submission
toSend = null; // nothing to send
}
else {
toSend = browse_image.files[0]; // for demo we don't actually modify it...
sub.disabled = false; // allow form's submission
}
}
<form method="POST" action="some_url">
<label>Please select a .png file</label>
<input type="file" id="browse_image" name="myfile">
<input type="submit" value="Submit" disabled id="sub">
</form>
Ps: note that even your original code wouldn't have sent anything, since no input in your form had a name attribute.

JavaScript FileReader Massive Result

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.

How do I save and restore a File object in local storage

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');

When using plupload HOWTO duplicate file object when selected & submit it separately for a related server-side processing

I am using plupload to let users upload images. But I also want to generate thumbnails for preview before they finally decide to keep it. I understand currently "Image preview" feature is not present in plupload. So to work around this I decided to submit a new form containing just one file for each image added, & let the server process it & return a thumbnail.
Now my question is how do I get the handle on the file object from the plupload so that I can create an "input" file field dynamically.
Currently I iterate over uploader.files & set input.name but I dont know how to set the input.value field, since I cant seem to get the complete file path of the file added.
I am up for any suggestions (in addition to replacing this approach completely), I just need thumbnail of the file selected for upload.
Maybe my answer is a bit late, but I searched today for a similar solution and came up with the following approach. It will only work with the HTML5 Runtime.
As there is no way to get the file objects from plupload, I changed the onchange event of the dynamically created input field and store the file objects for myself. This is done by binding to the PostInit-Event.
After that I can show the image to the user by using the FileReader API introduced with HTML 5. So there is no need to send the image to the server. See my FilesAdded Listener below.
// Currently added File Objects
var nativeFiles = {};
var uploader = new plupload.Uploader({
runtimes : 'html5,html4',
// Your settings...
});
uploader.bind('PostInit', function(up, params) {
// Initialize Preview.
if(uploader.runtime == "html5") {
var inputFile = document.getElementById(uploader.id + '_html5');
var oldFunction = inputFile.onchange;
inputFile.onchange = function() {
nativeFiles = this.files;
oldFunction.call(inputFile);
}
}
});
uploader.bind('FilesAdded', function(up, files) {
for (var i in files) {
// Your code...
// Load Preview
if(uploader.runtime == "html5") {
var fileObject = uploader.getFile(files[i].id);
var reader = new FileReader();
reader.onload = (function(file, id) {
return function(e) {
var span = document.getElementById('thumb_'+id);
span.innerHTML = "<img src='"+e.target.result+"'/>";
};
})(nativeFiles[i], files[i].id);
reader.readAsDataURL(nativeFiles[i]);
}
}
});

Categories