HTMLService forms and responses - google-apps-script

New to Google scripts. I cannot for the life of me get a Google script for forms working. The documentation is not helpful and I could not find any complete examples.
In sheets, I went to the script section. I created a basic "hello world" html. I added a function useMyForm, that will call on the script that has the form in it. After submit I cannot get the form to call the other script that has onFormSubmit.
Can someone write me a form with one entry and a call to the function that will process the data? Maybe my functions shouldn't be in separate scripts.
I write c, php, java..... this google script stuff is killing me.

Here is the sample code from the documentation, if there is something you don't understand please post your own version of the code that is not working:
https://developers.google.com/apps-script/guides/html/communication#forms
Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
function processForm(formObject) {
var formBlob = formObject.myFile;
var driveFile = DriveApp.createFile(formBlob);
return driveFile.getUrl();
}
index.html
<script>
function updateUrl(url) {
var div = document.getElementById('output');
div.innerHTML = 'Got it!';
}
</script>
<form id="myForm">
<input name="myFile" type="file" />
<input type="button" value="Submit"
onclick="google.script.run
.withSuccessHandler(updateUrl)
.processForm(this.parentNode)" />
</form>
<div id="output"></div>
The one thing that took me a while to grasp is that when you run google.script.run.withSuccessHandler(updateUrl).processForm(this.parentNode)
.processForm(variable) will run a function in the .gs file.
The return from that function (in the .gs file) gets sent to the .withSuccessHandler(updateUrl) function. Which in the example above is the "url" variable from function updateUrl(url)

Related

Control Flow Between Server and Client and Back to Server for Google Apps Script

I have a question about the control flow between some JavaScript code running as bound functions within a google spreadsheet - so server side - and a dialog (that happens to be Modal, but Modeless is the same) that is client side.
While the code examples below work fine in that the dialog successfully calls the server side function as per the line below, and the withSuccessHandler works too.
google.script.run.withSuccessHandler(success_callback).getCredentials(this.parentNode)
But what I actually want to achieve is for some server side code to carry on executing once the dialog has gone; ideally from the point the .showModalDialog() function was called, but I'd be happy just passing control back to any server-side function.
Some example software is below; don't forget this works, just not how I want it too! Essentially the event handler for a menu item created by the the OnOpen() function calls a modal dialog to prompt the user for security credentials.
var html = HtmlService.createHtmlOutputFromFile('authorization_dialog');
SpreadsheetApp.getUi()
.showModalDialog(html, 'Authorization Dialog');
The HTML file:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<form>
Authorization Code:
<input type="text" name="authorization_code"><br><br>
Account ID:
<input type="text" name="account_id"><br><br>
Enter account details...
<br>
<br><br>
<input type="button" value="OK"
onclick="google.script.run.withSuccessHandler(success_callback).getCredentials(this.parentNode)" />
<input type="button" value="Close"
onclick="google.script.host.close()" />
</form>
<script>
// Using this call back prevents the need to hit the Close Button after OK.
function success_callback() {
google.script.host.close(); // Close the dialog.
}
</script>
</body>
</html>
If you don't need a response from the server-side function, simply omit 'withSuccessHandler';
function func_client(){
google.script.run.func_server();
google.script.host.close();
}
In this case, the server-side code will continue executing without locking your client's UI - you can call any other functions inside 'func_server'.
If you'd like to process a response from the first function call, call the second function from 'success_callback'. The dialog will be closed without waiting for the google.script.run to complete, but the server code will continue executing.
In the example below, the 1st server function passes form data back to the client where 'success_callback' immediately invokes another server function that takes a while to complete (it logs each file in my Google Drive);
Client:
<form id="form">
.....
<input type="submit" id="ok" value="OK" />
<input type="button" value="Close" onclick="google.script.host.close()" />
.....
</form>
<script>
window.onload = function(){
document.getElementById("form")
.addEventListener("submit", function(e){
e.preventDefault();
google.script.run
.withSuccessHandler(success_callback)
.logFormData(this);
});
}
function success_callback(response) {
console.log(response);
google.script.run.scanFiles();
google.script.host.close(); // Close the dialog.
}
</script>
Server:
function showDialog(){
var ui = SpreadsheetApp.getUi();
//IMPORTANT: client-side scripts won't be executed
//without calling evaluate() on the HtmlTemplate object before passing it to UI;
var template = HtmlService.createTemplateFromFile("dialog");
var html = template.evaluate();
ui.showModalDialog(html, "dialog");
}
function logFormData(formData){
return formData;
}
function scanFiles() {
var files = DriveApp.getFiles();
var file;
while (files.hasNext()) {
file = files.next();
Logger.log(file.getName() + ": " + file.getMimeType());
}
}

