I've built a file uploader (that runs on php in the backend) that previews an image file prior to upload.
The problem I'm having is I can't get it work with multiple files.
It's based on a tutorial I watched and the crux of the issue is in the updateThumbnail function. When this function is called for multiple file uploads I think I need to change the second parameter from fileUploader.files[0] to fileUploader.files, but I'm struggling with the actual function itself.
I clearly need to run a foreach loop (or similar) in the updateThumbnail function but I can't get it to play ball.
Note: It seems CodePen doesn't allow drag & drop functionality, but there is an input file element that is also assigned to the drop-zone that is hidden in the HTML with display:none. This uses a click event listener and fileUploader.click() so when you click the drop-zone you can bring up the file picker window.
Codepen: https://codepen.io/pauljohnknight/pen/JjNNyzO
// hidden on the form, but has drag & drop files assigned to it
var fileUploader = document.getElementById("standard-upload-files");
var dropZone = document.getElementById("drop-zone");
var showSelectedImages = document.getElementById("show-selected-images");
dropZone.addEventListener("click", (e) => {
//assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
fileUploader.click();
});
fileUploader.addEventListener("change", (e) => {
if (fileUploader.files.length) {
// this function is further down but declared here and shows a thumbnail of the image
updateThumbnail(dropZone, fileUploader.files[0]);
}
});
dropZone.addEventListener('dragover', e => {
e.preventDefault()
})
dropZone.addEventListener('dragend', e => {
e.preventDefault()
})
// When the files are dropped in the 'drop-zone'
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
// assign dropped files to the hidden input element
if (e.dataTransfer.files.length) {
fileUploader.files = e.dataTransfer.files;
}
// function is declared here but written further down
updateThumbnail(dropZone, e.dataTransfer.files[0]);
});
// updateThumbnail function that needs to be able to handle multiple files
function updateThumbnail(dropZone, file) {
var thumbnailElement = document.querySelector(".drop-zone__thumb");
if (!thumbnailElement) {
thumbnailElement = document.createElement("img");
thumbnailElement.classList.add("drop-zone__thumb");
// append to showSelectedImages div
showSelectedImages.appendChild(thumbnailElement);
}
if (file.type.startsWith("image/")) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
thumbnailElement.src = reader.result;
};
} else {
thumbnailElement.src = null;
}
} // end of 'updateThumbnail' function
body {
margin: 0;
display: flex;
justify-content: center;
width: 100%;
}
form {
width: 30%;
}
#drop-zone {
border: 1px dashed;
width: 100%;
padding: 1rem;
margin-bottom: 1rem;
}
.select-files {
text-decoration: underline;
cursor: pointer;
}
/* image that is preview prior to form submit*/
.drop-zone__thumb {
width: 200px;
height: auto;
display: block;
}
#submit-images {
margin-top: 1rem;
}
<form id="upload-images-form" enctype="multipart/form-data" method="post">
<h1>Upload Your Images</h1>
<div id="drop-zone" class="drop-zone flex">
<p class="td text-center">DRAG AND DROP IMAGES HERE</p>
<p>Or</p>
<p class="select-files">Select Files</p>
</div>
<div id="show-selected-images"></div>
<div class="inner-input-wrapper">
<div class="upload-label-wrapper">
<input id="standard-upload-files" style="display:none" type="file" name="standard-upload-files[]" multiple>
</div>
<input type="submit" name="submit-images" id="submit-images" value="SUBMIT IMAGES">
</div>
</form>
I did quick Codesandbox example from your Codepen
Yes, you just need to iterate over your files and for each file add a preview. You can use for loop or just use Array.from and then .forEach (because FileList is not really an array, you need to convert it to array first to be able to use array inbuilt methods)
Array.from(fileUploader.files).forEach((file) => {
updateThumbnail(dropZone, file);
});
As for previews and updateThumbnail function - it all depends on how you want to use it. If you want users to be able to add more files after first selection then you can just append new previews. If you want to clear the old ones if the user select new ones then you would need to delete old previews. Or maybe you could add "Delete" button for each preview so the user could delete one of them after adding.
Here is the variant when you just want to append new previews:
function updateThumbnail(dropZone, file) {
if (file.type.startsWith('image/')) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
var thumbnailElement = document.createElement('img');
thumbnailElement.classList.add('drop-zone__thumb');
thumbnailElement.src = reader.result;
showSelectedImages.appendChild(thumbnailElement);
};
}
}
For drop you basically do the same:
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
// .. do whatever you want or need here
Array.from(e.dataTransfer.files).forEach((file) => {
updateThumbnail(dropZone, file);
});
});
As you can see functions for handling drop and select are quite similar, you can even make separate function which accepts fileList and then do something with it, so would not need to duplicate your code for both cases.
Made some minor changes to #Danila's version
Most notable differences is the use of es6 and a way to load image faster using URL.createObjectURL. The File reader is pretty much a legecy thing now when there is object urls + new promise based read methods on the prototype itself. Using bae64 is a waste of time decoding/encoding to/from base64
https://codesandbox.io/s/httpsstackoverflowcomquestions68416563-forked-o5spy?file=/src/index.js
import "./styles.css";
// Query all needed elements in one go
const [dropZone, showSelectedImages, fileUploader] = document.querySelectorAll(
"#standard-upload-files, #drop-zone, #show-selected-images"
);
dropZone.addEventListener("click", (evt) => {
// assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
fileUploader.click();
});
// Prevent browser default when draging over
dropZone.addEventListener("dragover", (evt) => {
evt.preventDefault();
});
fileUploader.addEventListener("change", (evt) => {
// Clear the already selected images
showSelectedImages.innerHTML = "";
// this function is further down but declared here and shows a thumbnail of the image
[...fileUploader.files].forEach(updateThumbnail);
});
dropZone.addEventListener("drop", (evt) => {
evt.preventDefault();
// Clear the already selected images
showSelectedImages.innerHTML = "";
// assign dropped files to the hidden input element
if (evt.dataTransfer.files.length) {
fileUploader.files = evt.dataTransfer.files;
}
// function is declared here but written further down
[...evt.dataTransfer.files].forEach(updateThumbnail);
});
// updateThumbnail function that needs to be able to handle multiple files
function updateThumbnail(file) {
if (file.type.startsWith("image/")) {
const thumbnailElement = new Image();
thumbnailElement.classList.add("drop-zone__thumb");
thumbnailElement.src = URL.createObjectURL(file);
showSelectedImages.append(thumbnailElement);
}
} // end of 'updateThumbnail' function
Related
I have an image uploader that I'm building that allows the user to remove images/ image previews prior to form submission. I seem to have a come across a side effect that a) I don't know what is causing it, and b) how to fix it.
When an image is deleted from the preview the image is removed via a click event and the remove() method on the image's parent figure element.
If a user then re-selects the same image (that has just been removed) from their computer with the file picker / input the image doesn't show on the preview the second time around? But, even more confusingly, if a completely different image is attached, and then the user tries to re-attach the original image that previously didn't show the second time, that image does then show again (I hope this makes sense).
I have no idea what is going on here?
Codepen: https://codepen.io/thechewy/pen/qBYYMYV
let dropZone = document.getElementById("zone"),
showSelectedImages = document.getElementById("show-selected-images"),
fileUploader = document.getElementById("upload-files");
dropZone.addEventListener("click", () => {
// assigns the dropzone to the hidden input element, when you click 'select files' it brings up a file picker window
fileUploader.click();
});
// Prevent browser default when draging over
dropZone.addEventListener("dragover", (e) => e.preventDefault());
// Prevent browser default when draging over
dropZone.addEventListener("drop", (e) => e.preventDefault());
fileUploader.addEventListener("change", (e) => {
// this function is further down but declared here and shows a thumbnail of the image
[...fileUploader.files].forEach(updateThumbnail);
console.log("fileUploader.files is: ", [...fileUploader.files]);
});
// updateThumbnail function
function updateThumbnail(file) {
if (file.type.startsWith("image/")) {
let uploadImageWrapper = document.createElement("figure"),
thumbnail = new Image(),
removeImage = `
<div class="remove-image jc-center flex"> Delete </div>
`;
// image thumbnail
thumbnail.classList.add("thumbnail");
thumbnail.src = URL.createObjectURL(file);
// appending elements
showSelectedImages.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(thumbnail); // image thumbnail
uploadImageWrapper.insertAdjacentHTML("afterbegin", removeImage); // 'x' to remove image
// get the original width and height values of the thumbnail using the decode() method
thumbnail
.decode()
.then((response) => {
thumbWidth = thumbnail.naturalWidth;
thumbHeight = thumbnail.naturalHeight;
// typical front end image validations
if (thumbWidth * thumbHeight < 4000000) {
// output the error message
}
})
.catch((encodingError) => {
// Do something with the error.
});
// Delete images from the preview
document
.querySelectorAll("#show-selected-images .remove-image")
.forEach((i) => {
i.addEventListener("click", (e) => {
if (e.target) {
var deleteFigureEl = e.target.closest("figure");
// removes the image via removing it's parent element
deleteFigureEl.remove();
}
});
});
}
} // end of 'updateThumbnail' function
body {
margin: 0;
display: flex;
justify-content: center;
font-family: arial;
}
form {
width: 50%;
max-width: 600px;
}
.select-files {
padding: 1rem;
background: red;
cursor: pointer;
color: #fff;
font-weight: bold;
}
#show-selected-images {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-top: 2rem;
}
figure {
width: 100%;
margin: 0;
}
img {
display: block;
width: 100%;
height: auto;
}
.remove-image {
cursor: pointer;
padding: 1rem;
background: lightgrey;
}
<form id="upload-images-form" enctype="multipart/form-data" method="post">
<h1>Upload Your Images</h1>
<div id="zone">
<p class="select-files">SELECT FILES</p>
</div>
<div class="inner-wrapper">
<div class="upload-label-wrapper">
<input id="upload-files" style="display:none;" type="file" name="upload-files[]" multiple>
</div>
<button id="submit-images" oninput="updateThumbnail(this.files)">SUBMIT</button>
</div>
<div id="show-selected-images"></div>
</form>
let dropZone = document.getElementById("zone"),
showSelectedImages = document.getElementById("show-selected-images"),
fileUploader = document.getElementById("upload-files"),
files = [],
filesIndexes = new Set();
dropZone.addEventListener("click", () => {
// assigns the dropzone to the hidden input element, when you click 'select files' it brings up a file picker window
fileUploader.click();
});
// Prevent browser default when draging over
dropZone.addEventListener("dragover", (e) => e.preventDefault());
// Prevent browser default when draging over
dropZone.addEventListener("drop", (e) => e.preventDefault());
fileUploader.addEventListener("change", (e) => {
// this function is further down but declared here and shows a thumbnail of the image
[...fileUploader.files].forEach((file) => {
updateThumbnail(file);
let i = files.length;
while (filesIndexes.has(i)) {
i++;
}
files.push({ file, index: i });
filesIndexes.add(i);
});
fileUploader.value = "";
console.log("fileUploader.files is: ", [...fileUploader.files]);
});
// updateThumbnail function
function updateThumbnail(file) {
if (file.type.startsWith("image/")) {
let uploadImageWrapper = document.createElement("figure"),
thumbnail = new Image(),
removeImage = `
<div class="remove-image jc-center flex" index="${files.length}"> Delete </div>
`;
// image thumbnail
thumbnail.classList.add("thumbnail");
thumbnail.src = URL.createObjectURL(file);
// appending elements
showSelectedImages.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(thumbnail); // image thumbnail
uploadImageWrapper.insertAdjacentHTML("afterbegin", removeImage); // 'x' to remove image
// get the original width and height values of the thumbnail using the decode() method
thumbnail
.decode()
.then((response) => {
thumbWidth = thumbnail.naturalWidth;
thumbHeight = thumbnail.naturalHeight;
// typical front end image validations
if (thumbWidth * thumbHeight < 4000000) {
// output the error message
}
})
.catch((encodingError) => {
// Do something with the error.
});
// Delete images from the preview
document
.querySelectorAll("#show-selected-images .remove-image")
.forEach((i) => {
i.onclick = (e) => {
console.log(e.currentTarget);
if (e.target) {
var deleteFigureEl = e.target.closest("figure");
let index = e.currentTarget.getAttribute("index");
files.forEach((obj, i) => {
if (obj.index == index) {
files.splice(i, 1);
filesIndexes.delete(Number(index));
}
});
// removes the image via removing it's parent element
deleteFigureEl.remove();
}
};
});
}
} // end of 'updateThumbnail' function
And then you should upload files like this
function upload(){
let formData = new FormData()
for (let index = 0; index < files.length; index++) {
formData.append(`file${files[index].index}`, files[index].file)
}
fetch("url", {method: "POST", body: formData})
}
I am trying to call (each) after completing the function change. My script add list the files from the input file field and I would like to show the files one by one after finishing it example. My code does not work, what am I doing wrong?
$('.drop-input').on('change', function(){
/* ... */
var data = $(this)[0].files;
$.each(data, function(index, file){
var fRead = new FileReader();
fRead.onload = (function(file){
return function(e) {
$('.append').append('<img src="'+e.target.result+'" class="drop-file" />');
};
})(file);
fRead.readAsDataURL(file);
});
/* ... */
}).each('.drop-file',function(i) {
$(this).delay(1000*i).fadeIn(1850);
});
.drop-file{
width:100px;
display:block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="file" class="drop-input" multiple />
<div class="append"></div>
There are some problems in your code, the most important of which is the TypeError caused by the following piece of code, which essentially makes the code break completely:
}).each('.drop-file',function(i) {
$(this).delay(1000*i).fadeIn(1850);
});
What you're trying to do with the above piece of code can be easily integrated inside the event handler you set for the load event. You only need to do the following:
Create a counter outside the loop that will be incremented by each loaded file.
Set a style with display: none to the created image, because when created, it's already visible due to the CSS rule of the drop-file class.
Snippet:
/* ----- JavaScript ----- */
$('.drop-input').on('change', function() {
/* Create a counter to use for the delay. */
var counter = 0;
/* Iterate over every file selected. */
$.each(this.files, function(index, file) {
/* Create a new file reader. */
var fRead = new FileReader();
/* Set the 'load' event. */
fRead.onload = (function(file) {
return function(e) {
/* Create a new image and append it to the '.append' div. */
var img = $("<img>", {
"src": e.target.result,
"class": "drop-file",
"style": "display: none"
}).appendTo(".append");
/* Use the counter to fade the image in. */
img.delay(1000 * counter++).fadeIn(1850);
};
})(file);
/* Read the file as a data url. */
fRead.readAsDataURL(file);
});
});
/* ----- CSS ----- */
.drop-file {
width: 100px;
display: block;
}
<!----- HTML ----->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="file" class="drop-input" multiple/>
<div class="append"></div>
How is it possible to upload a file directly without clicking file upload button(I want to click Add Widget Button which should give file upload dialog box)
I have a button declared as follows:
<button class="add-button" style="float:top;">Add Widget</button>
On clicking the button the following function is invoked
$(".add-button").on("click", function() {
// get selected color value
var color = $color_picker.val();
// build the widget, including a class for the selected color value
var $widget = $('<li>', {
'class': 'color_' + color
})
.append($('<button>', {
'class': 'delete-button',
'text':'-'
}))
.append($('<img src="abc.png" height="60px" width="60px">'));
// add widget to the grid
gridster.add_widget($widget, 1, 1);
});
But I first want a upload box to appear where in user can upload the image as soon as the button is clicked then the above code should get executed
I did something like this
$(".add-button").on("click", function() {
var x = document.createElement("INPUT");
x.setAttribute("type", "file");
x.setAttribute("onclick","previewFile()");
// get selected color value
var color = $color_picker.val();
// build the widget, including a class for the selected color value
var $widget = $('<li>', {
'class': 'color_' + color
})
.append($('<button>', {
'class': 'delete-button',
'text':'-'
}))
.append($('<img src="abc.png" height="60px" width="60px">'));
// add widget to the grid
gridster.add_widget($widget, 1, 1);
});
But this does not brings any dialog box where user can upload the image
This uploaded image I want to then use in the place of
.append($('uploaded image'));
Preview File Function (This also needs to be modified)
function previewFile() {
var preview = document.createElement('img');
var file = document.querySelector('input[type=file]').files[0];
var reader = new FileReader(); //API for reading file stored on user computer
reader.addEventListener("load", function () { //"load" :This eventlisterner "listens" loading of file. Once it is loaded function is triggered
preview.src = reader.result;
});
if (file) {
reader.readAsDataURL(file); // helps in reading the content of "file"
}
document.body.appendChild(preview);
}
My aim is that preview file function should return an image which I can put in
.append($('image from preview file'));
A version of code is at Fiddle
The way to do this is to have some hidden input with file type somewhere on the dom. You might be able to programatically put it there, but really no point in that. Once the add widget button is clicked, you can simulate a click for the hidden input. This will initiate a prompt to pick a file. Then what you want to do is, wait until the file has been "picked" by the user. This is done via the onchange event. Within that, you can grab the file, read it, and then when it's done you can have a callback via the onload method. I put up a working example here. I imagine you wanted to have the file picked as the image set on the src, so I did that as well.
hidden input
<input id="test" type="file" style="position: absolute; top: -10; left: -10; height: 0; width: 0;" />
button click function (this is what will wait until the file has been chosen), the call back method is used on the onload event of the filereader.
$(".add-button").on("click", function() {
$('#test').click();
$('#test').on('change', function(e) {
var test = document.getElementById('test');
if (!test) {
alert("Um, couldn't find the fileinput element.");
}
else if (!test.files) {
alert("This browser doesn't seem to support the `files` property of file inputs.");
}
else if (!test.files[0]) {
alert("Please select a file before clicking 'Load'");
}
else {
file = test.files[0];
console.log(file);
fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = function() {
var data = fr.result; // data <-- in this var you have the file data in Base64 format
callbackAddButton(data);
test.value = ''; //here we are resetting the file input's files
$('#test').replaceWith($('#test').clone()); //here we are resetting the input, if we clone the input with the files then it wont trigger the onchange event if you upload same image. So we need to make sure we have these 2 calls.
};
}
})
});
And finally, the callback method. Which is simply exactly what you had before but now only gets called once the file has been done (done as in, you read it via filereader and have access to contents if needed). The only difference here is now you have a base64 representation of the image the user uploaded. Which I am setting to the new image widget you created.
function callbackAddButton(file) {
// get selected color value
var color = $color_picker.val();
// build the widget, including a class for the selected color value
var $widget = $('<li>', {
'class': 'color_' + color
})
.append($('<button>', {
'class': 'delete-button',
'text':'-'
}))
.append($(`<img src="${file}" height="60px" width="60px">`));
// add widget to the grid
gridster.add_widget($widget, 1, 1);
}
EDIT
Once you're done with the input file element, it's good practice to now clear it because you dont need the file anymore (from the input at least, since you have a base64 representation). Just append a $('#test').replaceWith($('#test').clone()) after the you make the callback call.
I have sth like drawing app. User can save projects and then load them. When I load first time one file (for e.g. project1.leds) make some changes in the app but no saving it and then again load same file (project1.leds) nothing happen. I cant load same file more than once. If I load another file, it's working.
Code:
$("#menu-open-file").change(function(e){
var data=[];
var file = null;
file = e.target.files[0];
console.log(file)
var reader = new FileReader();
reader.onload = function(e){
data=JSON.parse(reader.result);
x=data[0].SIZE[0];
y=data[0].SIZE[1];
if(x==15) x=16;
if(x==30) x=32;
if(x==60) x=64;
if(y==15) y=16;
if(y==30) y=32;
if(y==60) y=64;
createLeds(x,y,data,false,false);
clearActiveTools();
var svg = $('#contener').find('svg')[0];
svg.setAttribute('viewBox','0 0 ' + x*20 + ' ' + y*20);
$("#contener").css("width",x*20).css("height",y*20);
$("#contener").resizable({
aspectRatio: x/y,
minHeight: 200,
minWidth: 200,
});
wiFirst = $("#contener").width();
hiFirst = $("#contener").height();
}
reader.readAsText(file);
});
Can i delete/remove cached file? Is it even cached in browser?
It is because you're calling the function onchange. If you upload the same file the value of the file input has not changed from the previous upload and therefore isn't triggered. This also explains why it works if you upload a different file. No need to clear cache, you can work around this by resetting the input field's value after you read the file.
$("#menu-open-file").change(function(e){
var data=[];
var file = null;
file = e.target.files[0];
if(file !== ''){
console.log(file)
var reader = new FileReader();
reader.onload = function(e){
data=JSON.parse(reader.result);
x=data[0].SIZE[0];
y=data[0].SIZE[1];
if(x==15) x=16;
if(x==30) x=32;
if(x==60) x=64;
if(y==15) y=16;
if(y==30) y=32;
if(y==60) y=64;
createLeds(x,y,data,false,false);
clearActiveTools();
var svg = $('#contener').find('svg')[0];
svg.setAttribute('viewBox','0 0 ' + x*20 + ' ' + y*20);
$("#contener").css("width",x*20).css("height",y*20);
$("#contener").resizable({
aspectRatio: x/y,
minHeight: 200,
minWidth: 200,
});
wiFirst = $("#contener").width();
hiFirst = $("#contener").height();
}
reader.readAsText(file);
$("#menu-open-file")[0].value = '';
}
});
because input is caching the same file value so when you load the same file again it uses the cache to read value property. all you need to do is set a conditional statement when you use input element and set the value property of input to an empty string and it should work
input.value = "";
and if you are using an event handler then
e.target.value = "";
Try the below code, It should work. While clicking the upload button clear the existing value.
$("#menu-open-file").click(function(e){
$('#menu-open-file').val('');
}
The only problem with the above answer is that your HTML will no longer display the file name after the upload. Instead, it will continue to say "No file chosen", which might be confusing for users.
To get around this, you can hide the input and replace it with a label that replicates the display of the input, like so:
HTML:
<input type="file" id="myFileInput" />
<label id="myFileLabel" for="myFileInput">Choose file</label><span id="myFileName">No file chosen</span>
CSS:
#myFileInput {
display: none;
}
#myFileLabel {
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-top: 5px;
padding-left: 6px;
padding-right: 6px;
}
#myFileName {
margin-left: 5px;
}
JavaScript:
var file = null
file = e.target.files[0];
//variable to get the name of the uploaded file
var fileName = file.name;
//replace "No file chosen" with the new file name
$('#myFileName').html(fileName);
well explained article how to do this here: https://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/
In Mozilla, everything was OK. But in Chrome, I faced the same problem:reader.onload was not uploading the same file a second time on the change event.
I was using pure javascript, and here is my solution!
HTML:
<input type="file" accept="image/*" name="image" id="file-input"/>
Javascript:
const fileInput = document.getElementById("file-input");
// My EventListener with change event and my_function
fileInput.addEventListener("change", my_function);
// reader.onload was used in my_function
//################### THIS IS THE SOLUTION ###################
// Add this EventListener to fix the problem with chrome
fileInput.addEventListener("click", function() {
fileInput.value = "";
});
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have a HTML form where the user can upload an image in the input field and it works fine but the image is uploaded when the form is submitted.
Is there a way where I can get the image to upload to the TMP directory when the file is chosen and the user is still filling out the form then when the user submits the form it can be moved to the actual file directory. This would make for a better user experience and especially people with slow internet connections would benefit from this as it would utilise the users time for effectively and efficiently.
I am not sure exactly but I think this would need some sort of jQuery/Ajax solution to upload the image mid form entry then use PHP to transfer the file from the TMP to the actual directory.
As Diodeus suggested, putting the form in an iframe would prevent it from posting in the current page frame and allow users to work on other form items. A solution more to what you were expecting would be using an AJAX request. You could look into the HTML5 API, there are many different already-built solutions and many tutorials.
Here's a simple example taken from this demo at html5demos.com
<title>Drag and drop, automatic upload</title>
<style>
#holder { border: 10px dashed #ccc; width: 300px; min-height: 300px; margin: 20px auto;}
#holder.hover { border: 10px dashed #0c0; }
#holder img { display: block; margin: 10px auto; }
#holder p { margin: 10px; font-size: 14px; }
progress { width: 100%; }
progress:after { content: '%'; }
.fail { background: #c00; padding: 2px; color: #fff; }
.hidden { display: none !important;}
</style>
<article>
<div id="holder">
</div>
<p id="upload" class="hidden"><label>Drag & drop not supported, but you can still upload via this input field:<br><input type="file"></label></p>
<p id="filereader">File API & FileReader API not supported</p>
<p id="formdata">XHR2's FormData is not supported</p>
<p id="progress">XHR2's upload progress isn't supported</p>
<p>Upload progress: <progress id="uploadprogress" min="0" max="100" value="0">0</progress></p>
<p>Drag an image from your desktop on to the drop zone above to see the browser both render the preview, but also upload automatically to this server.</p>
</article>
<script>
var holder = document.getElementById('holder'),
tests = {
filereader: typeof FileReader != 'undefined',
dnd: 'draggable' in document.createElement('span'),
formdata: !!window.FormData,
progress: "upload" in new XMLHttpRequest
},
support = {
filereader: document.getElementById('filereader'),
formdata: document.getElementById('formdata'),
progress: document.getElementById('progress')
},
acceptedTypes = {
'image/png': true,
'image/jpeg': true,
'image/gif': true
},
progress = document.getElementById('uploadprogress'),
fileupload = document.getElementById('upload');
"filereader formdata progress".split(' ').forEach(function (api) {
if (tests[api] === false) {
support[api].className = 'fail';
} else {
// FFS. I could have done el.hidden = true, but IE doesn't support
// hidden, so I tried to create a polyfill that would extend the
// Element.prototype, but then IE10 doesn't even give me access
// to the Element object. Brilliant.
support[api].className = 'hidden';
}
});
function previewfile(file) {
if (tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader();
reader.onload = function (event) {
var image = new Image();
image.src = event.target.result;
image.width = 250; // a fake resize
holder.appendChild(image);
};
reader.readAsDataURL(file);
} else {
holder.innerHTML += '<p>Uploaded ' + file.name + ' ' + (file.size ? (file.size/1024|0) + 'K' : '');
console.log(file);
}
}
function readfiles(files) {
debugger;
var formData = tests.formdata ? new FormData() : null;
for (var i = 0; i < files.length; i++) {
if (tests.formdata) formData.append('file', files[i]);
previewfile(files[i]);
}
// now post a new XHR request
if (tests.formdata) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/devnull.php');
xhr.onload = function() {
progress.value = progress.innerHTML = 100;
};
if (tests.progress) {
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
var complete = (event.loaded / event.total * 100 | 0);
progress.value = progress.innerHTML = complete;
}
}
}
xhr.send(formData);
}
}
if (tests.dnd) {
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragend = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
this.className = '';
e.preventDefault();
readfiles(e.dataTransfer.files);
}
} else {
fileupload.className = 'hidden';
fileupload.querySelector('input').onchange = function () {
readfiles(this.files);
};
}
</script>
This creates a zone to drop a file (instead of a browse button) and initiate the file upload when the drag and drop event occurs. It will do it asynchronously and allow the page contents to be interacted with as normal while the transfer proceeds in the background. There is an important thing in this example to change, however. This line:
xhr.open('POST', '/devnull.php');
Should be changed to a code file in your environment/server that will process the file upload data and save or process the file however you need. This script merely acts as a front-end to that script. Another thing to remember is the HTML5 File API is still a modern-browser-only type of thing; it's well supported in current browsers, but older ones are out of luck. If you need to have them supported, you should look for another solution.