Send FileList to Flask backend? - javascript

I want to give the user the possibility to upload multiple files during multiple stages which will be processed in my flask backend upon uploading. Therefore I have a multiple file input form in my HTML:
<form action="/do-stuff" method="POST" enctype="multipart/form-data">
<label id="file-uploader-label" for="file-uploader">Upload Files</label>
<input id="file-uploader" type="file" name="file" accept="image/*" multiple="true" required>
<button id="btn-upload" type="submit">Upload</button>
</form>
I display all files in a table (not shown above) and give the user the possibility to remove items from the file list as follows:
let fileList = new Array;
const fileUploader = this.document.getElementById('file-uploader');
let uniqueid = 1;
fileUploader.addEventListener('change', function () {
for (let i = 0; i < fileUploader.files.length; i++) {
let currFile = fileUploader.files[i];
fileList[uniqueid] = currFile;
// Removal and display code
uniqueid++;
}
});
This leaves me with the fileList of type "FileList" containing all desired files. Whenever I upload the file-uploader will only use the latest / most recently uploaded files and not all.
Having the complete list - is there a way in javascript to append to the files contained in the file-uploader or a workaround to pass the data to my flask backend (werkzeug.datastructures.FileStorage type)?
Thanks in advance :)

Once you have a complete list of files that you want to upload, you can make a POST request from JavaScript to your Flask endpoint to store the files.
With a minimal form
<form id="form-files">
<input id="file-input" type="file" multiple=true />
<button type="submit">Submit</button>
</form>
You can intercept the form submit action and send the files to a Flask endpoint. The fileList array comes from your file upload/remove logic.
var formFiles = document.getElementById('form-files');
formFiles.addEventListener('submit', function(e) {
e.preventDefault();
var formData = new FormData();
var request = new XMLHttpRequest();
for (var i = 0; i < fileList.length; i++) {
formData.append('files[]', fileList[i]);
}
request.open('POST', '/upload');
request.send(formData);
});
Finally write a flask upload endpoint that processes (stores) the files.
#app.route("/upload", methods=["POST"])
def upload():
uploaded_files = request.files.getlist("file[]")
# Store files

Related

How can I do a multi file upload in Django, allowing user to remove file upload before form submission?

