Apps-script function with dynamic parameter - google-apps-script

I am testing the app script platform and I have a doubt when using this code called from HTML file:
JSON.parse(<?= JSON.stringify(getDataFromSheet("tyreUse", "valueSearched")); ?>);
If I set the string value directly it works.
If I try to pass a variable that is declared in it does not recognize it. How can I pass a JS variable to the app script function like next example?
let value_searched = "cars";
JSON.parse(<?= JSON.stringify(getDataFromSheet("tyreUse", value_searched)); ?>);

Scriptlets like <?= ?> are used in html templates to load data from the server into html pages prior to rendering. If you want to pass data back to a server side function then you can use google.script.run and there are restrictions on the data types that you can pass.
google.script.run

Here is an example of getting data from spreadsheet dynamically. I typically build my page and then use an anonymous function of the form (function () {}()); to get the data from spreadsheet and populate the HTML elements with the values.
Create an HTML file HTML_Demo:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input id="A8SBwf" type="text">
<input id="gNO89b" type="button" value="Click Me" onclick="buttonOnClick()">
<script>
function buttonOnClick() {
try {
google.script.run.withSuccessHandler(
function(response) {
document.getElementById("A8SBwf").value = response;
}
).getCellA1();
}
catch(err) {
alert(err);
}
}
</script>
</body>
</html>
Then in Code.gs create the getCellA1:
function getCellA1() {
try {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var range = sheet.getRange("A1");
return range.getValue();
}
catch(err) {
return err.message;
}
}

Related

How to add text to an open Dialog window?

I am working on a Google Sheets macro that displays some text to the user, then presents some buttons for the user to interact with. The buttons run another function and I am struggling with how to have the button display the text to the user.
I can't find the method or object I am supposed to use to grab the currently open window and edit the html to add more text. Or if that isn't possible, how can I close the open window and then display a new window that also has the old text?
function example(text,title,height=90,width=350) {
const dialogbutton = '<br><input type="button" value="Do Stuff" onClick="google.script.run.doStuff();" />'
var html=HtmlService.createHtmlOutput(text+dialogbutton).setHeight(height).setWidth(width)
SpreadsheetApp.getUi().showModelessDialog(html, title);
}
function doStuff() {
const dialogWindow = ???//I am hoping to retrieve the open window as an object
const text = getText() //run some other function and get the new text to insert
dialogWindow.displayedText += text //modify the displayed window to add the new text
}
Here is a very simple example of how to communicate with the server (i.e. Spreadsheet or Doc). In this case a spreadsheet with Sheet1!A1 = hello
Here is a simple dialog
Server side code Code.gs bound to a spreadsheet
function showTest() {
var html = HtmlService.createTemplateFromFile("HTML_Simple");
html = html.evaluate();
SpreadsheetApp.getUi().showModalDialog(html,"Test");
}
function doStuff() {
try {
// let get a value from spreadsheet
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet1");
return sheet.getRange("A1").getValue();
}
catch(err) {
Logger.log("Error in doStuff() "+err);
}
}
HTML Page HTML_Simple.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input type="button" value="Do Stuff" onClick="doStuffOnClick()">
<input type="text" id="whatStuff">
<script>
function doStuffOnClick() {
try {
google.script.run.withSuccessHandler(
function(response) {
document.getElementById("whatStuff").value = response;
}
).doStuff();
}
catch(err) {
alert(err);
}
}
</script>
</body>
</html>
Reference
HTML Service Best Practices
google.script.run()

how to do to join the adress mail with "mailto", in javascript?

