javascript FileReader: preview for images in multiple files batches - javascript

Hi I'm trying to implement a script creating multiple file uploads preview using FileReader.
This is my starting point
<input id="attachments" type="file" multiple="multiple" name="attachments">
<table id="file_info_attachments" class="table table-preview"></table>
What I want to do is to read uploaded files list, put their names and size into file_info_attachments table (one row for every file) and, the tricky part, if they're images, add in the same row a preview.
My ideal preview should look like this
<input id="attachments" type="file" multiple="multiple" name="attachments">
<table id="file_info_attachments" class="table table-preview">
<tr>
<td>No preview</td>
<td>file_1.txt</td>
<td>2000</td>
</tr>
<tr>
<td><img src="[IMAGE DATA]" /></td>
<td>file_2.png</td>
<td>2000</td>
</tr>
</table>
My parsing function scratch looks like this:
function parseFiles(files, id) {
$("#file_info_" + id).html("");
if(files.length > 0) {
for (var i = 0; i < files.length; i++) {
$("#file_info_" + id).append("<tr><td class=\"preview preview_" + i + "\">No preview</td><td>" + files[i].name + "</td><td>Size: " + files[i].size + "</td></tr>");
if(/\.(jpe?g|png|gif)$/i.test(files[i].name) && typeof (FileReader) != "undefined") {
var reader = new FileReader();
reader.onload = function(e){
//obviously I cannot do that this way due to how reader.onload works
//$("#file_info_" + id + " .preview_" + i).html("<img src=\"" + e.target.result + "\" />");
};
reader.readAsDataURL(files[i]);
}
}
}
}
And that's how I call parseFiles function:
$(document).on("change", "#attachments", function(){
var files = $(this)[0].files;
parseFiles(files, "attachments");
});
I know I can make it work just by appending info rows into reader.onload function instead of adding that right after for cycle but, since I'm handling different types of files, I'd like just to keep them ordered and just update the preview field in case they're images.
How can I just make reader.onload update the inner HTML of my preview by adding the
Thanx in advance.

Not sure if browsers will preserve the order in which user has selected multiple files Actualy I'm pretty confident it is ordered alphabetically on my FF, but to display your images in the same order as you get in the input.files property,
simply don't use a FileReader for reading user selected Files.
Instead, you should prefer URL.createObjectURL() method.
This method is synchronous, so you won't have to deal with different scopes, also, for user selected Files, this is just a direct pointer to the File stored in user system, i.e, memory impact is almost 0.
file.onchange = function(e) {
for (let i = 0; i < this.files.length; i++) {
let url = URL.createObjectURL(this.files[i]);
let img = new Image();
img.src = url;
document.body.appendChild(img);
// you can even free these 10bits you occupy in memory if you don't need the url anymore
img.onload = function() {
URL.revokeObjectURL(this.src);
}
console.log(this.files[i].name);
}
}
<input type="file" accept="image/*" multiple id="file">

Related

Displaying preview of multiple selected files in Angular

I am kind of new to Angular and right now I am trying to select multiple files and display their preview before uploading them to server.
My below code works fine if there are multiple files but selected separately, hovewer while trying to select multiple images at once, only the last one is rendered. I checked with console.log and while selecting multiple files at once the reader.result of the previous files except the last one is always null ever though the reader.onload is executing (the object is also inserted into the array, but without the image url).
I want to be able to also remove the selected files, so both files and their imagesUrl are kept in an FileWithImage object to keep the right order of their previews.
component.ts:
onFilesSelected(event: any): void {
this.selectedFiles = event.target.files;
for (let i = 0; i < this.selectedFiles.length; i++) {
let fileWithImage: FileWithImage = {
imageUrl: '',
file: this.selectedFiles[i],
};
var reader = new FileReader();
reader.readAsDataURL(fileWithImage.file);
reader.onload = (event) => {
fileWithImage.imageUrl = reader.result;
console.log(fileWithImage.file.name);
console.log(fileWithImage.imageUrl);
this.filesWithImages.push(fileWithImage);
};
}
}
component.html (selecting and displaying images):
<button
type="button"
mat-raised-button
(click)="fileInput.click()"
class="button"
>
Choose File
</button>
<input
hidden
(change)="onFilesSelected($event)"
#fileInput
type="file"
accept="image/*"
multiple="multiple"
/>
<div *ngFor="let file of filesWithImages" class="images">
<img
[src]="file.imageUrl"
height="10%"
width="10%"
*ngIf="file.imageUrl"
/>
</div>
I tried blocking the loop untill the reader.onload is executed with a use of a flag, but it did not work.
I also tried to get the url directly from a method:
createImageUrl(file: File): any {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
return reader.result;
};
}
and call it in [src] of an image tag in html, but it made my program crash.
The main problem is probably why the result of reader is null for the first few files and only the last one is properly displayed. Can someone help me to find the cause of this problem?
Try changing reader variable to local scope.
Change
var reader = new FileReader();
to
let reader = new FileReader();

