Auto submit google form after a time limit - google-apps-script

I want to use app script in my Google form to automatically submit the form in 20 minutes if the user doesn't click on submit within 20 minutes. Anyway to implement this????

No, you cannot control the client-side of Google Forms, even if you add an Apps Script to it, because Apps Script runs on the server.
One possible solution is to serve your form as a Google Apps Script web app. At that point you can write client-side JavaScript and use window.setTimeout to submit the form after 20 minutes.
Here are some example files, Code.gs and quiz.html, that can provide a basic skeleton to start the web app. A blank project will have Code.gs as the default file, then you have to add File > New > HTML file to start the other file.
You can enter the id of any spreadsheet you own in the commented out lines in Code.gs to append the response into that spreadsheet. (You can also automate that process by creating a new spreadsheet as needed. Example of creating spreadsheet to hold data for Apps Script example can be found here.
// file Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("quiz");
}
function doPost(request) {
if (request.answer) {
console.log(request.answer); // View > Execution transcript to verify this
//var ss = SpreadsheetApp.openById(id).getSheetByName("Quiz Responses");
//ss.appendRow([request.answer /* additional values comma separated here */ ]);
}
}
<!DOCTYPE html>
<!-- file quiz.html -->
<html>
<head>
<base target="_top">
</head>
<body>
<h1>Quiz</h1>
<form>
What is Lorem Ipsum?
<input name="loremipsum" type="text"/>
<button>Submit</button>
</form>
<script>
const button = document.querySelector("button");
const timeLimitMinutes = 1; // low number for demo; change to 20 for application
const timeLimitMilliseconds = timeLimitMinutes * 60 * 1000;
// For this demo we are not going to serve a response page, so don't try to.
button.addEventListener("submit", submitEvent => submitEvent.preventDefault());
// attach our custom submit to both the button and to the timeout
button.addEventListener("click", submitForm)
window.setTimeout(submitForm, timeLimitMilliseconds)
function submitForm() {
button.setAttribute("disabled", true);
document.querySelector("h1").textContent = "Quiz submitted";
// for demo: submitting just a single answer.
// research Apps Script documentation for rules on submitting forms, certain values not allowed
// consider a helper function `makeForm()` that returns a safe object to submit.
const answer = document.querySelector("input").value;
google.script.run.doPost({ answer });
}
</script>
</body>
</html>
Test with Publish > Deploy as web app...

Related

Is there a way to grab a result array from a google app script and generate rows the result array has in an html? [duplicate]