Using google app script, the adresse mail filled in the variable , from google sheet API and then, I want to join "mailto:" with the variable of adress mail . This allows to appear this variable in html .
How to do it ?
in google app script:
var Email_user = Session.getActiveUser().getEmail();
var hrefmail = "mailto:"+Email_user ;
var val_htm_mail_1 =hrefmail ;
:
:
:
theHTML.linkEmail = val_htm_mail_1; ( this is to fill the variable "val_htm_mail_1" to send it to HTML
in html :
<span id="linkEmail"><?=linkEmail?></span><br />
thank you in advance for this helpness
I recommend using HTML DOM Edit HTML content instead of a scriptlet. Here's the w3 reference so you can try to write it yourself, instead of just copying mine. Here is an example program:
code.gs
function doGet() {
return HtmlService.createTemplateFromFile('Index')
.evaluate();
}
function include(filename) {
var Email_user = Session.getActiveUser().getEmail();
var hrefmail = "mailto:"+Email_user ;
val_htm_mail_1 = hrefmail ;
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<span id="linkEmail">...</span><br/>
<script>
document.getElementById("linkEmail").innerHTML = this.val_htm_mail_1;
</script>
<?!= include ('javascript'); ?>
</body>
</html>
javascript.html
<script>
window.addEventListener('load', function() {
console.log('Page is loaded');
});
</script>
*Note you may need to adjust this depending on your browser settings

How to pass file upload blob from HTML form to server-side Apps Script?

The Google support article example under the Forms heading is broken. From the article:
If you call a server function with a form element as a parameter, the form becomes a single object with field names as keys and field values as values. The values are all converted to strings, except for the contents of file-input fields, which become Blob objects.
I tested this by passing a Form element containing 5 text inputs and a file, then logging Object.keys() on the form object. It returned only the 5 text fields, the file was stripped from the form object. Attempting to assign the file blob directly returned Exception: Invalid argument: blob
How do I pass the file blob from the client-side Form to the server-side Apps Script?
EDIT: To clarify, I also copy-pasted the example provided by Google verbatim. It errors with Exception: Invalid argument: blob.
To reproduce:
Create new Google Apps Script project
Index.html contents:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
// Prevent forms from submitting.
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
function handleFormSubmit(formObject) {
google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
}
function updateUrl(url) {
var div = document.getElementById('output');
div.innerHTML = 'Got it!';
}
</script>
</head>
<body>
<form id="myForm" onsubmit="handleFormSubmit(this)">
<input name="myFile" type="file" />
<input type="submit" value="Submit" />
</form>
<div id="output"></div>
</body>
</html>
Code.gs contents:
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function processForm(formObject) {
var formBlob = formObject.myFile;
var driveFile = DriveApp.createFile(formBlob);
return driveFile.getUrl();
}
Publish as Web App
Submit the form with any file
Observe error in View -> Stackdriver Logging -> Apps Script Dashboard
Here's an example:
html:
<!DOCTYPE html>
<html>
<head>
<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 fileUploadJs(frmData) {
document.getElementById('status').style.display ='inline';
google.script.run
.withSuccessHandler(updateOutput)
.uploadTheFile(frmData)
}
function updateOutput(info) {
var br='<br />';
var outputDiv = document.getElementById('status');
outputDiv.innerHTML = br + 'File Upload Successful.' + br + 'File Name: ' + info.name + br + 'Content Type: ' + info.type + br + 'Folder Name: ' + info.folder;
}
console.log('My Code');
</script>
<style>
body {background-color:#ffffff;}
input{padding:2px;margin:2px;}
</style>
</head>
<body>
<h1 id="main-heading">Walking Tracks</h1>
<h3>Upload GPS Tracks Files</h3>
<div id="formDiv">
<form id="myForm">
<input name="fileToLoad" type="file" /><br/>
<input type="button" value="Submit" onclick="fileUploadJs(this.parentNode)" />
</form>
</div>
<div id="status" style="display: none">
<!-- div will be filled with innerHTML after form submission. -->
Uploading. Please wait...
</div>
<div id="controls">
<input type="button" value="Close" onClick="google.script.host.close();" />
</div>
</body>
</html>
server code:
function uploadTheFile(theForm) {
var fileBlob=theForm.fileToLoad;
var fldr = DriveApp.getFolderById('FolderId');
var file=fldr.createFile(fileBlob);
var fi=formatFileName(file);
var fileInfo={'name':fi.getName(),'type':fileBlob.getContentType(), 'size':fileBlob.getBytes(), 'folder':fldr.getName()};
return fileInfo;
}
I can confirm that this doesn't work in G-Suite Enterprise. I don't know why because I cannot find documentation that says how Google is serializing the data. It could be a browser/computer security setting or something in G-Suite.
However, there is an easier way to accomplish your need. You can use a Google Form with a file upload question and then create an on form submit trigger/event on it to copy the file to a team/shared drive. Here is sample code if you want to attach the trigger to the Google Form itself:
// ID of the destnation folder to save the file in
var destinationFolderID = "10gkU_2V9iYy-VKudOCOjydEpoepPTgPv"
function saveFileToTeamDrive(e)
{
// a place to save the URL of the uploaded file
var fileID;
// go through all of the responses to find the URL of the uploaded file
e.response.getItemResponses().forEach(function(itemResponse){
// once we find the question with the file
if(itemResponse.getItem().getTitle() == "File Upload Test")
{
// get the file ID from the response
fileID = itemResponse.getResponse();
return;
}
});
// stop if we didn't have one
if(!fileID.length) return;
// get the first index in the array
fileID = fileID[0];
// get the file
var file = DriveApp.getFileById(fileID);
// get the destination folder
var destinationFolder = DriveApp.getFolderById(destinationFolderID);
// make a copy
var newFile = file.makeCopy(destinationFolder);
Logger.log(newFile.getUrl());
}
You can also attach to the on form submit event of a Google Sheet that is linked to a Google Form. I find that way easier cause the Google Sheet on form submit trigger/event includes a JSON of the question/answers so you don't have to iterate all of them to find it. It also means you can re-run a submission if it failed.
WARNING
One important note, if you do either of these things do not give anyone else edit access to the code. This is because as soon as you create and authorize the trigger, anyone who has edit access to the code would be able to use it to gain access to your Google Drive (and anything else the trigger is authorized for). Please see securing a Google Apps Script linked to an authorized trigger so others can edit for more information.

Automated download of file from Drive in Web App?

I'm trying to write a polling web app that checks Google Drive and automatically downloads files without user interaction.
Using ContentService I have managed to get things working when I place the code in the doGet function.
However this only works once and there does not appear to be a way to refresh or reload the page automatically on a timer event.
Using a SetTimeout on the client side javascript I can get a function on the server side to automatically trigger at certain intervals but then I am stuck with what to do with the output from ContentService.
The on Success call back will not accept the output from createTextOutput.
My solution does not not need to be deployed and I'm happy to execute from the editor if that expands my choices.
So once I have the output from createTextOutput on my server side what am I supposed to do with it to get it back to the client in order to cause the file download?
I have included the code if that helps.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
setTimeout(
function ()
{
document.getElementById('results').innerHTML = 'Event Timer';
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.fetchFromGoogleDrive();
}, 60000);
function onSuccess(sHTML)
{
document.getElementById('results').innerHTML = 'File Downloaded ' + sHTML;
}
function onFailure(error)
{
document.getElementById('results').innerHTML = error.message;
}
</script>
</head>
<body>
<div id="results">Waiting to DownLoad!</div>
id="Fetch">Fetch!</button>
</body>
</html>
function doGet() {
Logger.log('doGet');
return HtmlService.createHtmlOutputFromFile('form.html');
}
function fetchFromGoogleDrive() {
//Logger.Log('fetchFromGoogleDrive');
var fileslist = DriveApp.searchFiles("Title contains 'Expected File'");
if (fileslist.hasNext()) {
//Logger.Log('File found');
var afile = fileslist.next();
var aname = afile.getName();
var acontent = afile.getAs('text/plain').getDataAsString();
var output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.CSV);
output.setContent(acontent);
output.downloadAsFile(aname);
return afile.getDownloadUrl();
}
else
{
//Logger.Log('No File Found');
return 'Nothing to download';
}
//Logger.log('All files processed.');
}
EDIT: Different answer after clarification.
If this is intended to run automated as a webapp what I would do is return the getDownloadUrl and create a new iFrame using that that as the source.
Apps Script
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function getDownloadLink(){
//slice removes last parameter gd=true. This needs to be removed. slice is a hack you should do something better
return DriveApp.getFileById("0B_j9_-NbJQQDckwxMHBzeVVuMHc").getDownloadUrl().slice(0,-8);
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<p id="dlBox"></p>
</body>
<script>
function buildLink(res){
var dlBox = document.createElement("iframe");
dlBox.src = res;
document.getElementById("dlBox").appendChild(dlBox)
}
//automate this as you need
google.script.run
.withSuccessHandler(buildLink)
.getDownloadLink();
</script>
</html>

How to call google apps script server-side functions synchronously?

I wrote a google apps script code, it will open a google spread sheet, and list the values by row, but there are 2 problems:
1. The output by random order.
2. The div text which id "loding" change to "Finished!" before list all of values.
I thought the script will wait for server-side function return when I run it by "withSuccessHandler()", but it's not.
How can I correct it?
index.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function jsListValue() {
// Get count.
google.script.run.withSuccessHandler(function(count) {
// List all values.
for( count; count>0; count=count-1) {
// Get a value.
google.script.run.withSuccessHandler(function(content) {
// Shows in "output".
var new_div = document.createElement("div");
new_div.appendChild(document.createTextNode(content));
document.getElementById("output").appendChild(new_div);
}).gsGetValue(count);
}
// Change loding notice.
document.getElementById("loding").innerHTML = "Finished!";
}).gsGetCount();
}
</script>
</head>
<body onload="jsListValue()">
<div id="output"></div>
<div id="loding">Loding now...</div>
</body>
</html>
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function gsOpenSheet() {
// Return sheet of the note data.
return (SpreadsheetApp.openById("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").getSheetByName("sheet1"));
}
function gsGetCount() {
// Return last row index in this sheet.
return (gsOpenSheet().getLastRow());
}
function gsGetValue(index) {
// Return value in the (index,1).
return (gsOpenSheet().getRange(index,1).getValue());
}
GAS is very similar to Javascript, and all calls to Google's server side functions are asynchronous. You cannot change this (at least I haven't seen any doc reg. that).
What you can do, is, use a callback function on the client side which polls the server for a "success" return value. It'll keep polling it say for 1 minute, or else exit. Let it set a client flag to "true" if the success value is returned by the server. Nothing should proceed on the client side, unless the flag is true. In this way, you can control what happens on the client side.
You want to use withSuccessHandler Docs
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess(numUnread) {
var div = document.getElementById('output');
div.innerHTML = 'You have ' + numUnread
+ ' unread messages in your Gmail inbox.';
}
google.script.run.withSuccessHandler(onSuccess)
.getUnreadEmails();
</script>
</head>
<body>
<div id="output"></div>
</body>
</html>
google.script.run is asynchronous, which means that is impossible to predict in what order gsGetValue(count) will return. When you're using withSuccessHandler you must perform the next action inside the callback function.
My suggestion is to get all the range you want and put it on an array. You can create a serverside function to do this. The code would look like this:
//Serverside function
function getDataForSearch() {
const ws = SpreadsheetApp.openById("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").getSheetByName("sheet1");
return ws.getRange(1,1,ws.getLastRow(),1).getValues();
}
getValues() will return an array of arrays that should contain all values from the range specified. More information about here
On your client-side, the script should be like this:
//array to get the data from getRange()
var data;
function jsListValue() {
google.script.run.withSuccessHandler(function(dataReturned){
data = dataReturned;
var new_div;
data.forEach(function(r){
new_div = document.createElement("div");
new_div.appendChild(document.createTextNode(r[0));
document.getElementById("output").appendChild(new_div);
});
document.getElementById("loding").innerHTML = "Finished!";
}).getDataForSearch();
}
As hinted by #Iuri Pereira If you return a parameter from the google script code, and then use it in the page javascript, then the procedure runs synchronously.
HTML:
<button onclick="google.script.run.withSuccessHandler(js_function).gs_code();">action</button>
GS:
function gs_code() {
// do Something;
return true;
}
HTML javascript:
function js_function(gs_return_value) {
console.log(gs_return_value);
}