Get file names while displaying and before uploading on server using html and jquery

I am selecting multiple images using html tag 'input file multiple html' as below.
#Html.TextBoxFor(model => model.files, "",
new {#id = "filesToUploadID", #type = "file", #multiple = "multiple" })
<div class="col-md-10" id="selectedFiles"></div>
Then in javascript, I attached 'onchange' event listner to the above tag. When I select multiple images, I get all the attached images to the tag using jquery. But I need image file names as well. While getting image file names, I get only one file name poplulated in my html along with image using jquery.
jquery/ javascript is below
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('#filesToUploadID').addEventListener('change', handleFileSelect, false);
selDiv = document.querySelector("#selectedFiles");
}
function handleFileSelect(e) {
debugger;
NameArray = [];
if (!e.target.files) return;
selDiv.innerHTML = "";
var files = e.target.files;
for (var i = 0; i < files.length; i++) {
var f = files[i];
NameArray.push(f.name);
var reader = new FileReader();
reader.onload = function (e) {
var html = "<img src='" + e.target.result + "' />";// + "<div>" + + "</div>";//+ "<br clear=\"left\"/>";
$(selDiv).append($(html));
//selDiv.innerHTML += html;
}
reader.readAsDataURL(f);
}
}
I tool help from this link and implemented both ways but could not succeed.
All images names are relevant to the images. What I am getting is like this
It's source code is
I have to assign id's based on image names that's why it is important to get relevant image names. Any lead to the topic will be high appreciated.
Thank you.
var f = files[i];
var fileName = e.target.f.name

Adding Metadata to Html5 file upload