Google apps script get user input with no length limit

I want to take user input (HTML specifically) using either:
var ui = SpreadsheetApp.getUi();
var response = ui.prompt('Paste HTML below');
or
var input = Browser.inputBox('Paste HTML below', Browser.Buttons.OK_CANCEL);
These work fine for small inputs, however when copying over the entire HTML for a page of interest an error occurs (in each case). This error cannot be caught, it simply crashes the script.
Do you know why this is happening? I can't find anything in the docs that mention limits on input size.
Any experience doing this a different way?
Edit: as per a suggestion in the comments, I have tried another method (below). This also fails (with no error message) when passed large input.
First I set up Page.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Paste Sitemap Content Below
<textarea id="user-input-box" rows="4" cols="50"></textarea>
<script>
function logToConsole() {
var userInput = document.getElementById("user-input-box").value;
google.script.run.doSomething(userInput);
}
</script>
<input type="button" value="Close" onclick="logToConsole();google.script.host.close();" />
</body>
</html>
Then in file Code.gs
function testDialog() {
var html = HtmlService.createHtmlOutputFromFile('Page')
.setWidth(400)
.setHeight(300);
SpreadsheetApp.getUi()
.showModalDialog(html, 'My custom dialog');
}
function doSomething(userInput){
Logger.log(userInput);
}
I just ran into the same problem and couldn't log the error. In my case as is yours, you're calling your logToConsole() function and then directly after you're closing the dialog by using google.script.host.close();
google.script.host.close() is the problem. For some reason it can cancel the script execution - this typically happens when you're sending a lot of data back. The trick is to use a successHandler when you call your script which then calls google.script.host.close(). This way, the data transfer from the dialog finishes correctly and when you call withSuccessHandler(), that callback closes the dialog. Try this amendment to your code:
<script>
function logToConsole() {
var userInput = document.getElementById("user-input-box").value;
google.script.run.withSuccessHandler(closeDialog).doSomething(userInput);
}
function closeDialog() {
google.script.host.close();
}
</script>
<input type="button" value="Close" onclick="logToConsole()" />

Upload a file to google drive by an anonymous user