I have a google spreadsheet, where some rows append on daily basis and using the google spreadsheet, the customer feedback team follows up.
Google Spreadsheet Data.
https://docs.google.com/spreadsheets/d/1V-XZdCUZAQVkfCat9vXVxITjjNMxNMPDin6B5j9uMWY/edit?usp=sharing
The above mentioned Google Spreadsheet always have the below mentioned data at google sheet (Highlighted in blue):
Ref ID
Company Name
Contact No.1
Contact No.2
Project Name
Agent ID
Rest of the mentioned details would be captured from the HTML UI basis the user response and finally click on 'Submit & Next' or 'Next' the input get stored at google sheet.
The User has to first enter the 'Agent Id' on HTML UI and accordingly one by one Ref ID detail would be given to particular 'Agent Id` user.
As mentioned in the attached screenshot, The left side of the information would be static as per the googlespread sheet, and right hand side information would be filled by the user basis the telephonic conversation.
Below mentioned particulars will be drop down or radio options basis user input:
Product : Lite, Lite-I, Elite
Ref Code: LIT-1, LIT-2, LIT-3
Status : Accept, Reject, Pending
Comment : Satisfied, Call Back, Pending
Below mentioned particulars will be derived:
Days Passed: It will be derived from the current system year - year mentioned in the `Date`
Below mentioned particulars will be user input as a free text.
Client Name
Notes
Final_Status
Note: The agents will be assigned and shown only those Ref ID where the Agent ID is not blank and Final_Status is either blank or other than 'Submit & Next' marked in Googlespread Sheet.
We need to add one more column in the Googlespread sheet, Which capture the Date time stamp as per the system date as soon as the Final_Status marked as 'Submit & Next` or 'Next'
Submit & Next button would only be enable if all the details are captured by user.
Next Button would only be enable if Comment option is selected.
Also, If there is no new rows available in the googlesheet for the data entry using UI, the UI will throw the message to User that there is 'No New task available' on a blank screen by clicking on 'Submit & Next' or 'Next' button.
Expected UI:
It looks like Google Apps Developer docs have a decent guide for something similar to this:
Useful for building web apps or adding custom user interfaces in Google Docs, Sheets, and Forms.
https://developers.google.com/apps-script/guides/html
Looks like you need to grant Google Apps Scripts access, then add one:
https://developers.google.com/apps-script/guides/standalone
You can create a standalone script, or manually connect your project:
Go to Google Drive and click New > More > Connect more apps.
When the "Connect apps to Drive" window appears, type "script" into the search box and press Enter.
Click Connect next to the listing for Google Apps Script.
Google's script example says your scripts would look something like this:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Dialog')
.addItem('Open', 'openDialog')
.addToUi();
}
function openDialog() {
var html = HtmlService.createHtmlOutputFromFile('Index');
SpreadsheetApp.getUi()
.showModalDialog(html, 'Dialog title');
}
with the corresponding HTML:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
My Google Sheets Interface.
<input type="button" value="Close"
onclick="google.script.host.close()" />
</body>
</html>
There seems to be good documentation here:
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app
so I'd say that's a good place to start. What you're trying to achieve looks doable for sure, I think it'll be a matter of tweaking it to what google scripts supports.
Good luck!!
Data Entry Dialog Created from Header Info on Spreadsheet
Code.gs:
function onOpen() {
SpreadsheetApp.getUi().createMenu("My Menu")
.addItem('Launch Dialog','launchTheFormAsDialog')
.addToUi();
}
function buildForm() {
var searchColumnName='RefId';
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var tA=sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
var hA=sh.getRange(2,1,1,sh.getLastColumn()).getValues()[0];
tA.splice(1,5);
var ftA=tA.slice();
hA.splice(1,5);
var fA=hA.slice();
var dstr=Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "yyyy-MM-dd");
var html='<style>input{margin:2px 5px 2px 0;}</style><form id="myForm">';
for(var i=0;i<fA.length;i++) {
switch(ftA[i]){
case 'date':
html+=Utilities.formatString('<br /><input type="%s" value="%s" name="%s" /> %s',ftA[i],dstr,fA[i],fA[i]);
break;
default:
html+=Utilities.formatString('<br /><input type="%s" name="%s" /> %s',ftA[i],fA[i],fA[i]);
break;
}
}
html+='<br /><input type="button" value="Submit" onclick="submitForm(this.parentNode)" /></form>';
return {html:html};
}
function testUpload() {
upload({'Status':'none', 'Comment':'to long to fit', 'ClientName':'Don Trump', 'RefCode':'Tweeter', 'Final_Status':'impeachment', 'Product':'Bullshit', 'RefId':'id3', 'DaysPassed':'12', 'Final_Status_Date':'2019-12-23', 'Date':'2019-12-23', 'Notes':'none'})
}
function upload(theForm) {
Logger.log(theForm);
var kA=Object.keys(theForm);
kA.splice(kA.indexOf('refId'),1);//remove refID
Logger.log(kA);
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var hA=sh.getRange(2,1,1,sh.getLastColumn()).getValues()[0];
var hObj={};
hA.forEach(function(e,i){hObj[e]=i+1});
Logger.log(hObj);
var vA=sh.getRange(3,1,sh.getLastRow()-2,2).getValues();
for(var i=0;i<vA.length;i++) {
if(theForm.RefId==vA[i][0]) {
kA.forEach(function(key){
Logger.log(hObj[key]);
Logger.log(theForm[key]);
sh.getRange(i+3,hObj[key]).setValue(theForm[key]);
});
}
}
return buildForm();
}
function launchTheFormAsDialog() {
var ui=HtmlService.createHtmlOutputFromFile('theform').setHeight(550);
SpreadsheetApp.getUi().showModelessDialog(ui, "Form Data Entry");
}
theform.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function() {
$(function(){
google.script.run
.withSuccessHandler(function(obj){
$('#formDiv').html(obj.html);
})
.buildForm();
});
});
function submitForm(frmData) {
google.script.run
.withSuccessHandler(function(obj){
//console.log('flag1');
$('#formDiv').html(obj.html);
})
.upload(frmData);
}
function updateSelect(vA,id){
var id=id || 'sel1';
var select = document.getElementById(id);
select.options.length = 0;
for(var i=0;i<vA.length;i++) {
select.options[i] = new Option(vA[i][1],vA[i][0]);
}
}
console.log('My Code');
</script>
</head>
<body>
<h1 id="main-heading">Form Data Entry</h1>
<div id="formDiv"></div>
</body>
</html>
My Spreadsheet:
I added input data types so that I could initialize the date fields and so that the form could be completely built from data on the Spreadsheet even if columns are added or moved around. You can always hide that row since it's on the top.
The Dialog:
If you are familiar with client-side coding frameworks like Angular or ReactJs, the best option is turning the Google sheet into a rest API and use that from client application. Sheety is the best free tool to do that.
Otherwise, use the Google Apps script to link a form to a sheet. All are explained here step by step.
Found this on medium.com https://medium.com/#jaejohns/how-to-use-google-sheets-as-your-website-database-b0f2f13d0396
Could be helpful.