I am trying to do a fairly common user requirement, allowing the user to upload multiple files in Django, and giving them the option to remove files that they may have accidentally uploaded.
As far as I can tell, even if the user remove the uploaded files from the DOM prior to the user submitting the form via Javascript code as shown here...Multiple file upload - with 'remove file' link, the uploaded files remain in the request.FILES.getlist(''). I have spent most of today researching this. I have in fact determined that when the uploaded files are deleted via Javascript from the DOM they are in fact still present in the request.FILES.getlist('').
I verified this by printing out the contents of the getlist file in my CreateView as shown below...
list=[] #myfile is the key of a multi value dictionary, values are the uploaded files
for f in request.FILES.getlist('files1'): #myfile is the name of your html file button
filename = f.name
print(filename)
My question is how can I get the contents of the DOM and compare it to what's in request.FILES.getlist? I surmise that's how I'll be able to tell Django what's real and what's not. Thanks in advance for any thoughts.
Here's the Javascript code that I'm using...I documented it via a link above but here it is again for ease of reading. The Javascript seems to work just fine...it's just that the files are still in request.FILES.getlist. I suspect this is specific to Django/Python. Something additional needs to happen in order to reconcile the DOM with what actually remains in request.FILES.getlist.
$(document).ready(function (){
$.fn.fileUploader = function (filesToUpload) {
this.closest(".files").change(function (evt) {
for (var i = 0; i < evt.target.files.length; i++) {
filesToUpload.push(evt.target.files[i]);
};
var output = [];
for (var i = 0, f; f = evt.target.files[i]; i++) {
var removeLink = "<a class=\"removeFile\" href=\"#\" data-fileid=\"" + i + "\">Remove</a>";
output.push("<li><strong>", escape(f.name), "</strong> - ",
f.size, " bytes. ", removeLink, "</li> ");
}
$(this).children(".fileList")
.append(output.join(""));
});
};
var filesToUpload = [];
$(document).on("click",".removeFile", function(e){
e.preventDefault();
console.log("Htell");
var fileName = $(this).parent().children("strong").text();
for(i = 0; i < filesToUpload.length; ++ i){
if(filesToUpload[i].name == fileName){
filesToUpload.splice(i, 1);
}
}
$(this).parent().remove();
});
$("#files1").fileUploader(filesToUpload);
});
The HTML...
<div class="leftwidth22">
<div class="width52">
<h2 class="floatright23">Attachment(s) - </h2>
</div>
</div>
<div class="rightwidth60">
<h2 class="width70">
<div class="row files" id="files1">
<span class="">
<input type="file" name="files1" multiple />
</span>
<br />
<ul class="fileList"></ul>
</div>
</h2>
</div>
Here's my view as well...it's a CreateView...and most of the work is happening in POST...
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Procedures:procedure_main_menu'))
else:
self.object = None
user = request.user
form_class = self.get_form_class()
form = self.get_form(form_class)
file_form = NewProcedureFilesForm(request.POST, request.FILES)
files = request.FILES.getlist('files1') #field name in model
if form.is_valid() and file_form.is_valid():
procedure_instance = form.save(commit=False)
procedure_instance.user = user
procedure_instance.save()
list=[] #myfile is the key of a multi value dictionary, values are the uploaded files
for f in request.FILES.getlist('files1'): #myfile is the name of your html file button
filename = f.name
print(filename)
for f in files:
procedure_file_instance = NewProcedureFiles(attachments=f, new_procedure=procedure_instance)
procedure_file_instance.save()
return self.form_valid(form)
else:
form_class = self.get_form_class()
form = self.get_form(form_class)
file_form = NewProcedureFilesForm()
return self.form_invalid(form)

JQuery : How to put multiple files into formData using dropzone.js and send it via ajax

