I've been looking around and haven't been able to find whether this is possible or not.
This is what I would like to happen before submitted: When a user selects a file to be uploaded, I want to grab the image's data and convert it to base64. After it's been converted, I would like to either directly display it in a div or have it sent to the server via AJAX, then displayed in the div.
Below is basically what I'm looking for:
// index.php
<input type="file" name="img" id="img" onChange="displayImg(this)">
// displayImg.js
function displayImg(img) {
imgData = img.?; // How do I do this?
img64 = // I know how to do this.
document.write("<img src='data:image/jpeg;base64,"+img64+"' />");
}
function displayImage(evt){
var files = evt.target.files;
var reader = new FileReader();
reader.onload = function(frEvent) {
document.getElementById("renderImage").innerHTML = '<img src="'+frEvent.target.result+'" />';
}
reader.readAsDataURL(files[0]);
}
The code above works for me. It lacks verification and all that good stuff, but this should be a good starting point for you.
Related
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.
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.
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');
Using the filereader API it is possible to show a preview of the file, by reading the file with readAsDataURL
What I am trying to do is:
The user selects a file
A preview is shown, so that the user has some feedback.
If the user is satisfied, he submits the data to the backend.
Implementing step 3 can be done by re-reading the file with readAsBinaryString, but this looks problematic because the data could have disappeared or changed on disk. So What I would like is to convert the data returned from readAsDataURL to the format returned by readAsBinaryString. How can I do this?
Another alternative would be to submit the data to the backend as returned by readAsDataURL, but I would like to avoid that, since that would require special handling on the backend in my case.
Like CBroe said, you dont need to read the file twice.
JS :
handleFileSelectThumbFile(evt){
var files = evt.target.files;
var file = files[0];
// You can get the mime type like this.
var thumbMIME = files[0]['name'].split('.').pop();
if (files && file) {
var reader = new FileReader();
reader.onload = function(readerEvt) {
// Split the readerEvt.target.result by a ','.
// You can send the binaryString variable to the server.
// Its base64 encoded already.
var binaryString = readerEvt.target.result.split(',')[1];
// Set the image preview to the uploaded image.
$('.img-preview').prop('src', readerEvt.target.result);
}.bind(this);
reader.readAsDataURL(file);
}
}
HTML :
<input type="file" onChange={this.handleFileSelectThumbFile} required/>
<img src='http://placehold.it/300' class='img-preview'/>
You can read the MIME type from the first part of readerEvt as well. Look at CBroe's comment above.
I'm making a simple slider to show off artwork for a friend of mine. I'm really only familiar with javascript/jquery, so I'm not 100% comfortable using something else right now.
Since my friend doesn't have any programming knowledge, I'm trying to keep this really simple for her to update (i.e., automating creating new images whenever she adds a new one to the folder). She will upload images to a folder and will have to number them (i.e., 1.jpg, 2.jpg). My javascript uses a for loop to loop through numbers (she will have to update the loop whenever she adds a new image) and insert them into the file name. HOWEVER this limits her to only uploading one type of file. Is there someway to change the extension only using javascript?
This is what I have so far:
function callImages(){
//create the image div
$('.artslider').append('<div class="image"></div>');
//create the files array
var files = [];
//start the loop, starting position will have to be updated as images are added
for (i=8;i>=0;i--){
//create the img src for a jpg img
var imgJPG = 'arts/'+i+'.jpg';
//find the natural width of the image after it loads to see if it actually exists
var imgWidth = $('imgJPG').load().naturalWidth;
//if the width is undefined, replace the jpg extension with gif
if (imgWidth===undefined){
var imgGIF = imgJPG.replace('jpg', 'gif');
files[i] = '<img src="'+imgGIF+'" class="artsliderimg"/>';
}
//otherwise keep the jpg extension
else {
files[i] = '<img src="'+imgJPG+'" class="artsliderimg"/>';
}
//then add the images to the img div
$('.image').append(files[i]);
}
};
The problem with this if/else is that it will only create a gif image. If you switch the order, it will only create a jpg image.
edit: here's what this code produces: https://googledrive.com/host/0B1lNgklCWTGwV1N5cWNlNUJqMzg/index.html
The problem is with this bit of code:
var imgJPG = 'arts/'+i+'.jpg';
var imgWidth = $('imgJPG').load().naturalWidth;
imgWidth will always be undefined.
Firstly you are passing in the string 'imgJPG' instead of the parameter imgJPG. Secondly I think you have misunderstood jQuery selectors, this is used for selecting HTML elements, inputting a file path into here will not achieve anything. Thirdly I think you have misunderstood the load function, this is used for loading data from the server into a HTML element.
I would suggest using a function like below to check if the image exists:
function urlExists(url) {
var http = jQuery.ajax({
type:"HEAD",
url: url,
async: false
});
return http.status == 200;
}
Then in your code:
if (!urlExists(imgJPG)){
var imgGIF = imgJPG.replace('jpg', 'gif');
files[i] = '<img src="'+imgGIF+'" class="artsliderimg"/>';
}
else {
files[i] = '<img src="'+imgJPG+'" class="artsliderimg"/>';
}