Bookmarklet hosted in Apps Script unable to run when shared

After deploying my web app in Apps Script with bookmarklet, it works fine on my end however not when I tried to share my web app, it throws an 'unsafe javascript' error like its being clicked from the web app and not as a bookmarklet.
The web app is run as user accessing and can be accessed by anyone in our organization.
What bookmarklet does is run a prompt for input and find it on the current page.
The code goes like this:
<a id="bkmark">Link</a>
<script>
document.body.onload=()=>{
google.script.run.withSuccessHandler(rep).getLink();
function rep(e){
document.querySelector('#bkmark').href = e; // return a javascript: IIFE wrapped in ``
}
}
</script>
And my gs returns Html output from a file.
Any help appreciated, Thanks!
Instead of adding the JavaScript as a booklet, you can directly import using ContentService and templates:
Page.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script src="<?= ScriptApp.getService().getUrl() + '?getAction=true' ?>" type="text/javascript" defer async></script>
</head>
<body>
<button id="trigger" onclick="doAction()" disabled>Click here to do something</button>
</body>
</html>
Code.js
function doGet(e) {
const { getAction } = e.parameter
if (getAction !== undefined) {
return genAction()
}
return HtmlService.createTemplateFromFile('Page').evaluate().setTitle("Booklet webapp")
}
function genAction() {
return ContentService
.createTextOutput(`
function doAction() {
alert("Hello world!")
}
(function loaded() {
document.getElementById("trigger").disabled = false
})();
`)
.setMimeType(ContentService.MimeType.JAVASCRIPT)
}
Notice that the script tag has defer and async. This makes it so the script doesn't start loading after the DOM has been fully loaded and it starts doing so asynchronously (without blocking the main thread).
On the Apps Script side, we use a template so we can dynamically get the script execution URL (with ScriptApp.getService().getUrl()) and we add a query parameter (getAction) so we can detect it on the doGet(e) function. There we check for that parameter and we have it we return a JavaScript content instead.
Also made the button disabled by default and I added a small code so the button get enabled when the script has finished loading (it's asynchronous so it can take some time to fully load).
This technique can also be used to dynamically add an script tag by fetching the result, creating the script element, and adding the contents manually.
References
Class ContentService (Google Apps Script reference)
Class HtmlTemplate (Google Apps Script reference)
HTML Service: Templated HTML (Google Apps Script guides)
HtmlService.createTemplateFromFile(filename) (Google Apps Script reference)
Service.getUrl() (Google Apps Script reference)

"SpreadsheetApp.getUi() cannot be called from this context"

In a Google Sheets spreadsheet, I want to show a modal dialog created from HTML, then run a function, then close that HTML prompt automatically.
The dialog should stay until the function finishes executing, then automatically disappear.
This process has to be repeated every 3 hours, and the script needs to run as me (as I have edit permissions that other users do not) so simple triggers probably won't work (I've read that you must create an installable trigger if you want the function to run as you and not whoever the current user is at the given time)
I currently have:
A .gs function Magic_Telling, that creates a modal dialog by using an HTML file
An HTML file, Prompt_Styling, that contains the css / html styling for the prompt. This HTML file then calls a .gs function All_In that processes the rows
My code:
Magic_Telling
Creates the modal dialog from HTML file.
function Magic_Telling() {
var UI = SpreadsheetApp.getUi();
var newline = '\n'
// Display a modal dialog box with custom HtmlService content.
var htmlOutput = HtmlService.createHtmlOutputFromFile('PromptStyling')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(300)
.setHeight(100);
UI.showModalDialog(htmlOutput, ' ');
}
Prompt_Styling HTML file for styling prompt + script that runs the function All_In that will process rows
<html>
<head>
// some irrelevant stuff here
</head>
<script>
window.onload = function() {
google.script.run
.withSuccessHandler(closeDialog)
.All_In();
};
window.closeDialog = function() {
google.script.host.close();
};
</script>
</html>
All_In Function to process rows
function All_In() {
UnlockRowBlocks();
UnhideRowBlocks();
LockRowBlocks();
HideRowBlocks();
}
When I run MagicTelling from the script editor, it works beautifully. The entire sequence executes (prompt shown, All_In executed, prompt disappeared). Perfect.
I then created an installable trigger by going to
Script Editor > Resources > Current project's triggers
and added a trigger to run Magic_Telling every 3 hours.
(I presume this is an "installable trigger")
But I get this error message:
Cannot call SpreadsheetApp.getUi() from this context.
...when the function reaches the first line of Magic_Telling
What should I do to get around this?
Ui Dialogs can not be called by time triggered functions, they have to be triggered by a user action, that's to say a click on a menu item or some sort of button that calls the function showing the UI.
Simple case getting the 'Cannot call SpreadsheetApp.getUi() from this context.'-Error for everybody who just got started with scripting using the Tools > Script editor Menu.
In this case you work with a standalone script only, meaning, your script is just attached to one document or spreadsheet.
The standalone script allows i.e. to simply call doc = DocumentApp.getActiveDocument(), the active Document the script is attached to.
It happened to me that I used var ui = SpreadsheetApp.getUi(); while getting the ERROR message in quest here...
and it took me hours to find out what went wrong with this simple line as I went down all the way to Oauth Scopes and the Developer Console.
So, it might be helpful for some beginners to know that I actually used the var ui = SpreadsheetApp.getUi(); within a Document-Script.
Very clear I got the error, but ...
Hope this is helpful for some simple scripters!
PS. I hope it is needless to mention that using a var ui = DocumentApp.getUi();
within a SpreadSheet will produce a similar Error message.
If this error happened then check the triggers or close the script editor tab and refresh google sheets then open the script project .
Make sure that the Apps Script is bound script and not standalone script . or the getUi() won't work .
If you need to periodically show a message or notification to a user, instead of using a time-driven trigger for calling the Class Ui, use a sidebar and client-side code, i.e. setTimeout in a recursive function, to call a server side function that calls the Class Ui. You might also show the message in the sidebar.
In the case of spreadsheets another option might be use Spreadsheet.toast. Another option is to edit the document. This might work in small documents where the edited section is shown all the time.
When running function calling Class Ui it will fail if the corresponding document editor UI and the Google Apps Script Editor hasn't a connection between an active document, form, slide or spreadsheet and the script.
Time-Driven triggers have a connection with the container / bounded file but there isn't one with the document editor UI, no matter if the script was opened from the document editor UI at the time that the time-driven trigger was executed.
This error will happen too when calling Class Ui from a standalone project because there is no connection with a document editor user interface. While the Google Apps Script editor might look as a "document editor", Class Ui doesn't work with it as the Class Ui can only be called from DocumentApp, FormApp, SlidesApp and the SpreadsheetApp classes.
Below is a simple sample. It adds a custom menu used to open a sidebar. The sidebar holds the client-side code that will open a modal dialog every 10 seconds for 3 times. The client-side code has a timer function that holds a setTimeout which calls the controller function which calls the server-side function and updates a counter used to limit the number of times that the server-side function will be executed.
Steps to use this code:
Create a new spreadsheet
Click Extensions > Apps Script
Remove the default content from default .gs file and add the Code.gs code.
Add two html files and name them sidebar and 'modalDialog.
Add the html sidebar.html and modalDialog.html to the corresponding files.
Run onOpen or reload the spreadsheet and click My Menu > Show sidebar (reloading the spreadsheet will will close the script editor) and authorize the script.
On the spreadsheet, click My Menu > Show sidebar
Code.gs
/**
* Adds a custom menu to show the sidebar
*/
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('My Menu')
.addItem('Show Sidebar', 'showSidebar')
.addToUi()
}
/**
* Shows the sidebar
*/
function showSidebar() {
const myHttpOutput = HtmlService.createHtmlOutputFromFile('sidebar')
.setTitle('My Sidebar')
SpreadsheetApp.getUi().showSidebar(myHttpOutput);
}
/**
* Shows the modal dialog. To be called from client-side code.
*/
function showModalDialog() {
const myHttpOutput = HtmlService.createHtmlOutputFromFile('modalDialog')
.setWidth(400)
.setHeight(150)
SpreadsheetApp.getUi().showModalDialog(myHttpOutput, 'My Modal');
}
sidebar.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<style>
.error {
color : red;
background-color : pink;
border-style : solid;
border-color : red;
}
</style>
<body>
<h1>Timed Modal Dialog Controller</h1>
<div id="sidebar-status">
The modal dialog will be shown after the specifed timeout interval.
</div>
<script>
const defaultInterval = 10000;
let count = 0;
/**
* Run initializations on sidebar load.
*/
(() => {
timer();
})();
/**
* Calls the controller function at the given interval.
*
* #param {Number} interval (optional) Time in ms between polls. Default is 2s (2000ms)
*
*/
function timer(interval) {
interval = interval || defaultInterval;
setTimeout(() => {
controller();
}, interval);
};
/**
* Calls the server side function that uses Class Ui to
* show a modal dialog.
*/
function controller(){
/** Maximum number of iterations */
const max = 3;
if(count < max){
google.script.run
.withSuccessHandler(() => {
const msg = `Counter: ${++count}`;
showStatus(msg);
timer();
})
.withFailureHandler(error => {
const msg = `<div class="error">${error.message}</div>`;
showStatus(msg);
})
.showModalDialog();
} else {
const msg = `<p>Maximum reached.</p>`;
showStatus(msg)
}
}
/**
* Displays the given status message in the sidebar.
*
* #param {String} msg The status message to display.
*/
function showStatus(msg) {
const status = document.querySelector('#sidebar-status');
status.innerHTML = msg;
}
</script>
</body>
</html>
modalDialog.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>Attention!</h1>
<p>It's time to take a break.</p>
</body>
</html>
It can be easily adapted to be used on a document, form or presentation.
If the manifest is being edited manually, please be sure to include https://www.googleapis.com/auth/script.container.ui in the list of OAuth scopes besides other required according to the type of document to which the script will be bounded.
If you need to work with a standalone script, instead of a bounded script, you should use it as and Editor add-on.
Reference
https://developers.google.com/apps-script/reference/base/ui
Related
How to poll a Google Doc from an add-on
How do I make a Sidebar display values from cells?
Exception: Cannot call SpreadsheetApp.getUi() from this context. (line 2, file "Code")

