I am working on a django app in which the html code called tool.hmtl along with the javascript code called myscripts.js let the user upload the folder and then do some processing on that data. Relevant section of tool.hmtl is given as follows.
<main class="tool mg-t-900">
<div id="folderUploadDiv">
<h1 class="tool__heading | text-center text-capitalize">
Upload your folder here
</h1>
<label
for="folder"
class="tool__upload | flex flex-center flex-column mg-t-500 text-center"
>
<i class="ri-upload-cloud-line tool__icon"></i>
<p class="tool__txt text-capitalize">Browse folder to upload</p>
</label>
<input type="file" id="folder" webkitdirectory multiple />
.............some code......
<script src="{% static 'myscripts.js' %}"></script>
<script src="{% static 'app.js' %}"></script>
<script>
const fileInput = document.querySelector("#folder");
const loder = document.querySelector(".loder");
const toggleLoder = function () {
loder.classList.add("active");
setTimeout(() => {
loder.classList.remove("active");
}, 30000);
startTypingAnimation();
};
fileInput.addEventListener("change", toggleLoder);
function startTypingAnimation() {
new TypeIt("#loder-text", {
strings: "Your file is being prepared...",
speed: 75,
loop: true,
}).go();
}
</script>
<script>
setTimeout(function () {
location.reload();
}, 300000); // refresh every 5 minutes (300000 milliseconds)
</script>
</body>
</html>
{% endblock %}
and relevant section of myscripts.js is as under.
// const axios = require('axios');
$("#folderUploadDiv").show();
$("#selectorDiv").hide();
username = "";
contentType = "";
var dataToSend = []
var flag=1
document.getElementById("folder").addEventListener("change", function(event) {
var output = document.querySelector("ul");
var files = event.target.files;
var jsonFiles =0;
var rightOrderFiles = [];
for (var i=0; i<files.length; i++) {
var item = document.createElement("li");
var innerFiles = files[i].webkitRelativePath.split("/");
if(innerFiles.length ===4){
jsonFiles++;
rightOrderFiles.push(files[i].webkitRelativePath)
var reader = new FileReader();
reader.onload = onReaderLoad;
reader.readAsText(files[i]);
}
// console.log("file path : ", files[i].webkitRelativePath);
item.innerHTML = files[i].webkitRelativePath;
output.appendChild(item);
};
}, false);
In this code snippets (both HTML and Javascript), we can see that user uploads the folder and then some processing is done in Javascript.
My question is, how can I change the code both in tool.hmtl along with the javascript code myscripts.js such that the user uploads a zip file and a folder is extracted from that zip file rather than user uploading the folder itself. Everything else remain the same, the only change needed is let the user upload zip file instead of the folder directly and the folder is then extracted from the zip file.
To change the code to extract a folder from a zip file, you need to make the following changes in both tool.html and myscripts.js:
1- In tool.html, change the type of the file input to "file" instead of "folder".
<input type="file" id="folder" />
2- In myscripts.js, use a library such as jszip.js to extract the contents of the uploaded zip file. You can add the library to your project by including the following line in the head of tool.html:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.2/jszip.min.js"></script>
3- Replace the event listener for the file input with the following code:
document.getElementById("folder").addEventListener("change", function(event) {
var output = document.querySelector("ul");
var file = event.target.files[0];
var reader = new FileReader();
reader.onload = function (e) {
var zip = new JSZip();
zip.loadAsync(e.target.result)
.then(function (zip) {
var rightOrderFiles = [];
var jsonFiles = 0;
zip.forEach(function (relativePath, file) {
if (relativePath.split("/").length === 4) {
jsonFiles++;
rightOrderFiles.push(relativePath);
file.async("text").then(function (content) {
var item = document.createElement("li");
item.innerHTML = relativePath;
output.appendChild(item);
});
}
});
});
};
reader.readAsArrayBuffer(file);
}, false);
This code uses JSZip to load the contents of the uploaded zip file, extract the files and process them in the same manner as before.
I using js to create my form, my problem is how to just select directory, not file?
Belows is my coding:
const realFileBtn = document.getElementById("real-file");
const customBtn = document.getElementById("custom-button");
const customTxt = document.getElementById("custom-text");
customBtn.addEventListener("click", function() {
realFileBtn.click();
});
realFileBtn.addEventListener("change", function() {
if (realFileBtn.value) {
customTxt.innerHTML = realFileBtn.value;
} else {
customTxt.innerHTML = "No file chosen, yet.";
}
});
<input type="file" id="real-file" hidden="hidden" />
<button type="button" id="custom-button">CHOOSE A FILE</button>
<span id="custom-text">No file chosen, yet.</span>
Below is my output:
Actually I want the output not to select the file, just select the folder, because the next step I need store file in this folder directory.
If success the output is folder directory name is:
C:\Staff Import Folder\Source
Hope someone can help me solve this problem. Thanks.
In newest browsers, you can select a directory content with the webkitdirectory attribute of the <input type="file"> element, as being defined by Files and Directories entries API, but I fear this will be deceptive to you since it will only select all the files in this directory (and its sub-folders). There is currently no way to navigate this directory from here.
We should be able to navigate such directory, by looking at the webkitEntries IDL attribute, but currently browsers only populate this from a drop event. You can see a demo in this Q.A.
inp.onchange = (e) => {
console.log( inp.webkitEntries ); // []
console.log( [...inp.files].map( file => file.name ) );
};
<input type="file" webkitdirectory id="inp">
But even then, while you'll be able to navigate that directory, you still won't be able to set anything on the FileSystem. Web scripts simply don't have the authorization to write anything on the disk, except in sand-boxed areas like IndexedDB.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
document.getElementById("filepicker").addEventListener("change", function(event) {
let output = document.getElementById("listing");
let files = event.target.files;
for (let i = 0; i < files.length; i++) {
let item = document.createElement("li");
item.innerHTML = files[i].webkitRelativePath;
output.appendChild(item);
};
}, false);
<input type="file" id="filepicker" name="fileList" webkitdirectory multiple />
<ul id="listing"></ul>
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.
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>
If we were on a nodeJS server, we could write a header, set a mime type, and send it:
res.header("Content-Disposition", "attachment;filename="+name+".csv");
res.type("text/csv");
res.send(200, csvString);
and because of the headers, the browser will create a download for the named csv file.
When useful data is generated in a browser, one solution to getting it in a CSV file is to use ajax, upload it to the server, (perhaps optionally save it there) and get the server to send it back with these headers to become a csv download back at the browser.
However, I would like a 100% browser solution that does not involve ping-pong with the server.
So it occurred to me that one could open a new window and try to set the header with a META tag equivalent.
But this doesn't work for me in recent Chrome.
I do get a new window, and it contains the csvString, but does not act as a download.
I guess I expected to get either a download in a bottom tab or a blank new window with a download in a bottom tab.
I'm wondering if the meta tags are correct or if other tags are also needed.
Is there a way to make this work without punting it to the server?
JsFiddle for Creating a CSV in the Browser (not working - outputs window but no download)
var A = [['n','sqrt(n)']]; // initialize array of rows with header row as 1st item
for(var j=1;j<10;++j){ A.push([j, Math.sqrt(j)]) }
var csvRows = [];
for(var i=0,l=A.length; i<l; ++i){
csvRows.push(A[i].join(',')); // unquoted CSV row
}
var csvString = csvRows.join("\n");
console.log(csvString);
var csvWin = window.open("","","");
csvWin.document.write('<meta name="content-type" content="text/csv">');
csvWin.document.write('<meta name="content-disposition" content="attachment; filename=data.csv"> ');
csvWin.document.write(csvString);
There's always the HTML5 download attribute :
This attribute, if present, indicates that the author intends the
hyperlink to be used for downloading a resource so that when the user
clicks on the link they will be prompted to save it as a local file.
If the attribute has a value, the value will be used as the pre-filled
file name in the Save prompt that opens when the user clicks on the
link.
var A = [['n','sqrt(n)']];
for(var j=1; j<10; ++j){
A.push([j, Math.sqrt(j)]);
}
var csvRows = [];
for(var i=0, l=A.length; i<l; ++i){
csvRows.push(A[i].join(','));
}
var csvString = csvRows.join("%0A");
var a = document.createElement('a');
a.href = 'data:attachment/csv,' + encodeURIComponent(csvString);
a.target = '_blank';
a.download = 'myFile.csv';
document.body.appendChild(a);
a.click();
FIDDLE
Tested in Chrome and Firefox, works fine in the newest versions (as of July 2013).
Works in Opera as well, but does not set the filename (as of July 2013).
Does not seem to work in IE9 (big suprise) (as of July 2013).
An overview over what browsers support the download attribute can be found Here
For non-supporting browsers, one has to set the appropriate headers on the serverside.
Apparently there is a hack for IE10 and IE11, which doesn't support the download attribute (Edge does however).
var A = [['n','sqrt(n)']];
for(var j=1; j<10; ++j){
A.push([j, Math.sqrt(j)]);
}
var csvRows = [];
for(var i=0, l=A.length; i<l; ++i){
csvRows.push(A[i].join(','));
}
var csvString = csvRows.join("%0A");
if (window.navigator.msSaveOrOpenBlob) {
var blob = new Blob([csvString]);
window.navigator.msSaveOrOpenBlob(blob, 'myFile.csv');
} else {
var a = document.createElement('a');
a.href = 'data:attachment/csv,' + encodeURIComponent(csvString);
a.target = '_blank';
a.download = 'myFile.csv';
document.body.appendChild(a);
a.click();
}
#adeneo answer works for Firefox and chrome... For IE the below can be used.
if (window.navigator.msSaveOrOpenBlob) {
var blob = new Blob([decodeURIComponent(encodeURI(result.data))], {
type: "text/csv;charset=utf-8;"
});
navigator.msSaveBlob(blob, 'FileName.csv');
}
See adeneo's answer, but don't forget encodeURIComponent!
a.href = 'data:application/csv;charset=utf-8,' + encodeURIComponent(csvString);
Also, I needed to do "\r\n" not just "\n" for the row delimiter.
var csvString = csvRows.join("\r\n");
Revised fiddle: http://jsfiddle.net/7Q3c6/
Once I packed JS code doing that to a tiny library:
https://github.com/AlexLibs/client-side-csv-generator
The Code, Documentation and Demo/Playground are provided on Github.
Enjoy :)
Pull requests are welcome.
We can easily create and export/download the excel file with any separator (in this answer I am using the comma separator) using javascript. I am not using any external package for creating the excel file.
var Head = [[
'Heading 1',
'Heading 2',
'Heading 3',
'Heading 4'
]];
var row = [
{key1:1,key2:2, key3:3, key4:4},
{key1:2,key2:5, key3:6, key4:7},
{key1:3,key2:2, key3:3, key4:4},
{key1:4,key2:2, key3:3, key4:4},
{key1:5,key2:2, key3:3, key4:4}
];
for (var item = 0; item < row.length; ++item) {
Head.push([
row[item].key1,
row[item].key2,
row[item].key3,
row[item].key4
]);
}
var csvRows = [];
for (var cell = 0; cell < Head.length; ++cell) {
csvRows.push(Head[cell].join(','));
}
var csvString = csvRows.join("\n");
let csvFile = new Blob([csvString], { type: "text/csv" });
let downloadLink = document.createElement("a");
downloadLink.download = 'MYCSVFILE.csv';
downloadLink.href = window.URL.createObjectURL(csvFile);
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
downloadLink.click();
See adeneo's answer, but to make this work in Excel in all countries you should add "SEP=," to the first line of the file. This will set the standard separator in Excel and will not show up in the actual document
var csvString = "SEP=, \n" + csvRows.join("\r\n");