Bookmarklet hosted in Apps Script unable to run when shared - google-apps-script

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)

Related

Apps Script - Use a Code.gs variable in HTML script

Here, the task is to use a variable from Code.gs to be used in the HTML side.
The best idea I've had is using google.script.run to get access to Code.gs where I have stored a variable that I wish to use in the HTML script. Eg: Suppose there is a variable in the Code.gs side that turned out to be 1+1. Then I would very much have liked the following to work:
Code.gs
function getMyGSValue() {
return 1+1
}
HTML
<script>
google.script.run.withSuccessHandler(myGsValue => {
myScriptVar = myGsValue
}).getMyGsValue()
// Here use MyScriptVar endlessly.
</script>
Which unfortunately fails to work. If it's of any help, I'm more interested in using string variables from the Code.gs side as these will be more likely the link to the images I want to display if particular conditions are met.
A related question follows:
Passing variable from Code.gs to html in Google App Script
But to be honest, It seemed to have its focus elsewhere.
Doing it with google.script.run. I just used window.onload event to get the data from the server
html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input type="text" id="txt1" />
<script>
window.onload = () =>{
//console.log("window.onload")
google.script.run.withSuccessHandler((v) => {
document.getElementById("txt1").value = v;
}).getMyGsValue()
//console.log("Code");
}
</script>
</body>
</html>
gs:
function getMyGsValue() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
return sh.getRange("A1").getValue();
}
function launchmydialog() {
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutputFromFile("ah2"),"Title");
}
If you need that the "variable" from the server side be ready when the web browser start parsing the web application, instead of using client-side code to retrieve the "variable", generate the client-side code with the "variable" that you need by generating first a HtmlTemplate, then assigns the variable to a HtmlTemplate property and finally evaluates it.
Below is an over simplistic example:
function doGet(e){
const myServerVariable = 1 + 1;
const template = `<body><script>const myClientVariable = <?!= tmpProp?> </script></body>`;
return (
HtmlService.createTemplate(template)
.tmpProp = myServerVariable
).evaluate();
}
The above example is so simple that you could do the same by using regular JavaScript string manipulation. Anyway, <?!= tmpProp?> is a force-printing scriptlet, also there are standard scriptlets and printing scriptlets that might be more frequently used on .html files in Google Apps Script.
Be careful when using scriptlets to built the client-side code to not make them to take too much time to generate the client-side code as this will impact how fast the web app responds to the initial request.
By the other hand, if you want to keep using google.script.run, just declare a global variable, and update it in the withSuccessHandler callback
<script>
var MyScriptVar;
google.script.run.withSuccessHandler(myGsValue => {
MyScriptVar = myGsValue
}).getMyGsValue()
// Here use MyScriptVar endlessly.
</script>
Just consider the case that MyScriptVar will be undefined while the google.script.run finish it's execution.
Related
How to pass a parameter to html?
Passing variable from google script to html dialog
References
https://developers.google.com/apps-script/guides/html/templates

Execute HtmlOutput code in Google App Script without opening a sidebar

edit: wound up using cheerio to manipulate the elements instead of creating them in a sidebar.
Is it possible to create an execute htmlOutput in a background page, or do so without showing the user anything?
Sample code below:
plugin.gs
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'run')
.addToUi();
}
/**
* Opens a sidebar in the document containing the add-on's user interface.
*/
function run() {
var ui = HtmlService.createTemplateFromFile('sidebar').evaluate()
.setTitle(constants.title);
DocumentApp.getUi().showSidebar(ui);
}
sidebar.html
<html>
<head>
<script>
console.log("Hello world!");
</script>
</head>
</html>
This works, but it pops open the sidebar. If I comment out DocumentApp.getUi().showSidebar(ui);, then the page is never created or executed.
Context: I'd like to run some scripts that need to use basic APIs/DOM manipulation like window, document, etc. These don't run on serverside gs files. I want this to happen without having to open a sidebar.

Auto submit google form after a time limit

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...

Google Script pulling dynamic HTML iFrame from Google Sheet Cell

I am working on a incorporating a Google Form as an iFrame in a Modal Dialog box on my Google Spreadsheet (it is already accessible through a customer menu) with iFrame code that dynamically changes based on what's going on in the spreadsheet.
My Google Script to reference the HTML file is:
//Help Guide
function openHelp() {
var html = HtmlService.createHtmlOutputFromFile('Help Guide').setHeight(560).setWidth(814.81).setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, ' ');
}
When I paste the generated iFrame code into the HTML file 'Help Guide', it loads as desired. However, I would like to instead reference my iFrame code that is housed on Sheet1!C1 when I run the openHelp() function from the menu. This would enable me to dynamically change the iFrame based on the spreadsheet. I have explored using Google's HTML Template service, but haven't been able to figure it out.
I am sure the script is quite easy, but I am unfamiliar with what code needs to be written in the HTML file to pull that generated iFrame code from Sheet1!C1.
Thanks!
Get some HTML code from a Cell in a Spreadsheet
You need to update the variables iframeSheet and iframeCell.
Create the iframe statement.
Deploy as a web app. The doGet is already there. You can also run the showIframeDialog() function to see it work.
The Code:
function getIframeCode()
{
var iframeSheet='Sheet1';
var iframeCell='A1';
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(iframeSheet);
var hl=sh.getRange(iframeCell).getValue();
return hl;
}
function showIframeDialog()
{
var ui=HtmlService.createHtmlOutputFromFile('iframecode');
SpreadsheetApp.getUi().showModelessDialog(ui, 'Dynamic Iframe')
}
function doGet()
{
var ui=HtmlService.createHtmlOutputFromFile('iframecode');
return ui.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
The iframecode.html file:
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function() {
google.script.run
.withSuccessHandler(updateIframeCode)
.getIframeCode();
});
function updateIframeCode(hl)
{
document.getElementById("iframe").innerHTML=hl;
}
console.log("My code");
</script>
</head>
<body>
<div id="iframe"></div>
</body>
</html>

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.