Run a Google apps script automatically upon loading a Google Site?

I wrote an Apps Script that takes a spreadsheet and converts it into a Google form. I want to display the form on my google site; however, I want the form to automatically refresh every time the site is opened, so that if the spreadsheet has changed, the form will also be updated when it displays. Essentially, I want the script to run automatically upon open of the Google site – any idea how to do this?
Also, as a side note (not as important), is there any way to incorporate a script into a google site without displaying it as a gadget? I don't want to display the script, I just want it to run in the background.
You can run an Apps Script function indirectly when the site loads by creating a stand alone Web App with HTML Service, publishing it, and putting window.onload into a script tag:
<script>
window.onload = function() {
console.log('Onload ran. ');//Open the browser console log to see debug messages
};
</script>
Then use google.script.run to run a server function:
<script>
window.onload = function() {
console.log('hah! it ran. ');
google.script.run
.withSuccessHandler(run_This_On_Success)
.gsServerFunction();
};
window.run_This_On_Success = function(the_Return) {
console.log('was successful!: ' + the_Return);
};
Code.gs
function gsServerFunction() {
return true;
};
index.html
<!DOCTYPE html>
<html>
<body>
This is the body
<script>
window.onload = function() {
console.log('hah! it ran. ');
google.script.run
.withSuccessHandler(wuzSugzezvul)
.gsServerFunction();
};
window.wuzSugzezvul = function() {
console.log('was successful!');
};
</script>
</body>
</html>
Remove the border and the title of the apps script gadget, make it very small, and don't put in any text to display in the HTML.