I have a problem trying to upload my multiple files. I have two method sending into a page via ajax. One is the dropzone, and other is my XMLHttpRequest. Every time I send by clicking button it duplicates my request following only the first method which is dropzone and ignoring my XMLHttpRequest. So I am trying figure out the way to send it binding into a formData to make it only 1 request of files all together.
My form
<form style="border:3px dashed #D9EDF7;" action="UploadImage"
class="dropzone dz-clickable"
id="my-awesome-dropzone" enctype="multipart/form-data">
<div class="dz-message">Drop files here or click to upload.
<br> <span class="note">(Selected files are not
automatically uploaded.)</span>
</div>
<div class="fallback">
<input name="file" id="filez" type="file" multiple/>
</div>
</form>
My Js
Dropzone.autoDiscover = false;
var myDropzone = new Dropzone('#my-awesome-dropzone', {
url : '../ajax/ajax_add/ajax_addNEWContestant.php?
multipleImage=multiple_image&event_id='+event_id,
autoProcessQueue : false
});
$(document).on('click','#addnewContestant', function(e){
myDropzone.processQueue();
var formdata = new FormData();
formdata.append('file_addnew', file_addnew.files[0]);
//If there is a way, I just want to bind all the files from dropzone into formData
var data = new FormData();
for(var b=0; b<imageContainer.length; b++){
formdata.append('All_files_from_dropzone[], input_file.files[b]);
}
var param = "?event_id="+encodeURIComponent(event_id)+
"&contestant_name="+encodeURIComponent(contestant_name)+
"&contestant_lastName="+encodeURIComponent(contestant_lastName)+
"&conAge="+encodeURIComponent(conAge)+
"&hAddress="+encodeURIComponent(hAddress)+
"&email_add="+encodeURIComponent(email_add)+
"&conContactNum="+encodeURIComponent(conContactNum)+
"&conDesc="+encodeURIComponent(conDesc)+
"&conId_hidden="+encodeURIComponent(conId_hidden)+
"&hidden_gender="+encodeURIComponent(hidden_gender)+
"&random_gender="+encodeURIComponent(random_gender)+
"&multipleImage="+encodeURIComponent(multipleImage);
beforeSend();
xhr = new XMLHttpRequest();
var url = '../ajax/ajax_add/ajax_addNEWContestant.php';
xhr.open('post', url+param, true);
});
Dropzone will already submit the other form fields along with the files, so you don't need to do any xhr work yourself

Sync a FileReader in a for loop: Javascript

I am trying to implement a JavaScript program to read a bunch of CSV files from a particular directory containing sub-directories. To do so, I am thinking of using Javascript to read multiple files using webkitdirectory and reading the files and then using java to push them.
HTML Form
<input id="csv" type="file" multiple webkitdirectory directory>
<input type="button" onclick="readCSV()" value="Submit">
JavaScript to Read Files
function readCSV(){
var fileInput = document.getElementById("csv");
var fileCount = fileInput.files.length;
if( fileCount != 0) {
alert(fileCount);
var reader;
for (var i = 0; i < fileCount; i++) {
reader = new FileReader();
reader.onload = function () {
document.getElementById('out').innerHTML = reader.result;
// Once the file is read, Send it to JavaServlet to save on server.
};
reader.readAsBinaryString(fileInput.files[i]);
}
} else {
alert("Select a File");
}
}
The problem I am facing here is that the code runs successfully when only one file is selected but breaks when multiple files are selected. I think the problem is in sync read. But I couldn't find a way to either sync read or pause the loop while the reader object completely reads the file.

JavaScript delete File from FileList to be uploaded

There is the code https://jsfiddle.net/bfzmm1hc/1 Everything looks fine but I want to delete some of the files from the set.
I have already found these:
How to remove one specific selected file from input file control
input type=file multiple, delete items
I know that FileList object is readonly, so I can just copy the files to a new array. But what should I do with this new array of File objects? I can't assign it to the files property...
I found a workaround. This will not require AJAX for the request at all and the form can be sent to the server. Basically you could create an hidden or text input and set it's value attribute to the base64 string created after processing the file selected.
<input type=hidden value=${base64string} />
You will probably consider the idea to create multiple input file instead of input text or hidden. This will not work as we can't assign a value to it.
This method will include the input file in the data sent to the database and to ignore the input file you could:
in the back-end don't consider the field;
you can set the disabled attribute to the input file before serialising the form;
remove the DOM element before sending data.
When you want to delete a file just get the index of the element and remove the input element (text or hidden) from the DOM.
Requirements:
You need to write the logic to convert files in base64 and store all files inside an array whenever the input file trigger the change event.
Pros:
This will basically give you a lot of control and you can filter, comparing files, check for file size, MIME type, and so on..
Since you cannot edit the Read Only input.files attribute, you must upload a form using XMLHttpRequest and send a FormData object. I will also show you how to use URL.createObjectURL to more easily get a URI from the File object:
var SomeCl = {
count: 0,
init: function() {
$('#images').change(this.onInputChange);
},
onInputChange: function() {
// reset preview
$('.container').empty();
// reset count
SomeCl.count = 0;
// process files
SomeCl.processFiles(this.files, function(files) {
// filtered files
console.log(files);
// uncomment this line to upload the filtered files
SomeCl.upload('url', 'POST', $('#upload').get(0), files, 'images[]');
});
},
processFiles: function(files, callback) {
// your filter logic goes here, this is just example
// filtered files
var upload = [];
// limit to first 4 image files
Array.prototype.forEach.call(files, function(file) {
if (file.type.slice(0, 5) === 'image' && upload.length < 4) {
// add file to filter
upload.push(file);
// increment count
SomeCl.count++;
// show preview
SomeCl.preview(file);
}
});
callback(upload);
},
upload: function(method, url, form, files, filename) {
// create a FormData object from the form
var fd = new FormData(form);
// delete the files in the <form> from the FormData
fd.delete(filename);
// add the filtered files instead
fd.append(filename, files);
// demonstrate that the entire form has been attached
for (var key of fd.keys()) {
console.log(key, fd.getAll(key));
}
// use xhr request
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.addEventListener('progress', function(e) {
console.log('lengthComputable', e.lengthComputable);
console.log(e.loaded + '/' + e.total);
});
xhr.addEventListener('load', function(e) {
console.log('uploaded');
});
xhr.addEventListener('error', function(e) {
console.log('this is just a demo');
});
xhr.send(fd);
},
preview: function(file) {
// create a temporary URI from the File
var url = URL.createObjectURL(file);
// append a preview
$('.container').append($('<img/>').attr('src', url));
}
};
SomeCl.init();
.container img {
max-width: 250px;
max-height: 250px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="upload">
<input name="other" type="hidden" value="something else">
<input name="images[]" id="images" multiple="multiple" type="file">
<div class="container"></div>
</form>

Handling multiple files from an input element in an array with Google Apps Script

I have a form, which allows to select an item from a dropdown list and upload a file. The name and the ID of the item are saved in a Spreadsheet document. Works with one file...but I want to upload multiple files. Could you help me fixing the script?
The HTML part looks like this
<div class="col-md-4 col-sm-6 ">
<div class="caption">
<h3>Bildauswahl</h3>
<p align="center"><input type="file" name="myFiles[]" id="myFiles" multiple></p>
</div>
</div>
My script, which is not working, is the following:
var dropBoxId = "XYZ";
var logSheetId = "ABC";
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('InputForm.html');
}
function uploadFiles(formObject) {
try {
// Create a file in Drive from the one provided in the form
var folder = DriveApp.getFolderById(dropBoxId);
var input = document.getElementById('myFiles');
for (i = 0; i<input.files.length; i++) {
var blob = input.files[i];
var file = folder.createFile(blob);
var ss = SpreadsheetApp.openById(logSheetId);
var sheet = ss.getSheets()[0];
sheet.appendRow([file.getName(), file.getUrl(), formObject.myName]);
}
// Return the new file Drive URL so it can be put in the web app output
return file.getUrl();
} catch (error) {
return error.toString();
}
}
Thanks.
As of right now you have to use a work around to work with multiple files. The multiple attribute only works in IFRAME mode, but file inputs are broken in IFRAME mode.
To see this workaround take a look at the bug submission for this issue:
https://code.google.com/p/google-apps-script-issues/issues/detail?id=4610
Also in your code you have some mixing of server side and client side code that will not work:
var folder = DriveApp.getFolderById(dropBoxId); //server side
var input = document.getElementById('myFiles'); //client side
You will need to do your multiple file processing on the client side
I came up with a nice solution for multi-file uploading. Limitations are files must be under 10 MB.
CODE.GS
function doGet() {
return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function saveFile(data,name,folderId) {
var contentType = data.substring(5,data.indexOf(';'));
var file = Utilities.newBlob(Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)), contentType, name);
DriveApp.getFolderById(folderId).createFile(file);
}
index.html
<div>
<input type="file" id="myFiles" name="myFiles" multiple/>
<input type="button" value="Submit" onclick="SaveFiles()" />
</div>
<script>
var reader = new FileReader();
var files;
var fileCounter = 0;
var folderId = "";
reader.onloadend = function () {
google.script.run
.withSuccessHandler(function(){
fileCounter++;
postNextFile();
}).saveFile(reader.result,files[fileCounter].name,folderId);
}
function SaveFiles(){
var folderSelect = document.getElementById("folderSelectId");
folderId = folderSelect.options[e.selectedIndex].value;
files = document.getElementById("myFiles").files;
postNextFile();
}
function postNextFile(){if(fileCounter < files.length){reader.readAsDataURL(files[fileCounter]);}else{fileCounter=0;alert("upload done")}}
</script>

Categories