I use a Google Apps Script for that.
Code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('form.html').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function uploadFile(form) {
var folderId = "folder_id";
try {
var folder = DriveApp.getFolderById(folderId);
var blob = form.picToLoad;
var file = folder.createFile(blob);
return "File uploaded successfully " + file.getUrl();
} catch (error) {
Logger.log(error);
return error.toString();
}
}
form.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1 id="main-heading">Main Heading</h1>
<br/>
<div id="formDiv">
<form id="myForm">
<input name="picToLoad" type="file" /><br/>
<input type="button" value="Submit" onclick="picUploadJs(this.parentNode)" />
</form>
</div>
<div id="status" style="display: none">
Uploading. Please wait...
</div>
</body>
<script>
function picUploadJs(frmData) {
document.getElementById('status').style.display = 'inline';
google.script.run
.withSuccessHandler(updateOutput)
.uploadFile(frmData)
};
function updateOutput() {
var outputDiv = document.getElementById('status');
outputDiv.innerHTML = "The File was UPLOADED!";
}
</script>
</html>
It all works fine when I'm authenticated within my domain (I use G Suite).
However, if I'm logged into Google as another user (e.g. a normal Gmail user) or not logged at all, I still can access the page, but the script doesn't execute properly with the following error in the console:
Error
google.script.run.withSuccessHandler(...).processForm is not a function
at picUploadJs (VM84 userCodeAppPanel:9)
at HTMLInputElement.onclick (VM104 userCodeAppPanel:1)
No additional logs are shown in the Log at the level of Apps Script.
I have deployed the script as:
Execute the app as: Me (and authorised it)
Who can access the app: Anyone, even anonymous
So, I think all should work fine and anyone should be able to upload a file to my drive. Unfortunately that's not the case.
Again, this happens only when I access the script from outside of my domain.
Can anyone see what's wrong?
As it was suggested in the comments by Zig Mandel, making a copy of the script and running it solved the problem. This restarted the process of authorisation so perhaps there was something wrongly initiallised with the permissions.

HTML Service - Uncaught NetworkError: Form submission failed

I am working through the google example code for HTML Service: Communicate with Server Functions.
I can't get the sample code to work for "Forms'. Is there an error in the code or is it something in my browser config?
The code is -
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function processForm(formObject) {
var formBlob = formObject.myFile;
var driveFile = DriveApp.createFile(formBlob);
return driveFile.getUrl();
}
index.html
<script>
function updateUrl(url) {
var div = document.getElementById('output');
div.innerHTML = 'Got it!';
}
</script>
<form id="myForm">
<input name="myFile" type="file" />
<input type="button" value="Submit"
onclick="google.script.run
.withSuccessHandler(updateUrl)
.processForm(this.parentNode)" />
</form>
<div id="output"></div>
The error in the debug windows of my browser is
Uncaught NetworkError: Form submission failed.
Thanks in advance. Will Brown.
This error is not due to a code problem at all, rather it is an interaction between a browser plug-in and Google's authentication services. See Issue 3339 and Issue 4394.
If you're using the LastPass password manager plugin, it must be disabled.
PS: #user3531933 - if you add your own answer, just leave a comment for me, and I'll happily delete this and leave it to you.

"getCalendarById" is not defined

What is wrong with my Code.gs that gives me "getCalendarById" is not defined? Also If I add CalendarApp. in front of the getCalendarById method the whole thing breaks and the URL returns nothing but a drive error.
Solution:
I do have to add CalendarApp in front of getCalendarById I read the doc wrong (doc link). In that doc under the section 'Granting Access Rights' I read -
Scripts do not request authorization if they...or if you access the script as a web app that runs under the script owner's user identity.
I was running the script under my own identity but failed to realize that the quote above meant it will not go out and get authorization, not that it still requires you to get it yourself . Many thanks to Serge insas for his great help, sticking with me and understanding.
Code.gs:
function doGet() {
return HtmlService.createHtmlOutputFromFile('index.html').setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
function listEvents(dateSelected) {
var calendar = getCalendarById('en.usa#holiday#group.v.calendar.google.com').getEventsForDay(new Date(dateSelected)); // if I put calendarapp. the whole thing returns an error from the URL
Logger.log('Number of events: ' + calendar.length);
}
html:
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/themes/redmond/jquery-ui.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js"></script>
<div class="container" >
<div>
<h1>Welcome</h1>
<p>Please select a date below.</p>
<p>Click Here: <input type="text" name="date" id="datepicker" /><p>
</div>
</div>
<div id='test'></div>
<script>
$("#datepicker").datepicker();
$("#datepicker").on("change", function () {
var dateSelected = $(this).val()
$("#test").text(dateSelected);
google.script.run.listEvents(dateSelected);
});
</script>
getCalendarById() is a method of the calendarApp service, the correct syntax is as follows :
var calendar = CalendarApp.getCalendarById("xxxxxxxx");
See doc here