Displaying a GoogleScript UiApp with the htmlService / html template

I have a UiApp that creates some form elements that write data to a specific spreadsheet. Now I want to load the UiApp widgets into an htmlService template, and I'm not sure how to go about this.
My doGet function loads the base template, and the doGetApp builds the UiApp:
function doGet() {
return HtmlService.createTemplateFromFile('base_template').evaluate();
}
function doGetApp() {
// creates input forms for a spreadsheet (the following is just an example
var app = UiApp.createApplication();
var tabPanel = app.createDecoratedTabPanel();
var flowPanel1 = app.createFlowPanel();
var flowPanel2 = app.createFlowPanel();
tabPanel.add(flowPanel1, "Create New Projects");
tabPanel.add(flowPanel2, "Edit Projects");
app.add(tabPanel);
// return app; // <<<< original doGet function returned the app
// testing different ways of returning the UiApp
return HtmlService.createHtmlOutput(app); // this displays the text "HtmlOutput"
}
My base_html template is very simple right now:
<html >
<?!= getCSS("css_js"); ?>
<body>
<h1>Template Test</h1>
<?!= doGetApp(); ?>
</body>
</html>
I've tried a few variations on this, but the results are generally the same: Text output that appears to describe the object, as opposed to displaying the UiApp. (the getCSS function works fine)
The goal is to be able to easily load some of my own css / js as well as the UiApp in order to easily style the forms / resulting content. I'm pretty new to this, so it is entirely possible that I'm approaching this the wrong way. Appreciate any pointers. Thanks!
-greg
For now, you either use html service or uiapp - you cannot combine the two