I have used createTemplateFromFile many time in the past. It lets me use an include method so that CSS and JavaScript can be broken out into a different file. But today I can't seem to make it work. Below is my test code bound to a spreadsheet. Any ideas why its not working? I tried another existing spreadsheet with a custom dialog using the technique and it works.
Code.gs
function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu("Test");
menu.addItem("Test", "test");
menu.addToUi();
}
function test() {
try {
var html = HtmlService.createTemplateFromFile("HTML_Test");
Logger.log(html);
// was html.evaluate();
html = html.evaluate(); // correction
SpreadsheetApp.getUi().showSidebar(html);
}
catch(err) {
Logger.log(err);
}
}
HTML_Test:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<select>
<option value="Doc1">Document 1</option>
<option value="Doc2">Document 2</option>
<option value="Doc3">Document 3</option>
</select>
</body>
</html>
Log
[19-01-24 09:54:51:240 PST] {}
[19-01-24 09:54:51:246 PST] Exception: Invalid argument: userInterface
I've been tweeking this every which way to figure out why it wouldn't work. You get blind to a simple solution. html = html.evaluate() works.
Related
I am trying to make a pop-up box with several multiple-choice answers to select.
I used this example:
google apps script : a select drop down list in google sheet
Which was a good starting point for 1 question, and I tried expanding on it to get multiple answers but failed expanding it.
Here's my test file:
https://docs.google.com/spreadsheets/d/1BRCqpvfRl64a7ISyuohxUJLWKbqX9Fz6NPCrL2iKEm0/
It contains script code triggered by a simple button press;
function start() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1:B1').activate();
spreadsheet.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
spreadsheet.getRange('A10').activate();
// START HTML POP-UP
dropDownModal()
};
// -------------------------------------------------------------------------
function dropDownModal() {
var htmlDlg = HtmlService.createHtmlOutputFromFile('dropdown.html')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(350)
.setHeight(175);
SpreadsheetApp.getUi()
.showModalDialog(htmlDlg, 'Box title');
};
function writeChoice(selection1) {
const writeResponseLocation1 = "A1";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation1)
.setValue(selection1);
}
function writeChoice(selection2) {
const writeResponseLocation2 = "B1";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation2)
.setValue(selection2);
}
// -------------------------------------------------------------------------
and this dropdown.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
function onSuccess1() {
google.script.host.close();
}
function submit1() {
const choice1 = document.getElementById('choice1').value;
const choice2 = document.getElementById('choice2').value;
google.script.run
.withSuccessHandler(onSuccess1)
.writeChoice(choice1)
.writeChoice(choice2);
}
function setup1() {
const button = document.getElementById('submitbutton1');
button.addEventListener("click", submit1)
}
</script>
<body onload="setup1()">
<p>
Text 1.
</p>
<form>
<select id="choice1">
<option value="choice 1-A">choice 1-A</option>
<option value="choice 1-B">choice 1-B</option>
<option value="choice 1-C">choice 1-C</option>
</select>
<br><br>
<select id="choice2">
<option value="choice 2-A">choice 2-A</option>
<option value="choice 2-B">choice 2-B</option>
<option value="choice 2-C">choice 2-C</option>
</select>
<br><br>
<button id="submitbutton1">Hit it 1</button>
</form>
</body>
</html>
and it's writing the answer from Question 1 into the location intended for Question 2.
Can someone please help find where I went wrong?
Thanks
You have two functions with the same name writeChoice
It is not enough to change the name of the function parameters (selection1, selection1), indeed those are only placeholders.
Instead you need to either create two different funcitons with two different names (sample 1) or pass writeResponseLocation as a second parameter (sample 2).
Furthermore, with google.script.run you can only call one Apps Script function at a time. You can call the second one e.g. within your success handler.
Sample 1:
Code.gs
...
function writeChoice1(selection1) {
const writeResponseLocation1 = "A1";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation1)
.setValue(selection1);
}
function writeChoice2(selection2) {
const writeResponseLocation2 = "B1";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation2)
.setValue(selection2);
}
...
html
<script>
function onSuccess1() {
const choice2 = document.getElementById('choice2').value;
google.script.run
.withSuccessHandler(onSuccess2)
.writeChoice2(choice2);
}
function onSuccess2() {
google.script.host.close();
}
function submit1() {
const choice1 = document.getElementById('choice1').value;
google.script.run
.withSuccessHandler(onSuccess1)
.writeChoice1(choice1);
}
function setup1() {
const button = document.getElementById('submitbutton1');
button.addEventListener("click", submit1)
}
</script>
...
Sample 2:
Code.gs
...
function writeChoice(selection, writeResponseLocation) {
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation)
.setValue(selection);
}
...
html
<script>
function onSuccess1() {
const choice2 = document.getElementById('choice2').value;
google.script.run
.withSuccessHandler(onSuccess2)
.writeChoice(choice2, "B1");
}
function onSuccess2() {
google.script.host.close();
}
function submit1() {
const choice1 = document.getElementById('choice1').value;
google.script.run
.withSuccessHandler(onSuccess1)
.writeChoice(choice1, "A1");
}
function setup1() {
const button = document.getElementById('submitbutton1');
button.addEventListener("click", submit1)
}
</script>
...
Think I found a solution here fiddling together with a friend.
// =========================================================================
//
// EMPTY CELLS A1 and A2 before re-run
//
function start() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1:B1').activate();
spreadsheet.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
spreadsheet.getRange('A10').activate();
// START HTML POP-UP
dropDownModal()
};
// -------------------------------------------------------------------------
function dropDownModal() {
var htmlDlg = HtmlService.createHtmlOutputFromFile('dropdown.html')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(350)
.setHeight(175);
SpreadsheetApp.getUi()
.showModalDialog(htmlDlg, 'Box title');
};
function writeChoice1(selection1) {
const writeResponseLocation1 = "A1";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation1)
.setValue(selection1);
}
function writeChoice2(selection2) {
const writeResponseLocation2 = "B1";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation2)
.setValue(selection2);
}
// =========================================================================
And the
dropdown.html :
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
function onSuccess() {
google.script.host.close();
}
function submit() {
const choice1 = document.getElementById('choice1').value;
const choice2 = document.getElementById('choice2').value;
google.script.run
.writeChoice1(choice1)
google.script.run
.withSuccessHandler(onSuccess)
.writeChoice2(choice2)
}
function setup() {
const button = document.getElementById('submitbutton');
button.addEventListener("click", submit)
}
</script>
<body onload="setup()">
<p>
Text 1.
</p>
<form>
<select id="choice1">
<option value="choice 1-A">choice 1-A</option>
<option value="choice 1-B">choice 1-B</option>
<option value="choice 1-C">choice 1-C</option>
</select>
<br><br>
<select id="choice2">
<option value="choice 2-A">choice 2-A</option>
<option value="choice 2-B">choice 2-B</option>
<option value="choice 2-C">choice 2-C</option>
</select>
<br><br>
<button id="submitbutton">Submit entries</button>
</form>
</body>
</html>
This all works.
The one thing I was hoping to understand and hopefully someone here can explain.
Why can I not
google.script.run
.withSuccessHandler(onSuccess)
.writeChoice1(choice1)
.writeChoice2(choice2)
And why does even in the working version
.withSuccessHandler(onSuccess)
.writeChoice2(choice2)
work, but this having the the closing after writing the choice not as such:
.writeChoice2(choice2)
.withSuccessHandler(onSuccess)
The confusing part is that I would assume close before writing would mean no write, so i switched them around somewhere early on in testing.
Happy the code works, but still wanting to learn if someone knows :)
This question already has answers here:
Adding a property to an Html template gives error "Object does not allow properties to be added or changed"
(2 answers)
Updating content in a Google Apps Script sidebar without reloading the sidebar
(1 answer)
I am trying to pass a variable from my Google Script through to HtmlOutputFromFile
(1 answer)
Closed 8 months ago.
I've been reading these answers and trying out some of the code, but I could not get my code to work. These are the links I've been reading:
https://developers.google.com/apps-script/guides/dialogs#page.html_1
How to pass a parameter to html?
Adding a property to an Html template gives error "Object does not allow properties to be added or changed"
My Code.gs:
function onOpen() {
SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.createMenu('Custom Menu')
.addItem('Show sidebar', 'showSidebar')
.addToUi();
}
function showSidebar() {
var html = HtmlService.createHtmlOutputFromFile('Page')
.setTitle('My custom sidebar');
SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.showSidebar(html);
}
function testCSV2() {
const text = SpreadsheetApp.getActiveSheet().getDataRange().getDisplayValues();
const result = cellArraysToCsv(text);
Logger.log(result);
return result;
}
function cellArraysToCsv(data) {
const regex = /"/g;
let change = data.map(row => row.map(value => `"${value.replace(regex, '\"\"')}"`)).join('\n');
return change;
}
My Page.html:
<!DOCTYPE html>
<script>
function answers() {
var data = google.script.run.testCSV2();
document.getElementById("myTitle").innerText = data;
}
</script>
<html>
<head>
<base target="_top">
</head>
<body>
Hello, world! <input type="button" value="Answers" onclick="answers()" />
<H2 id="myTitle"></H2><br><br>
<?!= testCSV2() ?>
</body>
</html>
I'm getting very confused. Why is it that when I click on the button "Answers", I get no output? And why is <?!= testCSV2() ?> unchanged in the <body> of Page.html?
Description
There are two part to this. Using templated HTML in which testCSV2() is run on the server as well as passing data before the HTML is displayed and using google.script.run.testCSV3() to get data from the server.
Code.gs
function onOpen() {
var menu = SpreadsheetApp.getUi().createMenu("Test");
menu.addItem("Show Test", "showSidebar").addToUi();
}
function showSidebar() {
var html = HtmlService.createTemplateFromFile("HTML_Test");
html.data = "greetings";
html = html.evaluate();
SpreadsheetApp.getUi().showSidebar(html);
}
function testCSV2() {
return "hello";
}
function testCSV3() {
return "goodbye";
}
HTML_Test
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Hello, world! <input type="button" value="Answers" onclick="answers()" />
<H2 id="myTitle"></H2><br><br>
<?!= data ?><br>
<?!= testCSV2() ?>
<script>
function answers() {
google.script.run.withSuccessHandler(
function (data) {
document.getElementById("myTitle").innerText = data;
}
).testCSV3();
}
</script>
</body>
</html>
References
Templated HTML
google.script.run
google.script.run.yourfunctionName();
Use this code in JavaScript to call your function that's defined in gs file.
Reference: Communicate with server function
I'm trying to make a Google App script with this simple code below
function onInstall(e) {
onOpen(e);
}
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Test Menu')
.addItem('Test', 'doTest')
.addToUi();
}
function doTest() {
var htmlOutput = HtmlService
.createHtmlOutput('<p>Some Text</p>')
.setTitle('My title');
SpreadsheetApp.getUi().showSidebar(htmlOutput);
}
The sidebar shows up correctly
[1]: https://i.stack.imgur.com/6QTR8.png
But i get this error in the chrome console:
message: "There was an error during the transport or processing of this request. Error code = 10, Path = /wardeninit"
name: "TransportError"
stack:"TransportError: There was an error during the transport or processing of this request. Error code = 10, Path = /wardeninit
at new Qo (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:200:381)
at Vo.w.Xc (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:208:372)
at Cf (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:79:228)
at Bf (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:76:472)
at xf.A (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:76:419)
at Cf (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:79:228)
at Bf (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:76:472)
at Vo. (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:207:159)
at An (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:165:294)
at Do (https://docs.google.com/static/macros/client/js/3949079914-warden_bin_i18n_warden__vi.js:200:262)"
I have been make some Google App script before but just get this error recently.
What am I doing wrong?
Updated
Because of this "TransportError", any "google.script.run" script will fail.
If i update the code like this:
function doTest() {
var htmlOutput =
HtmlService.createHtmlOutputFromFile('Index');
SpreadsheetApp.getUi().showSidebar(htmlOutput);
}
function doSomething() {
Logger.log('I was called!');
}
And the Index.html is
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess() {
console.log("onSuccess");
}
function onFailure(error) {
console.log("onFailure",error);
}
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onFailure).doSomething();
</script>
</head>
<body>
</body>
</html>
The onFailure will always be called and the error is empty.
Update 2
The problem with "google.script.run" script will fail.
only for google workspace account.
If I change to my personal gmail account and made the similar code, the problem does not happen.
I used your code like this:
html:(filename: ah3.html)
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess() {
console.log("onSuccess");
}
function onFailure(error) {
console.log("onFailure",error);
}
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onFailure).doSomething();
</script>
</head>
<body>
<div>Hello World</div>
</body>
</html>
gs:
function doTest() {
var htmlOutput = HtmlService.createHtmlOutputFromFile('ah3').setTitle('My title');
SpreadsheetApp.getUi().showSidebar(htmlOutput);
}
function doSomething() {
Logger.log('I was called!');
}
It works for me.
If you look in the console you will see the onSuccess printed and if you look in executions you will see Sep 3, 2021, 10:16:03 PM Info I was called!
I tried solution at I am trying to pass a variable from my Google Script through to HtmlOutputFromFile, but can't get it working. I get error (translated from Dutch): "TypeError: Can't find function createHtmlTemplateFromFile in object HtmlService.
function fncOpenMyDialog() {
//Open a dialog
var htmlDlg = HtmlService.createHtmlTemplateFromFile('DropDown_NewCompetitionFile');
htmlDlg.myVar = "November";
htmlDlg = htmlDlg.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(200)
.setHeight(150);
SpreadsheetApp.getUi()
.showModalDialog(htmlDlg, 'A Title Goes Here');
};
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<select name="nameYouWant">
<option value="something">Text</option>
<option value="anything">Drop Down Selection</option>
<option value="anotherthing"><?myVar?></option>
</select>
<hr/>
<ul>
<li>This is a list.</li>
<li>This is line two.</li>
</ul>
<button onmouseup="closeDia()">Close</button>
<script>
window.closeDia = function() {
google.script.host.close();
};
</script>
</body>
</html>
Replace
HtmlService.createHtmlTemplateFromFile
with
HtmlService.createTemplateFromFile
You can do things any way you wish but this seems a lot simpler:
<input type="button" value="Close" onClick="google.script.host.close()" />
than this:
<button onmouseup="closeDia()">Close</button>
<script>
window.closeDia = function() {
google.script.host.close();
};
</script>
and I prefer to load my select options via javascript on window.onload
What I'm Trying To Do
I'm trying to add HTML to a modal dialog box in google Forms using a click event to trigger a google.script.run.withSuccessHandler() call to supply the new HTML in order to get additional user input.
GS Code
function onOpen(e) {
FormApp.getUi().createMenu("My Menu").addItem('Set Up Invites', 'setUpInvite').addToUi();
}
function setUpInvite() {
//this is the main function
var ui = HtmlService.createHtmlOutputFromFile("Index")
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle("Setup");
FormApp.getUi().showModalDialog(ui, "Setup");
}
function getEventQAnswer(answer) {
var html;
switch(answer)
{
case "yes":
//TODO
//get the event info
return "";
break;
case "no":
//create the event
html = HtmlService.createHtmlOutputFromFile("createEvent.html")
return html;
break;
}
}
HTML Index Page
On this page I'm trying to change the get the functions to work onclick. I tried initially onchange, but it still didn't work. It starts with getSelectAnswer which gets the value from the select question, then calls the GS function getEventQAnswer which gets the proper HTML from the server side and delivers it to function addHTMLChoice. However, at present, it doesn't seem to do anything.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<script>
//document.getElementById("eventQ").addEventListener("onchange", getSelectAnswer);
document.getElementById("eventQ").onclick.getSelectAnswer();
function addHTMLChoice(html) {
var div = document.getElementById('eventInfo');
div.innerHTML = html;
}
function getSelectAnswer() {
var e = document.getElementById('eventQ');
var val = e.options[e.selectedIndex].value;
google.script.run.withSuccessHandler(addHTMLChoice).getEventQAnswer(val);
}
</script>
<form>
<div>
<select id="eventQ">
<option value="yes">Yes</option>
<option value="no">No, create one now</option>
</select>
</div>
<div id="eventInfo">
</div>
</form>
</body>
</html>
This is the createEvent.html I'm trying to return in test.
<div>
<input id="datetime" name="datetime" type="datetime-local">
<p>hi</p>
</div>
The server-side code can only return certain type of parameters, described here. Since you are trying to return an html object it is not passed to the client side (your Modal dialog). Hence, modify your server-side like so:
function getEventQAnswer(answer) {
var html;
switch(answer)
{
case "yes":
//TODO
//get the event info
return "";
break;
case "no":
//create the event
html = HtmlService.createHtmlOutputFromFile("createEvent.html").asTemplate().getRawContent()
return html;
break;
}
}
Note the conversion into RawContent.
Also, I find it easier to setup onchange event trigger to obtain the choice, like so:
<form>
<div>
<select id="eventQ" onchange ='getSelectAnswer()'>
<option value="yes">Yes</option>
<option value="no">No, create one now</option>
</select>
</div>
The final html index code will be:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<script>
//document.getElementById("eventQ").addEventListener("onchange", getSelectAnswer);
//document.getElementById("eventQ").onclick.getSelectAnswer();
function addHTMLChoice(html) {
console.log("success")
console.log(html)
var div = document.getElementById('eventInfo');
div.innerHTML = html;
}
function getSelectAnswer() {
console.log("getting selected Answer")
var e = document.getElementById('eventQ');
var val = e.options[e.selectedIndex].value;
console.log(val)
google.script.run.withSuccessHandler(addHTMLChoice).withFailureHandler(failed).getEventQAnswer(val);
}
function failed(e){
console.log("Failed")
console.log(e)
}
</script>
<form>
<div>
<select id="eventQ" onchange ='getSelectAnswer()'>
<option value="yes">Yes</option>
<option value="no">No, create one now</option>
</select>
</div>
<div id="eventInfo">
</div>
</form>
</body>
</html>
Note, the use of console.log to debug on the client side. This would be useful in future for your own debugging.
Hope that helps.