I initially saw this code online, that allows users to upload files to their google drive through a page. The script automatically creates a folder
//https://script.google.com/d/12EnDFZrsfpBubZ9lM7pnHIsn9M49_vyXm0TLBQ_pyx_ViAJH3HXgkoe9/edit?newcopy=true
So you will notice that initially the codes is supposed to be deployed as a webapp but I tweaked it to make it run on the sidebar. The html part loads fine, you can actually key in all the data, but once you click the upload form, it just returns a blank page. I'm pretty convinced that it's because the click button is not connecting to the script again, making it fail
This is the original code
/* The script is deployed as a web app and renders the form */
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('form.html')
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
// This is important as file upload fail in IFRAME Sandbox mode.
}
/* This function will process the submitted form */
function uploadFiles(form) {
try {
// Name of the Drive folder where the files should be saved
var dropbox = "Database";
;
var folder, folders = DriveApp.getFoldersByName(dropbox);
// Find the folder, create if the folder does not exist
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
// Get the file uploaded though the form as a blob
var blob = form.myFile;
var file = folder.createFile(blob);
// Set the file description as the name of the uploader
file.setName(form.myCode + " " + form.myfilename + " - " + form.myID + " - " + form.myName);
file.setDescription("Uploaded by " + form.myName + " - " + form.myEmail);
// Return the download URL of the file once its on Google Drive
return "File uploaded successfully, please check your drive with this link for confirmation: " + file.getUrl();
} catch (error) {
// If there's an error, show the error message
return error.toString();
}
}
AND THE HTML IS HERE
<!-- Include the Google CSS package -->
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<!-- You can also include your own CSS styles -->
<style>
form { margin: 40px 20px auto; }
input { display:inline-block; margin: 20px; }
</style>
<script>
// The function will be called after the form is submitted
function uploadFile() {
document.getElementById('uploadFile').value = "Uploading File..";
google.script.run
.withSuccessHandler(fileUploaded)
.uploadFiles(document.getElementById("labnol"));
return false;
}
// This function will be called after the Google Script has executed
function fileUploaded(status) {
document.getElementById('labnol').style.display = 'none';
document.getElementById('output').innerHTML = status;
}
</script>
<!-- This is the HTML form -->
<form id="labnol">
<!-- Text input fields -->
File Upload<br>
<br>
Your Name:<br>
<input type="text" name="myName" placeholder="Your name.."> <br><br>
Email Address: <br>
<input type="email" name="myEmail" placeholder="Your email.."> <br><br>
ID? <br>
<input type="number" name="myID" placeholder="Your ID.."> <br><br>
Upload Code: <br>
<input type="text" name="myCode" placeholder="Your Upload code.."> <br><br>
File Name: <br>
<input type="text" name="myfilename" placeholder="Your File Name"> <br><br>
<!-- File input filed -->
<input type="file" name="myFile">
<!-- The submit button. It calls the server side function uploadfiles() on click -->
<br>
<input type="submit" id="uploadFile" value="Upload File"
onclick="this.value='Uploading..';uploadFile();">
</form>
<!-- Here the results of the form submission will be displayed -->
<div id="output"></div>
So from the original code I tweaked it to replace the top part
function showSidebar() {
var html = HtmlService.createHtmlOutputFromFile('form')
.setTitle('Upload Form')
.setWidth(250);
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.showSidebar(html);
}
// This function will process the submitted form
function uploadFile(form) {
try {
// Name of the Drive folder where the files should be saved
var dropbox = "Database";
;
var folder, folders = DriveApp.getFoldersByName(dropbox);
// Find the folder, create if the folder does not exist
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
// Get the file uploaded though the form as a blob
var blob = form.myFile;
var file = folder.createFile(blob);
// Set the file description as the name of the uploader
file.setName(form.myCode + " " + form.myfilename + " - " + form.myID + " - " + form.myName);
file.setDescription("Uploaded by " + form.myName + " - " + form.myEmail);
// Return the download URL of the file once its on Google Drive
return "File uploaded successfully, please check your drive with this link for confirmation: " + file.getUrl();
} catch (error) {
// If there's an error, show the error message
return error.toString();
}
}
So I basically replaced the top part with a script to load the sidebar and the html "form" but the error appears is that upon clicking upload, it does not work.
I'm guessing it's this part
<input type="submit" id="uploadFile" value="Upload File"
onclick="this.value='Uploading..';uploadFile();">
since onClick, it should run the function uploadFile() but it does not work.
I've been trying to figure this out for quite some time but can't seem to make this last part work. So I'm here asking if anyone can help me solve this coding issues
Based from this documentation: HTML Service: Communicate with Server Functions
google.script.run is an asynchronous client-side JavaScript API that allows HTML-service pages to call server-side Apps Script functions. The following example shows the most basic functionality of google.script.run — calling a function on the server from client-side JavaScript.
Check how the Form communicates with Apps Script. If you call a server function with a form element as a parameter, the form becomes a single object with field names as keys and field values as values. The values are all converted to strings, except for the contents of file-input fields, which become Blob objects.
Here is there sample code:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
// Prevent forms from submitting.
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
function handleFormSubmit(formObject) {
google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
}
function updateUrl(url) {
var div = document.getElementById('output');
div.innerHTML = 'Got it!';
}
</script>
</head>
<body>
<form id="myForm" onsubmit="handleFormSubmit(this)">
<input name="myFile" type="file" />
<input type="submit" value="Submit" />
</form>
<div id="output"></div>
</body>
</html>
It would be best if you simplify how you call the function in an OnSubmit event. Also, you can debug you script using Execution Transcript, which is a record of each call to a Google Apps Script service that is made while the script runs.
Hope this helps!
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 a Google Form to collect information from my workers working in remote locations
Emp No *
Punch *
Customer details / mode or travel
The data goes into a Google spreadsheet with the below structure
Timestamp Emp No Punch Remark Name GeoCode GeoAddress Email
I am able to capture the GPS co-ordinates of the user by the below script. I made a web app (anyone even anonymous can run) and asked the user to click the link.
What I am not able to do :
I want to save the email ID (or emp no) of the user filling the form. But the email ID is not getting captured into the form. If I fill the form, the email ID is captured. For other users it is not captured. I don't want all the users to authenticate the script (to run the script as the logged in user). It must be captured by some other way. Is it possible?
If the GPS is not captured (it is empty), I want to display a different message in the HTML page. How to do it?
Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("Index");
}
//
function getLoc(value) {
var destId = FormApp.getActiveForm().getDestinationId() ;
var ss = SpreadsheetApp.openById(destId) ;
var respSheet = ss.getSheetByName("Location");
var numResponses = respSheet.getLastRow();
var currentemail = Session.getActiveUser().getEmail();
var c=value[0]; var d=value[1];
var e=c + "," + d ;
//respSheet.getRange(numResponses,6).setValue(e);
//respSheet.getRange(numResponses,8).setValue(currentemail);
var response = Maps.newGeocoder().reverseGeocode(value[0], value[1]);
var f= response.results[0].formatted_address;
//respSheet.getRange(numResponses,7).setValue(f);
respSheet.getRange(numResponses,6,1,3 ).setValues([[ e, f, currentemail ]]);
}
//
index.html
<!DOCTYPE html>
<html>
<script>
(function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
}
})()
function showPosition(position){
var a= position.coords.latitude;
var b= position.coords.longitude;
var c=[a,b]
getPos(c)
function getPos(value){
google.script.run.getLoc(value);
}
}
</script>
<body>
<p>Please ensure your GPS is on to record your location. You can generate the report from website to check. Pl. close this window (version 3)</p>
</body>
</html>
From the question
I want to save the email ID (or emp no) of the user filling the form. But the email ID is not getting captured into the form. If I fill the form, the email ID is captured. For other users it is not captured. I don't want all the users to authenticate the script (to run the script as the logged in user). It must be captured by some other way. Is it possible?
On a web application created using Google Apps Script to automatically get the user email ID you could set your web application to be executed as the user running the application instead being executed as you but if don't want to use this feature then you have to set your own authentication process.
From the question
If the GPS is not captured (it is empty), I want to display a different message in the HTML page. How to do it?
Use a JavaScript conditional expression
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
alert('Can\'t get the position');
}
})()
function showPosition(position){
var a= position.coords.latitude;
var b= position.coords.longitude;
var c=[a,b];
getPos(c);
function getPos(value){
google.script.run.getLoc(value);
}
}
The above code uses alert but you could use the DOM.
Resources
Web Apps | Google Apps Script
Document Object Model (DOM)
I was able to make a complete solution without any google form (just HTML) and managed to display an alert message also. The "Login" is still not possible.
Code.gs
It runs the form and saves the answers in the required columns into google sheet.
It runs faster than google form and "Submit" has to be clicked only once.
As the saving happens by "append row", the jumbling of data (between rows) which was happening in my earlier method is avoided.
/* #Include JavaScript and CSS Files */
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
/* #Process Form */
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/...../edit#gid=52499297";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Location");
var response = Maps.newGeocoder().reverseGeocode(formObject.lat, formObject.long);
var address= response.results[0].formatted_address;
ws.appendRow(
[
new Date(),
formObject.empno,
formObject.punch,
formObject.rem,
"",
formObject.lat+","+formObject.long,
address
]
);
}
Index.html
This has the questions.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<?!= include('JavaScript'); ?>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-6">
<form id="myForm" onsubmit="handleFormSubmit(this);">
<p class="h4 mb-4 text-left">Record Attendance and Location</p>
<div class="form-group">
<label for="empno">Emp No - Click to see list</label>
<input type="number" class="form-control" id="empno" name="empno" min="1" max="9999999" required>
</div>
<div class="form-group">
<label for="punch">Punch (Select one)</label>
<select class="form-control" id="punch" name="punch" required>
<option selected disabled hidden style='display: none' value=''></option>
<option value="In">In</option>
<option value="Out">Out</option>
<option value="Started">Started</option>
<option value="Reached">Reached</option>
</select>
</div>
<div class="form-group">
<label for="rem">Remark</label>
<input type="text" class="form-control" id="rem" name="rem">
</div>
<div class="form-group">
<input type="hidden" class="form-control" id="lat" name="lat">
<input type="hidden" class="form-control" id="long" name="long">
</div>
<button type="submit" class="btn btn-primary btn-block">Submit</button>
</form>
<div id="output"></div>
</div>
</div>
</div>
</body>
</html>
JavaScript.html
This processes the answers
<script>
function showPosition() {
navigator.geolocation.getCurrentPosition(showMap);
}
function showMap(position) {
// Get location data
var lat = position.coords.latitude;
var geo1 = document.getElementById("lat");
geo1.value = lat;
var long = position.coords.longitude;
var geo2 = document.getElementById("long");
geo2.value = long;
}
// Prevent forms from submitting.
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
window.addEventListener('load', showPosition);
function handleFormSubmit(formObject) {
google.script.run.processForm(formObject);
document.getElementById("myForm").reset();
alert('Data saved successfully');
}
</script>
I've created a pop-up email dialog box within google's html editor as follows with the input for email as follows:
<input type="email" name="email" class="form-control" id="mail" aria-describedby="emailHelp" value="">
In my .gs file I'm storing the value of my cell containing the email address I want to use as follows:
function getEmail()
{
var s=SpreadsheetApp.getActive().getSheetByName("Template");
var row=15;
var column=3;
var contactAddress=Utilities.formatString('%s',s.getRange(row, column).getValue());
Logger.log(contactAddress);
}
This works fine and is capturing the email address correctly and logging it. I now need to change the 'value' of my email input so that it populates with this address when the diolog opens. So I have the following in my HTML file:
window.onload = function (contactAddress)
{
document.getElementById('mail').value=contactAddress;
}
However, this is resulting in '[object Event]' being populated. I feel I'm close here but can't quite get it over the line!!!!
UPDATE:
So I added this to my .gs:
function showDialog() {
var html = HtmlService.createHtmlOutputFromFile('emailTemplate')
.setWidth(800)
.setHeight(500);
html.myvar = new getEmail();
html.evaluate().getContent();
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.showModalDialog(html, ' ');
}
However, when I run the script I get an error stating Object does not allow properties to be added or changed.
Edit: Original answer deleted.
So here is my answer to your problem. Tested it out, and it works.
My HTML page is set up like this just to test it:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess(contact) {
//var contactAddress = google.script.run.getEmail();
document.getElementById('mail').value=contact;
}
google.script.run.withSuccessHandler(onSuccess).getEmail();
</script>
</head>
<body>
<input type="email" name="email" class="form-control" id="mail" aria-describedby="emailHelp" value="">
</body>
</html>
My .gs file contains
function getEmail()
{
var s=SpreadsheetApp.getActive().getSheetByName("Sheet1");
var row=1;
var column=3;
var contactAddress=Utilities.formatString('%s',s.getRange(row, column).getValue());
Logger.log(contactAddress);
return contactAddress;
}
The only real difference is that I added a return statement (and changed the row from 15 to 1 for my test).
Seems the main problem was how you were calling the function on the HTML page. It needed a google.script.run.withSuccessHandler() call instead of a window.onload = function() call.
Currently I'm using an example to create an upload function for my webpage on Google Apps Script. This is the code:
Code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('form.html');
}
function uploadFiles(form) {
try {
var dropbox = "Test Files";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var blob = form.myFile;
var file = folder.createFile(blob);
file.setDescription("Uploaded by " + form.myName);
return "File uploaded successfully " + file.getUrl();
} catch (error) {
return error.toString();
}
}
form.html
<form id="myForm">
<input type="text" name="myName" placeholder="Your name..">
<input type="file" name="myFile">
<input type="submit" value="Upload File"
onclick="this.value='Uploading..';
google.script.run.withSuccessHandler(fileUploaded)
.uploadFiles(this.parentNode);
return false;">
</form>
<div id="output"></div>
<script>
function fileUploaded(status) {
document.getElementById('myForm').style.display = 'none';
document.getElementById('output').innerHTML = status;
}
</script>
<style>
input { display:block; margin: 20px; }
</style>
This code works fine by itself but when I try and implement it on my webpage with the current existing code: https://jsfiddle.net/05nmqy63/
It won't work like shown in the example. The example uploads a file into a folder in my Google Docs but when put into my code the page changes but it doesn't upload anything nor does it say the file has been submitted.
How do I fix this? Or is there an easier way to implement an upload button? (I want the submit order button to be able to function as the upload button)
Figured out the problem to my question after finding out what was wrong. The error I received was this: Uncaught InvalidArgumentError: Failed due to illegal value in property: 0
This error was solved by another person on this forum: google.script.run not working: Uncaught InvalidArgumentError: Failed due to illegal value in property: 0
so I'm trying to set up upload size limit, but it has been unsuccessful.
I have included the code with explanations, please hava a look and I would be very thankfull if you could help me.
More information on wha I needм help with is after the " // "
Here's the code: `
<html>
<p id="check"></p>
//ok so this part of <script> sends the user to "email.html"
<script type="text/javascript">
function getFile(){
document.getElementById("file").click();
}
function sub(obj){
var file = obj.value;
document.myForm.submit();
}
</script>
//here's the code for the button to upload a file (or image in my case)
<form action="e-mail.php" method="post" enctype="multipart/form-data" name="myForm">
<div id="yourBtn" onclick="getFile()">Yes</div>
<div style="text-align: center; overflow: hidden;">
<input type="file" value="upload" id="file" accept="image/*"
onchange="sub(this)"
size="1" style="margin-top: -50px;" "margin-left:-410px;" "-moz-opacity: 0;
"filter:
alpha(opacity=0);" "opacity: 0;" "font-size: 150px;" "height: 100px;">
</div>
</form>
<script>
var attachement = document.getElementById('file');
attachement.onchange = function() {
var file = attachement.files[0];
if (file.size < 1000000) {
function sub(obj){return true; }
//ok so here's the problem,
when I include this code between
'script' the user is not taken
to "e-mail.html" anymore... please help!!!
else { return false;}
}
}
</script>
</html> `
Thanks a lot:)
To go to a different page when the file is too big, you can assign the new URL to document.location. Note that the URL should be absolute (i.e. http://.../email.html).
I suggest to display an error when the file is too big and simply not submit the page. Otherwise, the user will see the new page and believe that everything was all right.
Also note that you need to do the same check on the server because an attacker might just create a POST request from scratch (without using the code from your page) to send files of arbitrary size to your server.
Because the funtion inside of the onchange is not global. It is only available to the onchange.
would would need to change it to
window.sub = function (obj){return true; }
BUT the flaw with this is the user can change the file a second time and submit since you just removed the return false. You could either add it back in on the else OR you can do validation when the form is submitted and not onchange.