I have built a component that lets a user upload a file by either clicking on it, or dropping a file directly on it. Most of the time this works just fine, but I have noticed that it causes Google Chrome (Version 94.0.4606.81) to frequently freeze up completely, instead of opening the file picker. It will still let me press Ctrl+W to close the tab, but other tabs will also remain unresponsive to mouse clicks. I am forced to close the entire browser. I could not reproduce this in Microsoft Edge however.
const fileInput = document.querySelector("#fileInput");
const dropArea = document.querySelector("#dropArea");
const upload = document.querySelector("#upload");
function onDragBegin(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "copy";
dropArea.classList.add("dropArea-highlight");
upload.classList.add("upload-highlight");
}
function onDragEnd(e) {
e.preventDefault();
dropArea.classList.remove("dropArea-highlight");
upload.classList.remove("upload-highlight");
}
function onDrop(e) {
fileInput.files = e.dataTransfer.files;
fileInput.dispatchEvent(new Event("change"));
e.preventDefault();
}
async function onChangeFileInput() {
const { files } = this;
dropArea.classList.remove("dropArea-highlight");
upload.classList.remove("upload-highlight");
const url = "/api/har";
const formData = new FormData();
formData.append("harFile", files[0]);
fetch(url, {
method: "POST",
body: formData,
}).then(async (res) => {
if (!res.ok) {
const text = await res.text();
alert(text);
}
if (res.redirected) {
window.location.href = res.url;
}
});
}
dropArea.ondragover = onDragBegin;
dropArea.ondragenter = onDragBegin;
dropArea.ondragleave = onDragEnd;
dropArea.ondrop = onDrop;
fileInput.addEventListener("change", onChangeFileInput);
.upload {
border: 1px dashed var(--copytext-grey);
background-color: #e7eaef;
}
.upload:hover {
background-color: #e1e4ea;
border-style: dotted;
}
.upload-icon {
height: 64px;
filter: invert(36%) sepia(13%) saturate(381%) hue-rotate(171deg)
brightness(93%) contrast(91%);
margin-bottom: 16px;
}
#dropArea {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 1rem;
}
#dropArea:hover {
cursor: pointer;
}
#dropArea h3 {
color: #555d66;
font-weight: bold;
}
#dropArea p {
color: #676f77;
}
#fileInput {
display: none;
}
.upload-highlight {
background-color: #e1e4ea;
border-style: dotted;
}
.dropArea-highlight {
cursor: pointer;
}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<script src="upload.js" defer></script>
</head>
<body>
<div class="upload" id="upload">
<label for="fileInput">
<div id="dropArea">
<img src="upload.svg" class="upload-icon">
<h3>Drag and drop or click</h3>
<p>to upload your file</p>
<input type="file" name="harFile" id="fileInput" accept=".har">
</div>
</label>
</div>
</body>
</html>
I have already made a bugreport, but in the meantime I need to find a fix to stop the browser from freezing up. How?
Given this is not your code that's faulty, it will be hard to workaround that issue. (my guess would be some badly registered file the OS has troubles getting the metadata from, or some network drives (dropbox, icloud) that aren't connecting correctly).
The only workaround I can think of would be the new File System Access API, which hopefully would not trigger Chrome's issue (I think they shouldn't need the same metadata). But I am absolutely not sure this will do either so please let me know since I can't repro that issue myself.
Also, this API is currently only supported in Chromium based browsers, so you'd still need an <input type=file> fallback for other browsers.
Live demo as a glitch since this API isn't available in sandboxed iframes.
// we need to be handling a click event
document.querySelector("button").onclick = async () => {
if( !("showOpenFilePicker" in self) ) {
console.error("unsupported browser, should fallback to <input type=file>");
return;
}
const handles = await showOpenFilePicker();
const file = await handles[0].getFile();
// now you can handle the file as usual
document.getElementById( "log" ).textContent = await file.text();
};
Related
This question already has answers here:
Wait for forEach with a promise inside to finish
(2 answers)
Closed 4 months ago.
Tried to get my head around this problem, but still couldn't(I feel like ELI5 would be appropriate approach for me when talking about Promises). I removed readMultipleFiles-function promise. Put Promise.all and tried async await, but the returned array is still empty. I'm completely lost and I would appreciate very much if you could point me my error(s) and maybe suggest some good YT lessons about these :/
Edited code and added all the required JS and HTML+CSS to create dropzone:
// Function to handle files user drag&drop to HTML page
const handleDrop = async (e) => {
console.log('1.0 handleDrop function starts... ');
// files are filelist-type
const dt = e.dataTransfer;
const files = dt.files;
let fileArray = [...files];
console.log('2. Starting to read files.');
let filesRead = await readMultipleFiles(fileArray);
// These gives still undefined
console.log('filesRead[1]: ',filesRead[1]);
console.log('filesRead: ',filesRead);
}
// Function to read multiple files in array. This should return array of files
const readMultipleFiles = async (fileArray) => {
console.log('\t2.1 readMultipleFiles working...');
return Promise.all(fileArray.map(f => {readFile(f)}));
}
// Function to read single file. This should return single file object:
// {filename: <somefilename>, content:<sometextcontent}
const readFile = (file) => {
console.log('\t2.2 readFile working...');
let myPromise = new Promise((resolve) => {
let singleFile;
// console.log('\t\t2.2.1 Reading single file...');
const reader = new FileReader();
reader.onload = (e) => {
// console.log('\t\t\t2.2.1 readFile -> file: ',e.target.result, 'typeof file:', typeof e.target.result);
fileContent = e.target.result;
fileName = file.name;
singleFile = {
'filename':file.name,
'content':e.target.result
};
// console.log('\t2.2.1 readFile --> fileRead: ', singleFile);
}
reader.readAsText(file);
resolve(singleFile);
})
return myPromise.then(res => {
// console.log('2.2.1.1 readfile -> myPromise.then res: ', res);
return res;
});
}
const initApp = () => {
const droparea = document.querySelector('.droparea');
const active = () => droparea.classList.add('green-border');
const inactive = () => droparea.classList.remove('green-border');
const prevents = (e) => e.preventDefault();
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evtName => {
droparea.addEventListener(evtName, prevents);
});
['dragenter', 'dragover'].forEach(evtName => {
droparea.addEventListener(evtName, active);
});
['dragleave', 'drop'].forEach(evtName => {
droparea.addEventListener(evtName, inactive);
});
droparea.addEventListener('drop', handleDrop);
}
document.addEventListener("DOMContentLoaded", initApp);
HTML page for this:
<html>
<head>
<meta charset="utf-8">
<title>Page</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div>
Page
</div>
<main>
<section class="droparea">
<i class="far fa-images"></i>
<p>Drop your files here</p>
</section>
</main>
<script type="text/javascript" src="index.js"></script>
CSS:
.hide {
display: none;
}
h3 {
margin: 0 auto;
padding-top: 10px;
}
.checkboxarea {
width: 99%;
overflow: hidden;
}
.checkboxitem {
width: 45%;
float: left;
}
.ui.segment {
border: 0;
box-shadow: none;
}
#drop_zone {
border: 5px solid blue;
width: 200px;
height: 100px;
}
.droparea {
margin: 1rem auto;
display: flex;
flex-direction: column;
justify-content: left;
align-items: center;
width: 384px;
max-width: 100%;
height: 160px;
border: 4px dashed grey;
border-radius: 15px;
}
.droparea i {
font-size: 3rem;
flex-grow: 1;
padding-top: 1rem;
}
.green-border {
border-color: green;
}
.selectResultData {
padding-right: 10px;
}
/* Style the button that is used to open and close the collapsible content */
.collapsible {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .collapsible:hover {
background-color: #ccc;
}
/* Style the collapsible content. Note: hidden by default */
.content {
padding: 0 18px;
display: none;
overflow: hidden;
background-color: #f1f1f1;
}
Original question:
I've been banging my head with this kind of problem for awhile now. I'm trying to use Javascript promises and async/await to fill array with object(txt-files dropped by user to HTML page) to be used later on my code but I cannot get around problem that array objects cannot be accessed although console.log shows array has objects. Seems to me that async/await is not waiting for the Array to be filled for some reason which I don't understand. Can you please help me with this?
//Code to handle user filedrops
const handleDrop = async (e) => {
console.log('1.0 handleDrop function starts... ');
// files are filelist-type
const dt = e.dataTransfer;
const files = dt.files;
let fileArray = [...files];
// Making sure files are in fileArray and they can be accessed
console.log('1.0 handleDrop -> fileArray.length',fileArray.length)
console.log('Files: ',fileArray);
console.log('2. Starting to read files.');
let filesRead = await readMultipleFiles(fileArray);
console.log('This log row should come after files have been read and saved to variable\nFiles read! filesRead: ', filesRead);
// **************************
// Why this gives undefined?
// **************************
console.log('filesRead[1]: ',filesRead[1]);
}
//Function to read Array of files and return them back in Array
const readMultipleFiles = (fileArray) => {
console.log('\t2.1 readMultipleFiles working...');
let myPromise = new Promise((resolve) => {
let filesRead = [];
fileArray.forEach(f => {
let file;
readFile(f).then(res => {
console.log('2.1.1 file returned by readFile', res, '\nf:',f);
file=res
return res;
}).then(res => filesRead.push(file));
});
resolve(filesRead);
})
return myPromise.then(res => res);
}
// Function to read single file and return it as an object
const readFile = (file) => {
console.log('\t2.2 readFile working...');
let myPromise = new Promise((resolve) => {
let singleFile;
console.log('\t\t2.2.1 Reading single file...');
const reader = new FileReader();
reader.onload = (e) => {
console.log('\t\t\t2.2.1 readFile -> file: ',e.target.result, 'typeof file:', typeof e.target.result);
fileContent = e.target.result;
fileName = file.name;
singleFile = {
'filename':file.name,
'content':e.target.result
};
console.log('\t2.2.1 readFile --> fileRead: ', singleFile);
resolve(singleFile);
}
reader.readAsText(file);
})
return myPromise.then(res => {
console.log('2.2.1.1 readfile -> myPromise.then res: ', res);
return res;
});
}
Your function readMultipleFiles returns a promise that resolves to an empty array because resolve is called before the asynchronous then callbacks have ran, and so filesRead is still [] when resolve(filesRead) is called.
Not your original problem, but it is an anti-pattern to create a promise with new Promise when you already have promises to work with (readFile(f)). In this case you probably want to get a promise from Promise.all
Here is how you can fix readMultipleFiles:
const readMultipleFiles = (fileArray) => {
console.log('\t2.1 readMultipleFiles working...');
return Promise.all(fileArray.map(readFile))
}
For simplicity sake, I didn't bother to have this line in the above correction:
console.log('2.1.1 file returned by readFile', res, '\nf:',f);
There is already a lot of debugging output there.
soo, i am trying to use localstorage to store the classlist so it will remember whether or not something was added to their favorites list. i need to do this with JavaScript. The error provided comes from the console in my web browser (chrome)
The ERROR
main.js:99 Uncaught TypeError: Cannot read property 'value' of undefined
at storeFavo (main.js:99)
at HTMLButtonElement.favorites (main.js:71)
HTML
<!-- Showcase -->
<section class="showcase">
<div class="container grid ">
<div class="showcase-form card">
<h2>*Input Car Name*</h2>
<img alt="" src="#" >
<button id="favo" class="btn">Add to Favorites</button>
<button id="info" class="btn">More Info</button>
</div>
</div>
</section>
JavaScript
window.addEventListener('load', init);
const info = document.querySelector('#info');
const div1 = document.querySelector('.moreInfo');
const favo = document.querySelector('#favo');
let apiUrl = 'webservice/includes/actions.php';
let apiUrl2 = 'webservice/index.php';
let Favos = document.getElementById('favo').getElementsByClassName('favorited')[0];
function init() {
info.addEventListener('click', moreInfo);
favo.addEventListener('click', favorites);
if (typeof window.localStorage === "undefined") {
console.error('Local storage is not available in your browser');
return;
}
checkFromLocalStorage()
carList;
}
function carList() {
fetch(apiUrl)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then(getAjaxSuccessHandler)
.catch(getAjaxErrorHandler);
}
function getAjaxSuccessHandler(data) {
console.log(data);
}
function getAjaxErrorHandler(data) {
console.error(data);
}
// Generate Cards
// getCars.map((item)=>{
// return (
// <div>
// <p> {item.brand}</p>
// <p> {item.type}</p>
// </div>
// );
// }),
// More Info Button
function moreInfo() {
let title= document.createElement('h1');
title.innerHTML = 'Info';
div1.appendChild(title)
console.log("info?")
if (div1.style.display == 'block'){
div1.style.display = 'none';
} else {
div1.style.display = 'block';
}
}
// Add To Favorites Button
function favorites() {
console.log("favo")
if (favo.classList == 'btn') {
favo.classList.remove('btn')
favo.classList.add('favorited')
favo.innerHTML = 'Remove from favorites';
storeFavo()
}
else {
favo.classList.remove('favorited')
favo.classList.add('btn')
favo.innerHTML = 'Add to Favorites';
deleteClickHandler()
}
}
//Check
/**
* Is local storage is available on page load? Let's fill the form
*/
function checkFromLocalStorage() {
if (localStorage.getItem('favos') !== null) {
Favos.value = localStorage.getItem('favos');
}
}
//Store
/**
* After submitting the form, let's save the values in the local storage
*
* #param e
*/
function storeFavo(e) {
localStorage.setItem('favos', Favos.value);
localStorage['Favos'] = document.getElementById("favo")
}
//Delete
/**
* Make sure we clean up the local storage again
*
* #param e
*/
function deleteClickHandler(e) {
localStorage.removeItem('favos');
}
CSS
.favorited {
display: inline-block;
padding: 10px 30px;
cursor: pointer;
background: #470aed;
color: #ffffff;
border: none;
border-radius: 5px;
}
.btn {
display: inline-block;
padding: 10px 30px;
cursor: pointer;
background: var(--primary-color);
color: #ffffff;
border: none;
border-radius: 5px;
}
I will do my very best to respond fast and to provide any needed information if asked for.
Check the documentation of localStorage.setItem https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem, you can write only string values, but you are trying to write an Object value (https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName). If you want to have some persistence fo your HTML element, you can save outerHTML https://developer.mozilla.org/ru/docs/Web/API/Element/outerHTML to the storage and parse it on load.
What I am trying to achieve is when my device size is less than 736 px, the button should animate. I got the button working correctly, however, I’m struggling to work with the specific screen size.
$(window).resize(function() {
if ($(window).width() <= 736) {
// do something
let myBtn = document.querySelector(".btn");
let btnStatus = false;
myBtn.style.background = "#FF7F00";
function bgChange() {
if (btnStatus == false) {
myBtn.style.background = "#FF0000";
btnStatus = true;
}
else if (btnStatus == true) {
myBtn.style.background = "#FF7F00";
btnStatus = false;
}
}
myBtn.onclick = bgChange;
}
});
.btn {
width: 200px;
height: 100px;
text-align: center;
padding: 40px;
text-decoration: none;
font-size: 20px;
letter-spacing: .6px;
border-radius: 5px;
border: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="btn">CLICK ME</button>
Here's an implementation of what you're trying to do that uses:
class to alter button styling instead of style,
vanilla JavaScript instead of jQuery.
Using class is a good idea, as it keeps the styling in the CSS and out of the JavaScript code.
Using vanilla JavaScript whenever you can is preferable.
Here are the two new classes:
.btn-small-screen {
background: #FF7F00;
}
.btn-clicked {
background: #FF0000;
}
.btn-small-screen class is applied when the window is small, .btn-clicked is toggled whenever the button is clicked.
Here's the JavaScript code:
let myBtn = document.querySelector('.btn');
let isSmallWindow = () => window.innerWidth <= 736;
function toggleButtonOnClick () {
myBtn.classList.toggle('btn-clicked');
}
function setButtonMode () {
if (isSmallWindow()) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
} else {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
}
}
// setup mode on resize
window.addEventListener('resize', setButtonMode);
// setup mode at load
window.addEventListener('load', setButtonMode);
References:
Document.querySelector()
Window.innerWidth
Element.classList
DOMTokenList.toggle()
DOMTokenList.add()
DOMTokenList.remove()
EventTarget.addEventListener()
A working example:
let myBtn = document.querySelector('.btn');
let isSmallWindow = () => window.innerWidth <= 736;
function toggleButtonOnClick () {
myBtn.classList.toggle('btn-clicked');
}
function setButtonMode () {
if (isSmallWindow()) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
} else {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
}
}
// setup small mode on resize
window.addEventListener('resize', setButtonMode);
// setup small mode at load
window.addEventListener('load', setButtonMode);
.btn {
width: 200px;
height: 100px;
text-align: center;
padding: 40px;
text-decoration: none;
font-size: 20px;
letter-spacing: .6px;
border-radius: 5px;
border: none;
}
.btn-small-screen {
background: #FF7F00;
}
.btn-clicked {
background: #FF0000;
}
<button class="btn">CLICK ME</button>
Note: There is one optimization that I left out, so the code would be easier to follow.
Notice that setButtonMode() changes the DOM every time, even though it might already be set to the desired mode. This is inefficient.
To improve efficiency and only change the DOM when necessary, you could introduce a state variable (call it smallMode), and set it true whenever appropriate. Like so:
let smallMode = false;
function setButtonMode () {
if (isSmallWindow()) {
if (!smallMode) {
myBtn.classList.add('btn-small-screen');
myBtn.addEventListener('click', toggleButtonOnClick);
smallMode = true;
}
} else if (smallMode) {
myBtn.classList.remove('btn-small-screen');
myBtn.classList.remove('btn-clicked');
myBtn.removeEventListener('click', toggleButtonOnClick);
smallMode = false;
}
}
HTML:
<input id="browse" type="file" multiple>
<div id="preview"></div>
Javascript:
var elBrowse = document.getElementById("browse");
var elPreview = document.getElementById("preview");
function readFile(file) {
//Create downloadable link and generate DOM elements.
var fileName = file.name;
elPreview.insertAdjacentHTML("beforeend", "<a>" + fileName + '</a>');
elPreview.insertAdjacentHTML("beforeend", "<a>Delete</a><br>");
}
elBrowse.addEventListener("change", function () {
var files = this.files;
// Check for `files` (FileList) support and if contains at least one file:
if (files && files[0]) {
// Iterate over every File object in the FileList array
for (var i = 0; i < files.length; i++) {
var file = files[i];
readFile(file);
}
}
});
I am facing a new requirement, but I don't have much experience in Javascript. Basically, I want users to be able to upload files by clicking the browse button and displays the files name with downloadable link. User can upload files of any type and be able to download the files back.
The whole process MUST not trigger the backend server and everything has to be done in javascript or JQuery. Could anyone help please? Thank you.
System and User interaction:
User uploads a file
System saves the file in javascript and displays the file name with downloadable link.
User delete it.
System removes it from DOM and javascript
User can repeat step 1-4. The whole process does not trigger the server at all.
Eventually, user submits all the files to the server by clicking somewhere else (This step is out of the scope of this post)
If you are using a modern browser then you can utilize the HTML5 FileReader. I've used it, but found an example more suited to your question here.
You can use the FileReader to read the files on the client side, and store in sessionStorage, localStorage, or even a variable. But the one caveat is that you'll probably run out of RAM because JavaScript's not really designed for retaining large BLOBs in RAM.
window.onload = function() {
var fileInput = document.getElementById('fileInput');
var fileDisplayArea = document.getElementById('fileDisplayArea');
fileInput.addEventListener('change', function(e) {
var file = fileInput.files[0];
var textType = /text.*/;
if (file.type.match(textType)) {
var reader = new FileReader();
reader.onload = function(e) {
fileDisplayArea.innerText = reader.result;
}
reader.readAsText(file);
} else {
fileDisplayArea.innerText = "File not supported!"
}
});
}
html {
font-family: Helvetica, Arial, sans-serif;
font-size: 100%;
background: #333;
}
#page-wrapper {
width: 600px;
background: #FFF;
padding: 1em;
margin: 1em auto;
min-height: 300px;
border-top: 5px solid #69c773;
box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}
h1 {
margin-top: 0;
}
img {
max-width: 100%;
}
#fileDisplayArea {
margin-top: 2em;
width: 100%;
overflow-x: auto;
}
<div id="page-wrapper">
<h1>Text File Reader</h1>
<div>
Select a text file:
<input type="file" id="fileInput">
</div>
<pre id="fileDisplayArea"><pre>
</div>
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.