I'm working on an application (in Node.js, which is irrelevant for this case) which allows the user to upload an image. It works fine using a form with an input (type="file") field.
However, what I want is to be able to upload an image using HTML5 drag and drop instead. As far as i've come it's possible to drag an image to the client, and the image thumbnail is displayed in a div. However I really need some help with getting the file upload working.
The thing is that I want to use the form that i'm using right now, and (somehow) pass the file's path to the input field, i.e. the flow will work exactly as it do now, but instead of choosing a file by browsing it I want to attach it to the input field by drag and drop.
In the js code below for drag and drop the file that was dragged to the client is stored in the variable "file", and i'm able to use "file.name", "file.type" and "file.size" exactly the same way as it works since before with the form. However, I can't access the files "path" (file.path) which makes it impossible to access the file server side for uploading the same way as I do it since before.
The question is, is it possible to pass the file object to the input field after the file has been dragged to the client, so that I can click on "submit" and upload the file? If so, how could this be done?
Thanks in advance!
the dropbox as well as the form i'm using for file uploads:
<div id='upload'>
<article>
<div id='holder'>
<p id='status'>File API and FileReader API not supported</p>
</div>
</article>
<form method='post' enctype='multipart/form-data' action='/file-upload'>
<p>
<input type='file' name='thumbnail'>
</p>
<p>
<input type='submit'>
</p>
</form>
</div>
the code for drag and drop:
uploadImage: function(){
var holder = document.getElementById('holder'),
state = document.getElementById('status');
if (typeof window.FileReader === 'undefined') {
state.className = 'fail';
} else {
state.className = 'success';
state.innerHTML = 'File API & FileReader available';
}
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragend = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
this.className = '';
e.preventDefault();
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function (event) {
holder.style.background = 'url(' + event.target.result + ') no-repeat center';
};
reader.readAsDataURL(file);
return false;
};
},
You cannot use the file input to add the file data.
Nevertheless, what you can do (among other technics) is to use the base64 (natively available through the reader.onload event as event.target.result, when using readAsDataURL method) encoded data and put it into an hidden field :
html
<article>
<div id='holder'>
<p id='status'>File API and FileReader API not supported</p>
</div>
</article>
<form method='post' enctype='multipart/form-data' action='/file-upload'>
<input type='file' name='thumbnail' />
<input type='hidden' name='base64data' />
<input type='submit' formenctype='application/x-www-form-urlencoded' />
</form>
js
reader = new FileReader();
reader.onload = function (event) {
document.getElementById('base64data').setAttribute('value', event.target.result);
};
reader.readAsDataURL(file);
From the server side you'll be able to get the base64 encoded data from the file, just decode it and use it as you want.
While submitting the form, you could also change the "enctype" attribute (done through the formenctype attribute) and remove the basic html file input, since the data will be post in a text field.
It is impossible to know the path of the field for security purposes. With drag and drop you must have it upload independently of the main form. Look here for an example: http://www.sitepoint.com/html5-file-drag-and-drop/
I find that the hidden field set in reader.onload (see answer by #challet) is not set when acccessed in code behind. I am using asp.net and a WebForms project. To access the hidden fields I have to prepend MainContent_ to the field names. aspx code is below
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
...
<script type="text/javascript">
function dropHandler(ev) {
alert("File(s) dropped");
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
//alert("Default prevented");
if (ev.dataTransfer.items) {
if (ev.dataTransfer.items.length > 1) {
alert("Only single files can be dragged and dropped into Caption Pro Web");
return;
}
// If dropped items aren't files, reject them
if (ev.dataTransfer.items[0].kind === 'file') {
var file = ev.dataTransfer.items[0].getAsFile();
document.getElementById("MainContent_DroppedFileName").value = ev.dataTransfer.items[0].name
reader = new FileReader();
reader.onload = function (event) {
document.getElementById('MainContent_DroppedFileContent').value = event.target.result;
};
reader.readAsDataURL(ev.dataTransfer.items[0]);
}
} else {
// Use DataTransfer interface to access the file(s)
if (ev.dataTransfer.files.length > 1) {
alert("Only single files can be dragged and dropped into Caption Pro Web");
return;
}
document.getElementById("MainContent_DroppedFileName").value = ev.dataTransfer.files[0].name
document.getElementById("MainContent_DroppedFileContent").value = "Test";
reader = new FileReader();
reader.onload = function (event) {
document.getElementById("MainContent_DroppedFileContent").value = event.target.result;
};
reader.readAsDataURL(ev.dataTransfer.files[0]);
}
document.getElementById('<%=btnDrop.ClientID %>').click();
}
</script>
...
<div id="drop_zone" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);">
<p>Drag image to this Drop Zone ...</p>
</div>
<asp:HiddenField ID="DroppedFileName" runat="server" />
<asp:HiddenField ID="DroppedFileContent" runat="server" />
...
</asp:Content>
I access the hidden fields from c# as shown below
protected void btnDrop_Click(object sender, EventArgs e)
{
string FileName = DroppedFileName.Value;
string FileContent = DroppedFileContent.Value;
}
If I use Internet Explorer as the target browser (not running VS as Admin as this disables drag/drop!) and set a breakpoint in the reader.onload() function the hidden field DroppedFileContent contains the encoded file content, but when I try to access it from btnDrop_Click it only contains "Test" as set before reader.onload() and does not contain the encoded file content. The field DroppedFileNam.Value is as set in the Javascript.
Related
I'm developing an app in Python/Flask that lets a user upload their own image to display as their avatar. I want to let the user preview the image in their browser before submitting the form. I researched this on MDN and in the Flask documentation, and I've got the submit working fine without the preview, but when I add the preview feature (via client-side Javascript) the POST request fails because the form's file element contains no files!
Code snippets below show my test setup. When I use the regular.html form (no client-side preview) the upload works. The file is saved on the server in the designated directory, etc., and the Flask console outputs:
File list: [<FileStorage: 'fulano.jpg' ('image/jpeg')>]
Filename is fulano.jpg.
When I upload the same file using the "preview.html" it previews fine in the browser, but upon submit, it outputs:
File list: []
No file found
So I'm wondering if the request.files multidict is not persisting outside the client-side JS function that simulates the click on the hidden file input? And if so, how can I return that multidict from the function and use it in the form submit?
"Regular" form:
<h1>Regular File Upload</h1>
<form method="POST" action="/regular" enctype="multipart/form-data">
<p><input type="file" name="file" accept="image/*"></p>
<p><input type="submit" value="Submit"></p>
</form>
"Preview" form:
<body>
<h1>File Upload with Preview</h1>
<form method="POST" action="/preview" enctype="multipart/form-data">
<input type="file" id="fileElem" accept="image/*" style="display:none">
<div id="thumbnailDiv" style="max-height: 200px;">
<img alt="your profile image" src="/static/avatars/avatar.png" style="height: 60px">
</div>
<p>Your name goes here</p>
Upload an image file
<button type="submit">Submit</button>
</form>
Return to home page
<script src="/static/preview.js"></script>
</body>
Preview.js:
const fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
thumbnailDiv = document.getElementById("thumbnailDiv");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
fileElem.addEventListener("change", handleFiles, false);
function handleFiles() {
if (this.files.length) {
thumbnailDiv.innerHTML = "";
const img = document.createElement("img");
img.src = URL.createObjectURL(this.files[0]);
img.height = 60;
img.onload = function() {
URL.revokeObjectURL(this.src); //even without this function, the upload fails
};
thumbnailDiv.appendChild(img);
}
}
And an excerpt from the application.py file (this code block is identical for both the regular and preview routes):
if request.method == "POST":
print (f"File list: {request.files.getlist('file')}")
if "file" not in request.files:
print ("No file found")
return redirect ("/")
uploaded_file = request.files['file']
filename = uploaded_file.filename
print(f"Filename is {filename}.")
if filename != '':
file_ext = os.path.splitext(filename)[1]
if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \
file_ext != validate_image(uploaded_file.stream):
abort(400)
uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], "123456" + file_ext)) #this will ultimately be the userID plus file_ext
return redirect("/")
I have this code and for a file to be converted into base64 I have to click on Choose file and then select it. I want to hardcode the file name so it is converted to base64 on page load.
JavaScript:
var handleFileSelect = function(evt) {
var files = evt.target.files;
var file = files[0];
if (files && file) {
var reader = new FileReader();
reader.onload = function(readerEvt) {
var binaryString = readerEvt.target.result;
document.getElementById("base64textarea").value = btoa(binaryString);
};
reader.readAsBinaryString(file);
}
if (window.File && window.FileReader
&& window.FileList && window.Blob) {
document.getElementById('filePicker')
.addEventListener('change', handleFileSelect, false);
} else {
alert('The File APIs are not fully supported in this browser.');
}
};
HTML:
<div>
<div>
<label for="filePicker">Choose or drag a file:</label><br/>
<input type="file" id="filePicker">
</div>
</br>
<div>
<h1>Base64 encoded version</h1>
<textarea id="base64textarea"
placeholder="Base64 will appear here"
cols="50" rows="15">
</textarea>
</div>
</div>
EDIT: Thank you for your answers, they were really helpful.
You simply can't do what you are trying to do. Setting the path for an input element through Javascript is not possible, as a security measure. Please check here: How to resolve the C:\fakepath?
You can launch chromium, chrome browser with --allow-file-access-from-files flag set, use fetch() of XMLHttpRequest() to request file from local filesystem.
fetch("file:///path/to/file")
.then(response => response.arrayBuffer())
.then(ab => {
// do stuff with `ArrayBuffer` representation of file
})
.catch(err => console.log(err));
See also Read local XML with JS
The File API is not good to read local files without user intervention, but the Web API is (of course, within its limitations, like not working in Chromium without explicitly enabling access to local files and whatnot).
So, here it is, in case someone else needs a working example of how to load a local file without user intervention, i.e., without requiring user to push any INPUT button (but still giving the user a means to abort the loading).
Parameters: file name, request type (text, blob etc.), MIME type and a function to be executed after the file is completely loaded. File is loaded in variable X, which is then used to populated an object.
To abort the file reading, just click on the progress bar (also, just an example, not essential for the program to work). Because it is asynchronous, as many files as wanted may be read at the same time (one progress bar is created for each file).
I only created examples for a text file and a video, but it should work with any kind of files.
<html>
<head>
<meta charset="utf-8"/>
<script type="text/javascript">
function LoadFile(FileName,RespType,FileType,RunMe)
{ var AJAXFileReader=new XMLHttpRequest();
// Creates new progress bar.
var ProgressBar=CreateSVGProgBar("ProgressBars");
AJAXFileReader.addEventListener("progress",
function FRProgress(AJAXFREvt)
{ // Calculate progress.
var X=-1;
if (AJAXFREvt.lengthComputable)
X=Math.trunc(AJAXFREvt.loaded/AJAXFREvt.total*100);
ShowProgressBar(ProgressBar,FileName,X);
});
AJAXFileReader.addEventListener("error",function FRFailed()
{ // This will be executed if an error occurs.
console.log("Error:",this.status);
});
AJAXFileReader.addEventListener("timeout",function FRTimeOut()
{ // This will be executed if the reading times out.
console.log("File reading timed out!");
});
AJAXFileReader.addEventListener("abort",
function FRCancel()
{ // This will confirm reading was aborted.
console.log("File reading cancelled by user!");
});
ProgressBar.addEventListener("click",
function KillMe()
{ // Adds an abort command to the object.
console.log(AJAXFileReader.readyState);
if (AJAXFileReader.readyState!=4)
{ console.log("Aborting file reading...");
AJAXFileReader.abort();
}
ProgressBar.removeEventListener("click",KillMe);
},
false);
AJAXFileReader.addEventListener("load",
function Finished()
{ // When reading is finished, send data to external function.
if ((this.readyState==4)&&(this.status==200))
{ ShowProgressBar(ProgressBar,FileName,100);
RunMe(this.response);
//ProgressBar.click();
}
},
false);
AJAXFileReader.open("GET",FileName,true);
AJAXFileReader.overrideMimeType(FileType);
AJAXFileReader.responseType=RespType;
AJAXFileReader.timeout=10000; // Setting time-out to 10 s.
AJAXFileReader.send();
}
function CreateSVGProgBar(AnchorId)
{ // Creates new SVG progress bar.
var Parent=document.getElementById(AnchorId);
var NewSVG=document.createElementNS("http://www.w3.org/2000/svg","svg");
NewSVG.setAttribute("viewBox","0 0 102 22");
NewSVG.setAttribute("width","102");
NewSVG.setAttribute("height","22");
Parent.appendChild(NewSVG);
return NewSVG;
}
function ShowProgressBar(E,N,X)
{ // Show progress bar.
var P=X<0?"???":X+"%";
E.innerHTML="<text x=\"50\" y=\"16\" font-size=\"12\" fill=\"black\" stroke=\"black\" text-anchor=\"middle\">"+N+"</text><rect x=\"1\" y=\"1\" width=\""+X+"\" height=\"20\" fill=\""+(X<100?"#FF0000":"#0000FF")+"\" stroke=\"none\"/><rect x=\"1\" y=\"1\" width=\"100\" height=\"20\" fill=\"none\" stroke=\"black\" stroke-width=\"1\"/><text x=\""+X/2+"\" y=\"16\" font-size=\"12\" fill=\"black\" stroke=\"black\" text-anchor=\"middle\">"+P+"</text>";
}
function TracerOn(X)
{ // This will be executed after the file is completely loaded.
document.getElementById("Tron").innerHTML=X;
}
function PlayIt(X)
{ // This will be executed after the file is completely loaded.
var blob_uri=URL.createObjectURL(X);
document.getElementById("MagicalBox").appendChild(document.createElement("source")).src=blob_uri;
}
function Startup()
{ // Run after the Page is loaded.
LoadFile("example.txt","text","text/plain;charset=utf-8",TracerOn);
LoadFile("video.mp4","blob","video/mp4",PlayIt);
}
</script>
</head>
<body onload="Startup()">
<div id="ProgressBars"></div>
<div id="Tron">Text...</div>
<video id="MagicalBox" width="400" controls>Video...</video>
</body>
</html>
I have this code and for a file to be converted into base64 I have to click on Choose file and then select it. I want to hardcode the file name so it is converted to base64 on page load.
JavaScript:
var handleFileSelect = function(evt) {
var files = evt.target.files;
var file = files[0];
if (files && file) {
var reader = new FileReader();
reader.onload = function(readerEvt) {
var binaryString = readerEvt.target.result;
document.getElementById("base64textarea").value = btoa(binaryString);
};
reader.readAsBinaryString(file);
}
if (window.File && window.FileReader
&& window.FileList && window.Blob) {
document.getElementById('filePicker')
.addEventListener('change', handleFileSelect, false);
} else {
alert('The File APIs are not fully supported in this browser.');
}
};
HTML:
<div>
<div>
<label for="filePicker">Choose or drag a file:</label><br/>
<input type="file" id="filePicker">
</div>
</br>
<div>
<h1>Base64 encoded version</h1>
<textarea id="base64textarea"
placeholder="Base64 will appear here"
cols="50" rows="15">
</textarea>
</div>
</div>
EDIT: Thank you for your answers, they were really helpful.
You simply can't do what you are trying to do. Setting the path for an input element through Javascript is not possible, as a security measure. Please check here: How to resolve the C:\fakepath?
You can launch chromium, chrome browser with --allow-file-access-from-files flag set, use fetch() of XMLHttpRequest() to request file from local filesystem.
fetch("file:///path/to/file")
.then(response => response.arrayBuffer())
.then(ab => {
// do stuff with `ArrayBuffer` representation of file
})
.catch(err => console.log(err));
See also Read local XML with JS
The File API is not good to read local files without user intervention, but the Web API is (of course, within its limitations, like not working in Chromium without explicitly enabling access to local files and whatnot).
So, here it is, in case someone else needs a working example of how to load a local file without user intervention, i.e., without requiring user to push any INPUT button (but still giving the user a means to abort the loading).
Parameters: file name, request type (text, blob etc.), MIME type and a function to be executed after the file is completely loaded. File is loaded in variable X, which is then used to populated an object.
To abort the file reading, just click on the progress bar (also, just an example, not essential for the program to work). Because it is asynchronous, as many files as wanted may be read at the same time (one progress bar is created for each file).
I only created examples for a text file and a video, but it should work with any kind of files.
<html>
<head>
<meta charset="utf-8"/>
<script type="text/javascript">
function LoadFile(FileName,RespType,FileType,RunMe)
{ var AJAXFileReader=new XMLHttpRequest();
// Creates new progress bar.
var ProgressBar=CreateSVGProgBar("ProgressBars");
AJAXFileReader.addEventListener("progress",
function FRProgress(AJAXFREvt)
{ // Calculate progress.
var X=-1;
if (AJAXFREvt.lengthComputable)
X=Math.trunc(AJAXFREvt.loaded/AJAXFREvt.total*100);
ShowProgressBar(ProgressBar,FileName,X);
});
AJAXFileReader.addEventListener("error",function FRFailed()
{ // This will be executed if an error occurs.
console.log("Error:",this.status);
});
AJAXFileReader.addEventListener("timeout",function FRTimeOut()
{ // This will be executed if the reading times out.
console.log("File reading timed out!");
});
AJAXFileReader.addEventListener("abort",
function FRCancel()
{ // This will confirm reading was aborted.
console.log("File reading cancelled by user!");
});
ProgressBar.addEventListener("click",
function KillMe()
{ // Adds an abort command to the object.
console.log(AJAXFileReader.readyState);
if (AJAXFileReader.readyState!=4)
{ console.log("Aborting file reading...");
AJAXFileReader.abort();
}
ProgressBar.removeEventListener("click",KillMe);
},
false);
AJAXFileReader.addEventListener("load",
function Finished()
{ // When reading is finished, send data to external function.
if ((this.readyState==4)&&(this.status==200))
{ ShowProgressBar(ProgressBar,FileName,100);
RunMe(this.response);
//ProgressBar.click();
}
},
false);
AJAXFileReader.open("GET",FileName,true);
AJAXFileReader.overrideMimeType(FileType);
AJAXFileReader.responseType=RespType;
AJAXFileReader.timeout=10000; // Setting time-out to 10 s.
AJAXFileReader.send();
}
function CreateSVGProgBar(AnchorId)
{ // Creates new SVG progress bar.
var Parent=document.getElementById(AnchorId);
var NewSVG=document.createElementNS("http://www.w3.org/2000/svg","svg");
NewSVG.setAttribute("viewBox","0 0 102 22");
NewSVG.setAttribute("width","102");
NewSVG.setAttribute("height","22");
Parent.appendChild(NewSVG);
return NewSVG;
}
function ShowProgressBar(E,N,X)
{ // Show progress bar.
var P=X<0?"???":X+"%";
E.innerHTML="<text x=\"50\" y=\"16\" font-size=\"12\" fill=\"black\" stroke=\"black\" text-anchor=\"middle\">"+N+"</text><rect x=\"1\" y=\"1\" width=\""+X+"\" height=\"20\" fill=\""+(X<100?"#FF0000":"#0000FF")+"\" stroke=\"none\"/><rect x=\"1\" y=\"1\" width=\"100\" height=\"20\" fill=\"none\" stroke=\"black\" stroke-width=\"1\"/><text x=\""+X/2+"\" y=\"16\" font-size=\"12\" fill=\"black\" stroke=\"black\" text-anchor=\"middle\">"+P+"</text>";
}
function TracerOn(X)
{ // This will be executed after the file is completely loaded.
document.getElementById("Tron").innerHTML=X;
}
function PlayIt(X)
{ // This will be executed after the file is completely loaded.
var blob_uri=URL.createObjectURL(X);
document.getElementById("MagicalBox").appendChild(document.createElement("source")).src=blob_uri;
}
function Startup()
{ // Run after the Page is loaded.
LoadFile("example.txt","text","text/plain;charset=utf-8",TracerOn);
LoadFile("video.mp4","blob","video/mp4",PlayIt);
}
</script>
</head>
<body onload="Startup()">
<div id="ProgressBars"></div>
<div id="Tron">Text...</div>
<video id="MagicalBox" width="400" controls>Video...</video>
</body>
</html>
I have an web page which is a profile viewing page. Here I have an blank image. On click of this image I am able to prompt browse image option. But I don't know how to save the image into storage. Here is my code:
<div class="profileImage" >
<ul>
<li style="margin-top:5px;">
.Hii
</li>
<li>
<input type="file" id="my_file" style="display: none;" />
</li>
<li>
<img id="fileupload" name="filetoupload" src="../../static/img/img.png">
</li>
</ul>
</div>
$(document).ready(function(){
$("#fileupload").on("click", function() {
$("#my_file").click();
}
I tried this below jQuery to save image but is was of no use.
$(document).ready(function(){
$("#fileupload").on("click",function(){
$("#my_file").click();
userImage = document.getElementById('fileupload');
imgData = getBase64Image(userImage);
localStorage.setItem("imgData", imgData);
});
});
My requirement is I should be able to save the image from clicking add image i.e img.png in my code. I am using beego as web framework, but i will be happy if this get solved in jQuery or javascript.
jsBin demo
// https://stackoverflow.com/a/17711190/383904
function readImage() {
if ( this.files && this.files[0] ) {
var FR= new FileReader();
FR.onload = function(e) {
localStorage.setItem("imgData", event.target.result); // Send to Localstorage
};
FR.readAsDataURL( this.files[0] );
}
}
$(document).ready(function(){
$("#fileupload").on("click",function(){
$("#my_file").click().change( readImage );
});
});
To save that image on server you'll need to send that data to PHP.
PHP can than convert your base64 string to an image.
Otherwise, why not simply send that image using AJAX to a PHP script that will put it inside a server folder?
Send Image to server using File input type
https://stackoverflow.com/a/17711190/383904
I'm trying to read an input type="file" tag into a javascript string. I know this should be simple but I simply cannot get my code to work. The file is plain .html. Here's what I have
<h3>Select location of html file.</h3>
<form onSubmit="submitButtonPressed()">
<input type="file" id="classList" />
<input type="submit" />
</form>
<script>
var reader = new FileReader();
var htmlFile = document.getElementById("classList").files[0]; //read the file selected with the <input> tag
reader.readAsText(htmlFile);
var htmlText = reader.result; //and create a string with the contents
function submitButtonPressed() {
var lengthOfText = htmlText.length;
alert("It is " + lengthOfText + " characters long");
}
</script>
I'm just trying to create a string that contains the contents of the .html file selected by the input tag. I can't figure out why htmlText doesn't contain the contents of the .html file, could someone explain what I'm doing wrong?
this is the right answer for you :
HTML5 File API: How to see the result of readAsText()
reader.onload = function(e) {
// e.target.result should contain the text
};
reader.readAsText(file);
so i guess you will have to check on pressing the button if the file was loaded or not , maybe it is still in progress , or encountered an error