Is it possible to add metadata information along with html 5 images before they are uploaded?
clarification: html 5 images upload (like the drag and drop thumbnail method or single input file) sample HTML 5 Upload/drag_drop
So say user is uploading 10 images, he will fill some text info for each image describing it, then this information will be saved on the server.
So for each thumbnail user can add "title","location","description" in text boxes.
the example below allows an upload of a single image and preview of a thumbnail, what I am trying to achieve is to interact with this thumbnail and submit some text to the server along with it.
clarification 2: this question is not related to the picture EXIF,
I am asking about general metadata information user can add to the picture, like "I took the picture right before my wedding", or "this picture of my old home" or like tags "Red"
document.getElementById("files").onchange = function () {
var reader = new FileReader();
reader.onload = function (e) {
// get loaded data and render thumbnail.
document.getElementById("image").src = e.target.result;
};
// read the image file as a data URL.
reader.readAsDataURL(this.files[0]);
};
<input type="file" id="files" />
<img width="100" height="140" id="image" />
I am not sure why you are getting down votes for this question. It may have something to do with your wording. I assume you mean Exif data rather then metadata. See https://en.wikipedia.org/wiki/Exif
For modifying Exif data of an image in JavaScript you can use piexifjs by hMatoba, it will allow you to modify the images Exif metadata before uploading to the server.
Hope this helps )
Maybe this is what you need.
As charlietfl already said in the comments you can use FormData
Here is an example:
First try with multiple inputs
Here you can see how it is saved than:
{
"args": {},
"data": "",
"files": {
"input1": "yourimg1",
"input2": "yourimg2"
},
"form": {
"input1": [
"house",
"london"
],
"input2": [
"bridge",
"berlin"
]
},
"headers": {
....
}
Now you have your files and descriptions connected with the name of the input field. It is important that all the beloning inputfields have the same name.
So the file from input1 belongs to the description in the array named input1 and the file from input2 to the array input2 and so on.
A few good explanations you can find here and if you want you can use this site to test the code and output.
You can take the code from here. Add at the end of the js function
console.log(request.response);
and change the url to http://httpbin.org/post.
Or just play a little bit around with it.
EDIT:
If you want it with multiple files you can do it like this:
var testForm = document.getElementById('test-form');
var div = document.getElementById('output');
var data;
testForm.onsubmit = function(event) {
event.preventDefault();
var request = new XMLHttpRequest();
request.open('POST', 'http://your.url/post', /* async = */ false);
var formData = new FormData(document.getElementById('test-form'));
request.send(formData);
}
function handleFileSelect(evt) {
var files = evt.target.files;
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML =
[
'<img src="',
e.target.result,
'" title="',
escape(theFile.name),
'"/><div class="desc"><div>Location:<input name="',
escape(theFile.name),
'"> </div><br><div>Title:<input name="',
escape(theFile.name),
'"></div><br><div>Description:<input name="',
escape(theFile.name),
'"></div><br></div>'
].join('');
document.getElementById('list').insertBefore(span, null);
};
})(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
//makes description visible
$(document).on("click","img",function(){
$(this).next().css("display", "block");
});
document.getElementById('files').addEventListener('change', handleFileSelect, false);
.desc{
display:none;
}
img{
height: 75px;
border: 1px solid #000;
margin: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id='test-form'>
<input type="file" id="files" name='input1' multiple/><br>
<output id="list"></output><br>
<input type='submit'>
</form>
Then this is you output:
{
"args": {},
"data": "",
"files": {
"input1": "all_your_images"
},
"form": {
"favicon.png": [
"1a",
"1b",
"1c"
],
"loginBackground.jpg": [
"2a",
"2b",
"2c"
]
},
"headers": {
....}
The disadvantage is that all images are uploaded together.
As you can see now each description is safed in an array with the name of its beloning image.
<div>Location:<input name="', escape(theFile.name),'"> </div>
This gives the inputname the name of the belonging image.
So if you want to safe the images and descriptions on your server in an array or something like that you have to get the names of the uploaded images with PHP.
$filename = pathinfo($_FILES['picture']['name'], PATHINFO_FILENAME);
(see HERE)
I know you want it with drag and drop, but the code would be to long with that.
If you still want to do this I can recomment you this site.
You only have to add the functionality to my posted code and it should work.
If you want to safe the informations directly in the image then I only can point to the answer of tnt-rox. Then you should visit his links and read more about exif.
I think the problem is that you put static image element. So I make a code that generates images and title text box for every file and this is the code snippet for what I reach
var count = 0;
document.getElementById("files").onchange = function (e) {
var image = document.createElement("img");
image.width = "100";
for(var i=0;i<this.files.length;i++){
var reader = new FileReader();
reader.onload = function (e) {
//Add the image
var im = image.cloneNode();
im.id= "image"+count;
im.src = e.target.result;
document.body.appendChild(im);
//Add the title for example
var title = document.createElement("input");
title.id = "title-img"+count;
title.placeholder= "Enter the title of image "+count;
document.body.appendChild(title);
count++;
};
reader.readAsDataURL(this.files[i]);
}
};
<html>
<head>
</head>
<body>
<input type="file" multiple id="files" />
</body>
</html>
I hope it helps you.
For this Purpose, Just use the Form to get the details from the user. The code example is given below,
HTML Code,
<Form action="imageSave.php" method="Post">
<table>
<tr>
<td> Title </td>
<td> <input type="text" name="imageTitle" > </td>
<tr>
<tr>
<td> Location</td>
<td> <input type="text" name="imageLocation" > </td>
<tr>
<tr>
<td> Description</td>
<td> <textarea name="description" > </textarea> </td>
<tr>
<tr>
<td> Image </td>
<td> <input type="file" name="files" > </td>
<tr>
</table>
</Form>
In your Database add these Columns,
TableName : tblImages
Column 1 : id [int]
Column 2 : imageTitle [varchar(255)]
Column 3 : imageLocation [varchar(255)]
Column 4 : description [text]
Column 5 : imageName [varchar(255)]
Then use this PHP File (imageSave.php)
$imageTitle=$_POST['imageTitle'];
$imageLocation=$_POST['imageLocation'];
$description=$_POST['description'];
$imageName=$_FILES['files']['name'];
if ($_FILES["files"]["error"] == 0)
{
$remove_these = array(' ','`','"','\'','\\','/','(',')');
$newname = str_replace($remove_these,'',$_FILES['files']['name']);
//Make the filename unique
$newname = time().".".$extension;
//Save the uploaded the file to another location
$upload_path = "/images/$newname";
//echo $upload_path;
move_uploaded_file($_FILES['files']['tmp_name'], $upload_path);
// Query and Code to Update the Database
}

How To Remove selected file in Javascript/Jquery

I have a form that allows users to upload multiple images. Users has an option to remove Images one by one if they don't want to upload that particular image.
Now, How to remove the value from the file type of the one that they removed (e.g. didn't want to upload)?
I have tried using ($('input').val("")) but it works only for single image not for multiple images. If I used this for multiple image then all uploaded images value become empty instead of that particular image that user want to remove.. Below is my Code:-
HTML
<input type="file" id="files2" name="cert_img" multiple>
<output id="list2"></output>
Javascript
var count2=0;
function handleFileSelect2(evt) {
var $fileUpload = $("input#files2[type='file']");
count2=count2+parseInt($fileUpload.get(0).files.length);
var files = evt.target.files;
for (var i = 0, f; f = files[i]; i++) {
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
reader.onload = (function (theFile) {
return function (e) {
var span = document.createElement('span');
span.innerHTML = ['<img class="thumb" id="image_X" src="', e.target.result, '" title="', escape(theFile.name), '"/><span class="remove_img_preview"></span>'].join('');
document.getElementById('list2').insertBefore(span, null);
};
})(f);
reader.readAsDataURL(f);
}
}
$('#files2').change(function(evt){
handleFileSelect2(evt);
});
$('#list2').on('click', '.remove_img_preview',function () {
$(this).parent('span').remove();
//$('input').val("");
});
Thanks in advance..
According to this question you can't change FileList content because it is readonly.
But if you want to remove a file of FileList, you can create a new object and set to it files that you want to upload. Then use created object to upload files. Run my example and select multiple files. Then click on any file that you want to delete. After delete files, see browser console.
var files;
$("#files").on("change", function(e){
files = $("#files")[0].files;
$.each(files, function(index, value){
$("ol").append("<li data-index='"+index+"'>"+ value.name +"</li>");
});
});
$(document).on("click", "li", function(){
var newFiles = {};
var index = $(this).index();
$("li:nth-child(" + (index + 1) + ")").remove();
$("li").each(function(index, element){
var dataIndex = $(element).attr("data-index");
newFiles[index] = files[dataIndex];
});
console.log(newFiles);
});
li:hover {
color: red;
cursor: default;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="file" id="files" multiple>
<ol></ol>

Retrieve image data from file input without a server

For context, I'm trying to create a "click image" file uploader. Initially there is a default image, which I then click. I trigger a file upload, and the user picks an image file they want. Then I will set the image to replace the default (and do other things with it later). Right now, the front end looks something like this:
<div class="right-preview">
<input type="image" src="img/logo.png" height="240px" width="240px" ng-click="uploadImage('right-image')" id="upload-right-image"/>
<input type="file" id="upload-right" style="visibility: hidden">
</div>
When the image is clicked, it triggers an upload action.
$scope.uploadImage = function(side) {
$image = $('#upload-' + side);
$fileInput = $('#upload-right');
$fileInput.change(function(changeEvent) {
var files = changeEvent.target.files;
for(var i = 0; i < files.length; i++) {
file = files[i];
console.log(file);
}
});
$fileInput.trigger('click');
}
When the change event is fired after the user finishes picking their file, I have the changeEvent and I know they've selected their file. Each of the files has some properties (like name and size) but I'm not seeing anything for accessing the raw data so I can set the src on my other element.
Am I just completely missing how to get the image data, or is there a better way to do this? I have no server (right now) to post this to. Perhaps there is a better way to approach this?
This link may be helpful to you - https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
I took one method from that page and added some additional functionality to hide the file upload button and have the image placeholder trigger its click event.
$('#placeholder').click(function() {
$('#img-upload').trigger('click');
});
function previewFile() {
var preview = document.querySelector('img');
var file = document.querySelector('input[type=file]').files[0];
var reader = new FileReader();
reader.addEventListener("load", function () {
preview.src = reader.result;
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
.hidden {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<img width="250" height="250" id="placeholder" src="http://place-hold.it/250x250&text='click to upload'">
<input class="hidden" type="file" onchange="previewFile()" id="img-upload">

Categories