I'm creating a Google Form add-on. The user indicates in the sidebar which file to use and where the files are to land in Google Drive. The add-on needs to save these two variables permanently on the server. How can I save the user settings?
My HTML - sidebar on right, when a owner of this form is creating questions and type of answers
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div>
<form id="myForm" onsubmit="handleFormSubmit(this)">
Link to your document:
<br>
<input type="text" name="document"><br><br>
Link to your folder in your Google Drive:
<br>
<input type="text" name="drive"><br><br>
<input type="submit" value="PrzeĊlij">
</form>
</div>
<br>
<script>
function handleFormSubmit(formObject) {
google.script.run.withSuccessHandler(close).processForm(formObject);
}
function close() {
google.script.host.close();
}
</script>
</body>
</html>
My code.gs
function onOpen() {
FormApp.getUi()
.createMenu('Additional menu')
.addItem('Answers to file', 'showSidebar')
.addSeparator()
.addToUi();
}
function showSidebar() {
var html = HtmlService.createHtmlOutputFromFile('sidebar')
.setTitle('Save your form!')
.setWidth(300);
FormApp.getUi()
.showSidebar(html);
}
function processForm(formObject) {
var ui = FormApp.getUi();
var linkToDoc = formObject.document;
ui.alert ('Link do dokumentu to: '+linkToDoc);
var idOfDoc = linkToDoc.match(/[-\w]{25,}/);
ui.alert ('ID dokumentu: '+ idOfDoc);
var linkToDrive = formObject.drive;
ui.alert ('Link do folderu na Dysku Google: '+linkToDoc);
var idOfDrive = linkToDrive.match(/[-\w]{25,}/);
ui.alert ('ID folderu na Dysku Google: '+ idOfDrive);
}
function myFunction(e) { //Trigger that starts when the end user completes and sends the form.
How can I use here variables from proccessForm()? I mean idOfDoc and idOfDrive.
}
Now works great. Below is my function to save my variables from form. I've added one additional input in form, so that's why here it's mail variable.
var scriptProperties = PropertiesService.getScriptProperties();
function processForm(formObject) {
var ui = FormApp.getUi();
var linkToDoc = formObject.document;
ui.alert ('Link to doc: '+linkToDoc);
var idOfDoc = linkToDoc.match(/[-\w]{25,}/);
ui.alert ('ID of doc: '+ idOfDoc);
var linkToDrive = formObject.drive;
ui.alert ('Link to drive: '+linkToDoc);
var idOfDrive = linkToDrive.match(/[-\w]{25,}/);
ui.alert ('ID of folder in Google Drive: '+ idOfDrive);
var mail = formObject.mail;
ui.alert('Your email: '+mail);
var myJSON = JSON.stringify(idOfDoc);
var myJSON2 = JSON.stringify(idOfDrive);
var myJSON3 = JSON.stringify(mail);
var myJSON4 = JSON.stringify(linkToDoc);
var myJSON5 = JSON.stringify(linkToDrive);
scriptProperties.setProperty('doc', myJSON);
scriptProperties.setProperty('drive', myJSON2);
scriptProperties.setProperty('mail', myJSON3);
scriptProperties.setProperty('linkdoc', myJSON4);
scriptProperties.setProperty('linkdrive', myJSON5);
}
Related
I created a web form using Google Apps Script, where form visitors would see result.html after data submission. However, the data may be submitted multiple times if visitors reload the result.html by pressing F5, Ctrl + R, ignoring the alert of resubmission. The same concern has already been posted here, and I tried implementing one of the solutions for that, but in vain.
I have now four files in the same project of Google Apps Script:
index.html that produces the form
JavaScript.html that defines functions used in index.html
result.html that is presented after the form submission
code.gs that shows the form by doGet(), and processes the submitted data and presents result.html by doPost(). include() defined in this file enables to input JavaScript.html into index.html
The solution I have tried is adding the following JavaScript code result.html. I also add that to JavaScript.html so that the code is to be executed in index.html, too.
<script>
if ( window.history.replaceState ) {
window.history.replaceState( null, null, window.location.href );
}
</script>
However, the resubmission still occurs when I reload the result.html even after I added that code to both result.html and index.html. What am I missing?
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- <?!= include("css"); ?> -->
</head>
<body onload="addOptions()"> <!--Execute addOptions function immediately after a page has been loaded-->
<form class="" action="<?!= getScriptUrl(); ?>" method="post" onSubmit="document.getElementById('submit').disabled=true;">
<div>
<h1 id="Question">
Choose either cheesecake or chocolate cake.
</h1>
<select id="dropdownList" name="cake" class="form-control">
</select>
</div>
<div class="form-submit">
<input type="submit" name="" value="Submit">
</div>
</form>
</body>
<?!= include('JavaScript') ?>
</html>
JavaScript.html
<script>
function addOptions() {
/*This will call server-side Apps Script function getAvailableExps and if it is successful,
it will pass the return value to function addListValues which will add options to the drop down menu*/
google.script.run
.withFailureHandler(onFailure)
.withSuccessHandler(addListValues)
.getAvailableExps();
}
function addListValues(values) {
//Add options to drop down menu using the values of parameter 'values'.
for (var i = 0; i < values.length; i++) {
var option = document.createElement("option");
option.text = values[i][0];
option.value = values[i][0];
var select = document.getElementById("dropdownList");
select.appendChild(option);
}
}
function onFailure(err) {
alert('Error: ' + err.message);
}
if ( window.history.replaceState ) {
window.history.replaceState( null, null, window.location.href );
}
</script>
result.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<base />
<title>Thank you for your order!</title>
<!-- <?!= include('css'); ?> -->
</head>
<script>
if ( window.history.replaceState ) {
window.history.replaceState( null, null, window.location.href );
}
</script>
<body>
<p>
Don't forget what you've ordered!
</p>
</body>
</html>
code.gs
var sheetID = "............................................";
var inventory_sheet = "Inventory";
function doGet(){
return HtmlService.createTemplateFromFile("index").evaluate();
}
function include(filename){
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
Logger.log(url);
return url;
}
function doPost(e){
var ss = SpreadsheetApp.openById(sheetID);
var sh = ss.getSheets()[0];
sh.appendRow([String(e.parameters.cake)]);
//update Inventory
var inventory = ss.getSheetByName(inventory_sheet);
var row = inventory.createTextFinder(e.parameters.cake).findNext().getRow();
var range = inventory.getRange(row, 2);
var data = range.getValue();
range.setValue(parseInt(data - 1))
return HtmlService.createTemplateFromFile("result").evaluate();
}
function getAvailableExps(){
var inventory = SpreadsheetApp.openById(sheetID).getSheetByName(inventory_sheet);
var data = inventory.getRange(2, 1, 2, 2).getValues();
var filtered = data.filter(arr => arr[1] > 0 || arr[1] != ''); //remove exp to array if quantity is 0 or empty
return filtered;
}
In your situation, how about checking the submit using PropertiesService? When your script is modified, it becomes as follows.
Modified script:
In this modification, 2 functions of doGet and doPost of code.gs are modified.
doGet
function doGet() {
PropertiesService.getScriptProperties().setProperty("key", "sample");
return HtmlService.createTemplateFromFile("index").evaluate();
}
doPost
function doPost(e) {
var p = PropertiesService.getScriptProperties();
if (p.getProperty("key") == "sample") {
var ss = SpreadsheetApp.openById(sheetID);
var sh = ss.getSheets()[0];
sh.appendRow([String(e.parameters.cake)]);
//update Inventory
var inventory = ss.getSheetByName(inventory_sheet);
var row = inventory.createTextFinder(e.parameters.cake).findNext().getRow();
var range = inventory.getRange(row, 2);
var data = range.getValue();
range.setValue(parseInt(data - 1))
p.deleteProperty("key");
}
return HtmlService.createTemplateFromFile("result").evaluate();
}
When you access to your Web Apps, sample is stored by setProperty("key", "sample") in doGet(). And, when the HTML form is submitted, the PropertiesService is checked in doPost(e). When sample is existing, the data is put, and the PropertiesService is cleared. By this, even when the submitted page is reopened, the PropertiesService is not existing. By this, the resubmitted can be avoided.
Reference:
Properties Service
In the original script, when there is no value in the text bar and I click on the button called Radar 2, the page is automatically refreshed, as I understand it, as it generates an error in the function, the page refreshes because of that.
To prevent this error and this forced update, I tried using try{} catch{}, so if there is an error when trying to parse the first function, open a blank iframe.
But the page keeps updating when I click the Radar 2 button.
I would like to know what I'm doing wrong and what the correct script would look like for my needs.
The original script works as follows:
<form action="" method="post" id="url-setter2">
<button type="submit" name="submit">Radar 2</button>
<input type="text" name="url2" id="url2" style="width: 282px;" />
<iframe id="the-frame2" width="347" height="282" src=""></iframe>
<script type="text/javascript">
(function () {
"use strict";
var url_setter = document.getElementById('url-setter2'), url = document.getElementById('url2'), the_iframe = document.getElementById('the-frame2');
url_setter.onsubmit = function (event) {
let link = document.getElementById("url2").value;
let value2 = link.split("OB_EV")[1];
value2 = value2.split("/")[0];
event.preventDefault();
the_iframe.src = "https://sports.staticcache.org/scoreboards/scoreboards-football/index.html?eventId=" + value2;
};
}());
</script>
My Script Error Test:
<script type="text/javascript">
(function () {
"use strict";
var url_setter = document.getElementById('url-setter2'), url = document.getElementById('url2'), the_iframe = document.getElementById('the-frame2');
url_setter.onsubmit = try {
function (event) {
let link = document.getElementById("url2").value;
let value2 = link.split("OB_EV")[1];
value2 = value2.split("/")[0];
event.preventDefault();
the_iframe.src = "https://sports.staticcache.org/scoreboards/scoreboards-football/index.html?eventId=" + value2;
}
}
catch (e) {
function (event) {
event.preventDefault();
the_iframe.src ="";
}
};
}());
</script>
Using type="submit" will try to send the form data to the server for processing which is why the page refreshes.
Instead try using <button onclick="myFunction()">Radar 2</button> where
myFunction() is a function with your javascript code in it
I am trying to pass a searchterm from a google web app to display the results. I am having trouble on submission I receive a blank screen. When the form is submitted, I would like it to display the results. The main code works within the logger- now I am just working on the UI and getting the form to work.
Any help is appreciated!
So far this is the code I have:
CODE:
function SearchFiles() {
//Please enter your search term in the place of Letter
var searchterm ="'mysearchinput'"; \\ this would be the variable that is passed from the form on index.html
var searchFor ="title contains " + searchterm;
var owneris ="and 'youremail#yourdomain.com' in Owners";
var names =[];
var fileIds=[];
var files = DriveApp.searchFiles(searchFor + owneris);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
fileIds.push(fileId);
var name = file.getName();
names.push(name);
}
for (var i=0;i<names.length;i++){
Logger.log(names[i]);
Logger.log("https://drive.google.com/uc?export=download&id=" + fileIds[i]);
}
}
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function processForm(formObject) {
Logger.log('I was called!');
// here is where I would like to display results of searthterm.
}
HTML:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function handleFormSubmit(formObject) {
google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
}
function onFailure(error) {
var div = document.getElementById('output');
div.innerHTML = "ERROR: " + error.message;
}
</script>
</head>
<body>
Hello, World!
<form id="myForm" onsubmit="handleFormSubmit(this)">
<input type="text" name="search">
<input type="submit" value="Submit" />
</form>
<input type="button" value="Close"
onclick="google.script.host.close()" />
SEARCH FUNCTION:
function SearchFiles() {
Logger.log('I Searched Files');
//Please enter your search term in the place of Letter
var searchterm ="'Whatevertextisinthesearchbox'";
var searchFor ="title contains " + searchterm;
var owneris ="and 'Youremail#yourdomain' in Owners";
var names =[];
var fileIds=[];
var files = DriveApp.searchFiles(searchFor + owneris);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
fileIds.push(fileId);
var name = file.getName();
names.push(name);
}
for (var i=0;i<names.length;i++){
// Logger.log(names[i]);
// Logger.log("https://drive.google.com/uc?export=download&id=" + fileIds[i]);
var filesreturned = {
name:names[i],
urls:"https://drive.google.com/uc?export=download&id=" + fileIds[i]
}
Logger.log(filesreturned.name + " - " + filesreturned.urls);
return(filesreturned);
}
}
As per your comment above, here is the code to simply show hello world on button click. For your reference, I have also added a code to pass data between javascript and appscript.
Index.html file
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function displayMessage()
{
google.script.run.withSuccessHandler(helloWorld).parseDataFromAppscript();
}
function helloWorld(stringText)
{
document.writeln(stringText);
}
</script>
</head>
<body>
<input type="button" value="submitButton" name="submitButton" onclick="displayMessage()"/>
</body>
</html>
Code.gs file
function doGet(e) {
var template = HtmlService.createTemplateFromFile('Index');
return template.evaluate()
.setTitle('Hello World')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function parseDataFromAppscript()
{
return "Hello World!";
}
To run this, go to publish -> deploy as web app -> update. And then click latest code.
If you want explanation for any part, please feel free to ask. I'm assuming you already know html javascript and google.script.run method. :)
tried to find some useful answer in existing threads but nothing is really matching my issue. I guess there is a quick fix to it, i just cannot see it.
I have a HTML form and want to upload the file to my google drive (works great) and save the text fields to my spreadsheet (does not work at all).
I just cannot find any major difference between the two functions, its frustrating!
Below my code, and here is also the [link to my public script][1].
Form:
<!DOCTYPE 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;
google.script.run.withSuccessHandler(fileUploaded)
.doPost(this.parentNode);">
</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>
[1]: https://script.google.com/d/1K9jGl7ALHCZ93rz8GoeV8t7PE_7vgbNZlLJed1h6ZWyuoon11gIbik24/edit?usp=sharing
server.gs:
function doGet(e) {
var html = HtmlService.createTemplateFromFile("form");
html = html.evaluate();
html.setSandboxMode(HtmlService.SandboxMode.IFRAME);
return html;
}
function uploadFiles(form) {
try {
var dropbox = "Customer Shapes";
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();
}
}
function doPost(form) { // change to doPost(e) if you are recieving POST data
html.setSandboxMode(HtmlService.SandboxMode.EMULATED);
var name = form.myName;
var url="url...";
var message = 'Ram';
var submitSSKey = '...kHI';
var sheet = SpreadsheetApp.openById(submitSSKey).getActiveSheet();
var lastRow = sheet.getLastRow();
var targetRange = sheet.getRange(lastRow+1, 1, 2, 2).setValues([[name,url]]);
}
Thank You for Your kind help!
Martin
If you are going to use google.script.run, then it's pointless to have a function named doPost(). And it's also pointless to have a submit type input tag: type="submit"
If you want to run a withSuccessHandler(fileUploaded) to change the display, you can't have a button inside of the form. It will cause the display to go blank.
And you don't need two google.script.run calls. You need multiple changes and improvements:
form.html
<form id="myForm">
<input type="text" name="myName" placeholder="Your name..">
<input type="file" name="myFile">
</form>
<input type="button" value="Upload File"
onclick="this.value='Uploading..';
var theForm = document.getElementById('myForm');
google.script.run.withSuccessHandler(fileUploaded)
.processFormData(theForm);">
<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>
server.gs
function processFormData(form) {
uploadFiles(form.myFile);//Call first function to process the file
var innerArray = [];
var outerArray = [];
innerArray.push(form.myName);
innerArray.push(form.myFile);
outerArray.push(innerArray);//Call second function to write data to SS
writeDataToSS(outerArray);
};
function uploadFiles(theFile) {
try {
var dropbox = "Customer Shapes";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var blob = theFile;
var file = folder.createFile(blob);
file.setDescription("Uploaded by " + theFile);
return "File uploaded successfully " + file.getUrl();
} catch (error) {
return error.toString();
}
}
function writeDataToSS(values) {
//html.setSandboxMode(HtmlService.SandboxMode.EMULATED);
var url="url...";
var message = 'Ram';
var submitSSKey = '...kHI';
var sheet = SpreadsheetApp.openById(submitSSKey).getActiveSheet();
var lastRow = sheet.getLastRow();
var targetRange = sheet.getRange(lastRow+1, 1, 1, 2).setValues(values);
}
The html file is not a template, it does not have a scriptlet in it. Just use createHtmlOutputFromFile():
function doGet(e) {
var html = HtmlService.createHtmlOutputFromFile("form");
html.setSandboxMode(HtmlService.SandboxMode.IFRAME);
return html;
}
I have a similar problem to this post when trying to convert my apps script web app to use IFRAME Sandbox. I have converted to 'input = "button"' as suggested.
My web app is a simple form for students to use to sign in and out of a school library. The idea for the app is for it to be as easy as possible for students to use. Students should enter their id number and be able to either click the submit button or hit the enter key. Their ID is then validated before being stored in a spreadsheet and they get a message back saying thanks for signing in or out, or please enter a valid ID Number. Then focus should return to the text box and be cleared, ready for the next student to enter their id.
I had it working as described above using NATIVE mode. When I tried to convert it to IFRAME mode, clicking the button works, but if you hit the enter key everything just disappears and no data goes to the spreadsheet. How can I get hitting the enter key to work the same as clicking the submit button?
index.html code:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
</head>
<body>
<h3>Please Sign In & Out</h3>
<div id="box" class="frame">
<form id="signSheet" onsubmit="google.script.run
.withSuccessHandler(updateInfo)
.validateID(this.parentNode)">
<input type="text" name="myID" id="myID" placeholder="Enter your student ID" autocomplete="off">
<input type="button" value="Submit" onmouseup="google.script.run
.withSuccessHandler(updateInfo)
.validateID(this.parentNode)">
</form>
<span id="thank_you" hidden="true"></span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<?!= include('javascript'); ?>
</body>
</html>
javascript.html code:
<script>
function updateInfo(ret){
if(ret[0]){
$( "#thank_you" ).removeClass("error");
$( "#thank_you" ).addClass("valid");
}
else{
$( "#thank_you" ).removeClass("valid");
$( "#thank_you" ).addClass("error");
}
$( "#thank_you" ).text(ret[1]);
$( "#thank_you" ).show("slow");
$( "#signSheet" )[0].reset();
$( "#myID" ).focus();
console.log(ret);
}
</script>
Code.gs code:
//spreadsheet key is needed to access the spreadsheet.
var itemSpreadsheetKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
//Open the spreadsheet and get the sheet objects
var openedSS = SpreadsheetApp.openById(itemSpreadsheetKey);
var studentList = openedSS.getSheetByName("Student List");//Spreadsheet must match with sheet name
var studentRecord = openedSS.getSheetByName("Sign In-Out Record");
function doGet() {
var html = HtmlService.createTemplateFromFile('index').evaluate()
.setTitle('Sign In/Out Sheet')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
return html;
}
function include(filename) {
Logger.log('enter include');
Logger.log(filename);
var html = HtmlService.createHtmlOutputFromFile(filename).getContent();
Logger.log(html);
return html;
}
function validateID(form){
var idNum = form.myID;
var valid = false;
var numIdList = studentList.getLastRow()-1; //-1 is to exclude header row
//get the item array
var idArray = studentList.getRange(2,1,numIdList,1).getValues();
i= idArray.length;
while(i--){
if(idArray[i][0]==idNum & idNum!='') {
valid=true;
break;
}
}
if(valid)
return [1, updateRecord(idNum)];
else return [0, "ID number " + idNum + " not recognized. Please enter a valid ID number."];
}
function updateRecord(idNum){
studentRecord.appendRow([idNum]);
var formulas = studentRecord.getRange("B2:D2").getFormulasR1C1();
var lRow = studentRecord.getLastRow();
var range = studentRecord.getRange(lRow, 2, 1, 3);
range.setFormulas(formulas);
var vals = range.getValues();
var now = new Date();
studentRecord.getRange(lRow, 5, 1, 1).setValue(now);
now = Utilities.formatDate(now, "EST", "HH:MM a");
idNum = "Thanks " + vals[0][0] + ", you have signed " + vals[0][2] + " at " + now;
return idNum;
}
Update: I found this post and added the following code to javascript.html:
$(document).ready(function() {
$(window).keydown(function(event){
if(event.keyCode == 13) {
var idVal = $("#myID").val();
google.script.run.withSuccessHandler(updateInfo).validateID(idVal);
return false;
}
});
})
This solved the the problem for me with a little more tweaking to parts of index.html and Code.gs
I found this post and added the following code to javascript.html:
$(document).ready(function() {
$(window).keydown(function(event){
if(event.keyCode == 13) {
var idVal = $("#myID").val();
google.script.run.withSuccessHandler(updateInfo).validateID(idVal);
return false;
}
});
})
This listens for the enter key and sends the value of the text field to the apps script function. In this case I didn't need to use `event.preventDefault();'
Then I had to adjust the button's onmouseup function to take this.parentNode.myID and change my apps script function to take a value instead of a form object.
You must remove the onsubmit attribute from the <form> tag:
Currently you have:
<form id="signSheet" onsubmit="google.script.run
.withSuccessHandler(updateInfo)
.validateID(this.parentNode)">
Change it to this:
<form id="signSheet">
You already have a call to google.script.run in your button, so leave that.