I have an HTML form within a Web App, created with GAS.
This HTML form was created from this file upload script here:
The point is that I needed the files to be uploaded in folders that follow the following pattern:
The first number refers to the model selected on the form, the second number refers to the slot used.
Therefore, it was necessary to create a function to identify the input
chosen in the Model
and, according to this input
, check which is the first empty folder, then take the ID
of that folder and pass it to the client side to upload the file inside it.
With the help of some members of the community, some adaptations were made and the final function was this:
/** Modified version of script written by Tanaike */
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " parentFolderId " not found");
}
return folder.getId();
} catch (error) {
return error;
}
}
It works perfectly, the point is that this form also has a function that generates a .csv file when the form is submitted, the function is this one:
.gs
file:
const saveDataAsCSV = (data, folderId) => DriveApp.getFolderById(folderId).createFile("Sample.csv", data);
HTML
file:
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
var data = form_values.join(",");
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
My goal is to make the .csv file be generated with the same name as the folder where the file was uploaded, that is, if the file is uploaded in folder 01-01
, the file name has to be 01-01.csv
, if the file is uploaded in the 02-02
folder, the file name has to be 02-02.csv
, and so on.
How can I achieve this?
The complete script code can be viewed here:
.gs
file:
function doGet(e) {
return HtmlService.createTemplateFromFile('forms0101.html').evaluate();
}
function getOAuthToken() {
return ScriptApp.getOAuthToken();
}
function getParent(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var id = ss.getId();
var parent = DriveApp.getFileById(id).getParents().next().getId();
return parent
}
function getLimitFolder(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
return limitfolder;
}
function getFilesLimit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var files = [];
Logger.log(pastapai);
//var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
var limitfolder = pastapai.next().getFoldersByName("_").next().getFiles();
while(limitfolder.hasNext()){
var file = limitfolder.next();
files.push([file.getId()]);
}
console.log(files.length);
return files.length;
}
//function testenumeroarquivos(){
//console.log(checkForFiles()); // When you use this line, you can see the filename of the files.
//}
/**
* creates a folder under a parent folder, and returns it's id. If the folder already exists
* then it is not created and it simply returns the id of the existing one
*/
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " parentFolderId " not found");
}
console.log("Test" nextFolderName)
return folder.getId();
} catch (error) {
return error;
}
}
const saveDataAsCSV = (data, folderId) => {
const folder = DriveApp.getFolderById(folderId);
folder.createFile(`${nextFolderName.getName()}.csv`, data);
}
const increaseRequest = (data, folderId) => DriveApp.getFolderById(folderId).createFile("IncreaseRequest.csv", data);
// NOTE: always make sure we use DriveApp, even if it's in a comment, for google to import those
// libraries and allow the rest of the app to work. see https://github.com/tanaikech/Resumable_Upload_For_WebApps
HTML
file:
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drive Multi Large File Upload</title>
<link href="https://fonts.googleapis.com/icon?family=Material Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA 058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<style>
@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;600;700&display=swap');
.disclaimer{
width: 480px;
color: #646464;
margin: 20px auto;
padding:0 16px;
text-align:center;
font:400 12px Rubik,sans-serif;
}
h5.center-align.teal-text {
font:700 26px Rubik,sans-serif;
color: #00F498!important;
}
.row {
font:600 14px Rubik,sans-serif;
}
.btn {
background-color: black;
}
.btn:hover {
background-color: #00F498;
}
body {
margin-top: -40px;
}
#progress {
color: #00000;
}
.disclaimer a{
color: #00BCAA;
}
#credit{
display:none
}
.btnOpenModal {
font-size: x-large;
padding: 10px;
border: none;
border-radius: 5px;
background-color: blueviolet;
color: white;
cursor: pointer;
}
hr {
border: 0px;
border-top: 1px solid lightgray;
}
.modal-container {
width: 100vw;
position: fixed;
top: 30px;
display: none;
z-index: 999;
justify-content: center;
}
.modal {
display: flex;
flex-direction: column;
padding: 30px;
background-color: white;
border-radius: 10px;
width: 50%;
}
.active {
display: flex;
}
.active .modal {
animation: modal .4s;
}
@keyframes modal {
from {
opacity: 0;
transform: translate3d(0, -60px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.btns {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.btns button {
font-size: medium;
padding: 10px;
border: none;
border-radius: 5px;
color: white;
cursor: pointer;
}
.btnOK {
background-color: blue!important;
}
.btnClose {
background-color: brown!important;
}
</style>
</head>
<body>
<form id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
<div id="forminner">
<div >
<div >
<h2>Info</h2>
<hr />
<span>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of
type and scrambled it to make a type specimen book.
</span>
<hr />
<div >
<button onclick="increaseRequest(); closeModal();">OK</button>
<button onclick="closeModal()">Close</button>
</div>
</div>
</div>
<h5 style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">YOUR NAME</h5>
<div >
<div >
<input id="name01" type="text" name="Name" required="required" aria-required="true">
<label for="name" >Name</label>
</div>
</div>
<h5 style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">SOME DESCRIPTION</h5>
<div >
<div >
<input id="description" type="text" name="Description" required="required" aria-required="true">
<label for="name">Description</label>
</div>
</div>
<div >
<div >
<h6>Model</h6>
<select id="Model">
<option selected="">Choose...</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
</select>
<h6>Color</h6>
<select id="Color">
<option selected="">Choose...</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</div>
</div>
<div >
<div >
<h5 >Upload the Video File</h5>
</div>
</div>
<div >
<div >
<div id="input-btn" >
<span>File</span>
<input id="files" type="file" single="">
</div>
<div >
<input type="text" placeholder="Select the file">
</div>
</div>
</div>
<div >
<div >
<button id="submit-btn" type="submit" onclick="submitForm(); return false;">Submit</button>
</div>
</div>
<div >
<div id="update">
<hr>
<p>
Por favor, aguarde enquanto seu arquivo está sendo carregado.<br><span style="color: #00000;"><b>Não feche ou atualize a janela durante o upload.</b></span>
</p>
</div>
</div>
<div >
<div id="progress">
</div>
</div>
</div>
</div>
<div id="success" style="display:none">
<h5 >Tudo certo!</h5>
<p>Se você já preencheu todos os campos é só fechar essa janela e clicar em enviar!</p>
<button id="fechar" style ="transform: translateX(160%);" type="button" onclick="google.script.host.close()">Fechar</button>
</div>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
var upload_folder = "01";
const chunkSize = 5242880;
const uploadParentFolderId = <?=getParent()?>; // creates a folder inside of this folder
const limitfolder = <?=getLimitFolder()?>;
const fileslimit = <?=getFilesLimit()?>;
const modal = document.querySelector('.modal-container');
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
function submitForm() {
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
// Added the below script.
if ($('#submit-btn.disabled')[0]) return; // short circuit
var name = upload_folder
var files = [...$('#files')[0].files]; // convert from FileList to array
if (files.length === 0) {
showError("Por favor, selecione um arquivo");
return;
}
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
google.script.run.saveDataAsCSV(data, limitfolder);
disableForm(); // prevent re submission
// the map and reduce are here to ensure that only one file is uploaded at a time. This allows
// the promises to be run sequentially
files.map(file => uploadFilePromiseFactory(file))
.reduce((promiseChain, currentTask) => {
return promiseChain.then(currentTask);
}, Promise.resolve([])).then( () => {
console.log("Completed all files upload");
showSuccess();
});
}
function checkforfiles() {
if (fileslimit <= 2) {
submitForm();
}
if (fileslimit == 3) {
openModal();
}
if (fileslimit >= 4) {
showError("erro");
return;
}
}
function openModal() {
modal.classList.add('active');
}
function closeModal() {
modal.classList.remove('active');
}
function increaseRequest() {
google.script.run.increaseRequest(data, limitfolder);
}
function disableForm() {
$('#submit-btn').addClass('disabled');
$('#input-btn').addClass('disabled');
$('#update').removeClass('hide');
$('#update').removeClass('hide');
}
function closer(){
google.script.host.close();
}
function uploadFilePromiseFactory(file) {
return () => {
console.log("Processing: ", file.name);
return new Promise((resolve, reject) => {
showProgressMessage("Seu arquivo está sendo carregado");
var fr = new FileReader();
fr.fileName = file.name;
fr.fileSize = file.size;
fr.fileType = file.type;
// not sure of a better way of passing the promise functions down
fr.resolve = () => resolve();
fr.reject = (error) => reject(error);
fr.onload = onFileReaderLoad;
fr.readAsArrayBuffer(file);
});
};
}
/**
* Gets called once the browser has loaded a file. The main logic that creates a folder
* and initiates the file upload resides here
*/
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
/**
* call to the DriveApp api. Wrapped in a promise in case I want to address timing issues between a
* createFolder and findFolderById
*/
function createOrGetFolder(folderName, parentFolderId) {
return new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(response => {
console.log("createOrGetFolder response: ", response);
if (response && response.length) {
resolve(response);
}
reject(response);
}).createOrGetFolder(folderName, parentFolderId);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function uploadFileToDriveFolder(parentFolderId) {
var fr = this;
return new Promise((resolve, reject) => {
var fileName = fr.fileName;
var fileSize = fr.fileSize;
var fileType = fr.fileType;
console.log({fileName: fileName, fileSize: fileSize, fileType: fileType});
var buf = fr.result;
var chunkpot = getChunkpot(chunkSize, fileSize);
var uint8Array = new Uint8Array(buf);
var chunks = chunkpot.chunks.map(function(e) {
return {
data: uint8Array.slice(e.startByte, e.endByte 1),
length: e.numByte,
range: "bytes " e.startByte "-" e.endByte "/" chunkpot.total,
};
});
google.script.run.withSuccessHandler(oAuthToken => {
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
xhr.setRequestHeader('Authorization', "Bearer " oAuthToken);
xhr.setRequestHeader('Content-Type', "application/json");
xhr.send(JSON.stringify({
mimeType: fileType,
name: fileName,
parents: [parentFolderId]
}));
xhr.onload = () => {
doUpload(fileName, {
location: xhr.getResponseHeader("location"),
chunks: chunks,
}).then(success => {
resolve(success);
console.log("Successfully uploaded: ", fileName);
},
error => {
reject(error);
});
};
xhr.onerror = () => {
console.log("ERROR: ", xhr.response);
reject(xhr.response);
};
}).getOAuthToken();
});
}
function showSuccess() {
$('#forminner').hide();
$('#success').show();
$('#fechar').show();
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#update').html(e);
}
function showProgressMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function doUpload(fileName, e) {
return new Promise((resolve, reject) => {
showProgressMessage("Carregando: <span style='color: #00F498 ;'>" "0%</span>");
var chunks = e.chunks;
var location = e.location;
var cnt = 0;
var end = chunks.length;
var temp = function callback(cnt) {
var e = chunks[cnt];
var xhr = new XMLHttpRequest();
xhr.open("PUT", location, true);
console.log("content range: ", e.range);
xhr.setRequestHeader('Content-Range', e.range);
xhr.send(e.data);
xhr.onloadend = function() {
var status = xhr.status;
cnt = 1;
console.log("Uploading: " status " (" cnt " / " end ")");
showProgressMessage("Carregando: <span style='color: #00F498 ;'>"
Math.floor(100 * cnt / end) "%</span>" );
if (status == 308) {
callback(cnt);
} else if (status == 200) {
$("#progress").text("Done.");
resolve();
} else {
$("#progress").text("Error: " xhr.response);
reject();
}
};
}(cnt);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function getChunkpot(chunkSize, fileSize) {
var chunkPot = {};
chunkPot.total = fileSize;
chunkPot.chunks = [];
if (fileSize > chunkSize) {
var numE = chunkSize;
var endS = function(f, n) {
var c = f % n;
if (c == 0) {
return 0;
} else {
return c;
}
}(fileSize, numE);
var repeat = Math.floor(fileSize / numE);
for (var i = 0; i <= repeat; i ) {
var startAddress = i * numE;
var c = {};
c.startByte = startAddress;
if (i < repeat) {
c.endByte = startAddress numE - 1;
c.numByte = numE;
chunkPot.chunks.push(c);
} else if (i == repeat && endS > 0) {
c.endByte = startAddress endS - 1;
c.numByte = endS;
chunkPot.chunks.push(c);
}
}
} else {
var chunk = {
startByte: 0,
endByte: fileSize - 1,
numByte: fileSize,
};
chunkPot.chunks.push(chunk);
}
return chunkPot;
}
</script>
</body>
</html>
It can also be viewed by accessing this worksheet
CodePudding user response:
Although I'm not sure whether I could correctly understand your actual expected result, please test the following modification.
Google Apps Script side:
Please modify saveDataAsCSV
of Google Apps Script as follows.
const saveDataAsCSV = (data, folderId1, folderId2 = null) => {
if (folderId2) {
DriveApp.getFolderById(folderId1).createFile(`${DriveApp.getFolderById(folderId2).getName()}.csv`, data);
} else {
DriveApp.getFolderById(folderId1).createFile("sample.csv", data);
}
}
Javascript side:
Please remove google.script.run.saveDataAsCSV(data, uploadParentFolderId);
in the function submitForm
.
And, please modify onFileReaderLoad
of Javascript as follows.
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
google.script.run.saveDataAsCSV(data, uploadParentFolderId, newFolderId);
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
- By this modification, I think that a CSV file like
01-01.csv
is created touploadParentFolderId
folder.