How to call Google Apps Script encapsulated function from html template - google-apps-script

Hi I am using Google App Scripts on a Google Sheet and I wanted to organize my code, so I tried encapsulating some of the functions. However, I can't seem to figure out how to call the encapsulated functions from the html templates.
Does anyone know if it is possible to call an encapsulated function with google.script.run from an html template?
Example:
Code.gs
function onOpen(e){
var menu = SpreadsheetApp.getUi().createMenu('Document Routing')
.addItem('View/Edit My Routes', 'ViewMyRoutes')
.addToUi();
}
function ViewMyRoutes() {
var html = HtmlService.createHtmlOutputFromFile('MyRoutes')
.setTitle('My Routes')
.setWidth(300);
SpreadsheetApp.getUi()
.showSidebar(html);
}
User.gs
var User = function(){
return {
GetMyRoutes: function (){
var userProperties = PropertiesService.getUserProperties();
var routeData = userProperties.getProperty("SpreadsheetRoutes");
return routeData;
}
};
}();
Sample.html
<h1>Edit Route</h1>
<select id="EditRoute" onchange="LoadSelectedRoute()"></select>
<div id='divRoutes'></div>
<script>
function LoadEditDropDown(){
google.script.run
.withFailureHandler(onFailure)
.withSuccessHandler(OnGetRoutesSuccess)
.User.GetMyRoutes();
function OnGetRoutesSuccess(scriptProperties){
//do stuff with scriptProperties here
}
}
LoadEditDropDown();
</script>

How about this? In such case, I use this way. But I don't know whether this is the best way. I'm sorry. I worry about that this may be the opposite way for you want.
1. Please add this function to GAS
function UserGetMyRoutes() {
return User.GetMyRoutes();
}
2. Please modify google.script.run as follows
From :
google.script.run
.withFailureHandler(onFailure)
.withSuccessHandler(OnGetRoutesSuccess)
.User.GetMyRoutes();
To :
google.script.run
.withFailureHandler(onFailure)
.withSuccessHandler(OnGetRoutesSuccess)
.UserGetMyRoutes();

Related

Make a google Script Run from a HTML front

I have a script that will run an HTTP request to our server and bring the most recent orders.
We want to be able to run the script on request but also be able to Install it as an addon to different sheets for our different stores
The front End of the app is generated by this html
<link href="https://ssl.gstatic.com/docs/script/css/add-ons.css"
rel="stylesheet">
<div class="sidebar">
<div class="block form-group">
<button class="blue" id="load_orders">Import Order Data</button>
</div>
<div id='orders'></div>
</div>
<script>
$(function onSuccess(load_orders) {
});
withSuccessHandler(onSuccess).importcogs();
});
</script>
Then on the .gs I have a script to show the app (before we Deploy it) and the import orders script
function onInstall() {
onOpen();
}
function onOpen() {
SpreadsheetApp.getUi()
.createAddonMenu() // Add a new option in the Google Docs Add-ons Menu
.addItem("Import Order Data", "showSidebar")
.addToUi(); // Run the showSidebar function when someone clicks the menu
}
function showSidebar() {
var html = HtmlService.createTemplateFromFile("Front")
.evaluate()
.setTitle("Import Order - Search"); // The title shows in the sidebar
SpreadsheetApp.getUi().showSidebar(html);
}
function importcogs() {
Logger.log("import begin");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var urlsheet = ss.getSheetByName("GetInfo");
var request = urlsheet.getRange(5,2).getValue();
Logger.log(request);
var response = UrlFetchApp.fetch(request);
Logger.log("download data finish");
Logger.log(response.getContentText());
var sheet = ss.getSheetByName("Data");
var obj = JSON.parse(response);
let vs = obj.data.map(o => Object.values(o));//data
vs.unshift(Object.keys(obj.data[0]));//add header
sheet.getRange(1,1,vs.length, vs[0].length).setValues(vs);//output to spreadsheet
}
I Haven't been able to link the "Import orders" button to the script for some reason .
As what Cooper has said in the above comment, it is not a standalone function:
Sample Usage:
google.script.run.withSuccessHandler(onSuccess).importcogs();
This will in return runs the importcogs function and will trigger onSuccess function if the function successfully finished. Parameters of the onSuccess will be the return of the function called importcogs if it has any.

Calling library function from html in that library

In order to share with my colleagues the AppsScript code I am developing, I created a standalone project used as a library in our Docs template.
This library contains:
a .gs file for the server side code
a .html file for the client side sidebar
In the sidebar, I have a button which triggers a call to a function from the library with a parameter.
The javascript call is the following:
google.script.run
.withFailureHandler(
function(msg, element) {
showError(msg, $('#button-bar'));
})
.test();
I read on other pages that the library code wasn't exposed so the test function is in fact in the AppsScript code of my Docs and calls the equivalent library function.
As it was suggested here: Can a Google Spreadsheet Apps Script library contain a user dialog?
Code in the Docs AppsScript:
function test()
{
myLibrary.test();
}
Code in myLibrary library:
function test()
{
DocumentApp.getUi().alert('test');
}
The problem is that the failure handler from the javascript returns a ScriptError stating that I need to have the authorization to perform this action.
Any ideas of what I am doing wrong?
PS: I know I could make an add-on but this is not something I can easily do inside my company :)
Yes, when you use the html code (sidebar) from your library it'll call the test() function inside the script bound to the document rather that the one in the library.
You need to request permission to the user to be able to prompt UI elements, you can do this with a custom menu [1].
Also, the users need to have at least read access on the library [2]. I add the withSuccessHandler function [3] to show the response from client-side in the sidebar. The following code worked for me:
Script bound to the document:
function onOpen() {
DocumentApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.createMenu('Custom Menu')
.addItem('Show sidebar', 'showSidebar')
.addToUi();
}
function showSidebar() {
myLibrary.showSidebarLibrary();
}
//
function test() {
myLibrary.test();
}
Library:
code.gs
function showSidebarLibrary() {
var html = HtmlService.createHtmlOutputFromFile('Page')
.setTitle('My custom sidebar')
.setWidth(300);
DocumentApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.showSidebar(html);
}
function test() {
DocumentApp.getUi().alert('test');
return "Success";
}
Page.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Hello, world!<br>
<input type="button" value="Alert" onclick="alert()" /><br>
<input type="button" value="Close" onclick="google.script.host.close()" />
<p id="msg">Replace message with response<p>
</body>
<script>
function alert() {
google.script.run.withFailureHandler(handler).withSuccessHandler(handler).test();
}
function handler(msg) {
document.getElementById("msg").innerHTML = msg;
}
</script>
</html>
[1] https://developers.google.com/apps-script/guides/menus
[2] https://developers.google.com/apps-script/guides/libraries#gaining_access_to_a_library_and_including_it_in_your_project
[3] https://developers.google.com/apps-script/guides/html/communication#success_handlers

How to use scriptlets in HTMLOutput in Google Apps Script

I'm loading a modal dialog with:
var html = HtmlService.createHtmlOutputFromFile('File')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(1000)
.setHeight(700);
SpreadsheetApp.getUi()
.showModalDialog(html, 'My Page');
Now, in File.HTML, I want to load another HTML file with CSS settings, how do I do that?
I've tried including it as in HtmlTemplate using scriptlets but it doesn't work:
<?!= include('File'); ?>
EDIT:
I have defined the include function in code.gs:
function include (file) {
return HtmlService.createTemplateFromFile(file).evaluate().getContent();
}
The problem is that you are using:
createHtmlOutputFromFile
instead of:
createTemplateFromFile
You need to create a template:
This is what you are seeing:
The scriptlet is not running, but being interpreted as text.
This is what you want to see:
Here is how the code should be:
Code.gs
// Use this code for Google Docs, Forms, or new Sheets.
function onOpen() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.createMenu('Dialog')
.addItem('Open', 'openDialog')
.addToUi();
}
function openDialog() {
var html = HtmlService.createTemplateFromFile('index')
.evaluate();//This is necessary
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.showModalDialog(html, 'Dialog title');
}
function include(File) {
return HtmlService.createHtmlOutputFromFile(File).getContent();
};
index.html
<?!= include('File'); ?>
Hello, world!
<input type="button" value="Close"
onclick="google.script.host.close()" />
File.html
<div>
This is a test. it worked!
</div>
Basically, you need to change:
var html = HtmlService.createHtmlOutputFromFile('index')
to:
var html = HtmlService.createTemplateFromFile('index')
Create a TEMPLATE from file.
And I also changed the code to this:
function openDialog() {
var html = HtmlService.createTemplateFromFile('index')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
Original answer:
include is not something like a keyword or a built in function. You need to create a function in a .gs script file named include.
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
};
Also, you can't mix the HTML Service and the UI Service. I don't know if that's what you are trying to do, but I thought I'd mention it.
What you want to accomplish is describe in the documentation here:
Documentation - Best Practices

Google Sheets & Docs Script fails to create add-on menu when add-on is installed from Chrome store

Possible cause is the following:
Usually this is caused by a problem with the Authorization Lifecycle, specifically the opening stage.
The most common culprit is a global variable in the code that tries to access Google services without authorization, like:
var doc = DocumentApp.getActiveDocument();
See the documentation:
Warning: When your onOpen(e) function runs, the entire script is loaded and any global statements are executed. These statements execute under the same authorization mode as onOpen(e) and will fail if the mode prohibits them. This preventsonOpen(e) from running. If your published add-on fails to add its menu items, look in the browser's JavaScript console to see if an error was thrown, then examine your script to see whether the onOpen(e) function or global variables call services that aren't allowed in AuthMode.NONE.
Here is my script:
function onOpen(e) {
SpreadsheetApp.getUi().createAddonMenu()
.addItem('Browse Templates', 'browseTemplates')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}
function browseTemplates(){
collectBasicData();
// Display a modal dialog box with custom HtmlService content.
var htmlOutput = HtmlService
.createTemplateFromFile("Gallery").evaluate()
.setWidth(700)
.setHeight(510);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Spreadsheet123 - Template Vault');
}
function collectAllData(){
var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(DATA_SHEET);
DATA = sheet.getDataRange().getValues();
return DATA;
}
function collectBasicData(){
var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(PIVOT_SHEET);
var tabSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(TAB_SHEET);
BASIC_DATA = {
"tab_about" : getValue(tabSheet,"B1"),
"tab_help": getValue(tabSheet,"B2"),
"pivot":sheet.getDataRange().getValues()
};
return false;
}
function getValue(sheet,addr){
return sheet.getRange(addr).getValue().toString().replace(/^\s+|\s+$/g, '');
}
function createACopy(id){
var docName = DocsList.getFileById(id).getName();
return DocsList.getFileById(id).makeCopy(docName).getUrl();
}
function insertInCurrent(id){
var destinationSpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheets = SpreadsheetApp.openById(id).getSheets();
for(var i=0;i<sourceSheets.length;i++){
var sheetName = sourceSheets[i].getName();
var source = SpreadsheetApp.openById(id).getSheetByName(sheetName);
source.copyTo(destinationSpreadSheet).setName(sheetName);
}
}
Can you please help me a little or a lot.
Thanks in advance
OK, so my code was actually correct, but my mistake was that I should have saved any changes made to my code under the new version before publishing it to the store, which I did not and therefore all changes that I made were simply ignored.
function onOpen(e) {
SpreadsheetApp.getUi().createAddonMenu()
.addItem('Browse Templates', 'browseTemplates')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}

Initiate a download from google apps script

I added a new menu item to my spreadsheet using google apps script. This menu item creates a file, but I'd like for it to initiate the download of the file after creating it.
Is this possible?
Remember, this is not a web app, but a menu item in my spreadsheet.
Thanks
Edit:
Thanks to Serge insas' suggestion, the following simple script works perfectly, and opens a download window with the link I need:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var csvMenuEntries = [ {name: "Zip all CSVs", functionName: "saveAsCSV"} ];
ss.addMenu("CSV", csvMenuEntries);
};
function saveAsCSV() {
var folder = createCSVs(); // creates a folder with CSV for each Sheet
var zipFile = zipCSVs(folder, "DI.zip"); // creates a zip of all CSVs in folder
var ui = UiApp.createApplication().setTitle("Download");
var p = ui.createVerticalPanel();
ui.add(p);
p.add(ui.createAnchor("Download", zipFile.getDownloadUrl()));
SpreadsheetApp.getActive().show(ui)
}
EDIT : read the comments below, Zig Mandel is perfectly right when he points out the limitations of the "complicated" version, it was really a simple (and fun) exercice to show other methods.
I think you'll have to use an intermediate Ui as a popup to confirm the download.
After that there are 2 possible ways that I know, one is very simple and the other is quite cumbersome, make your choice, the code below shows both of them.
note : to use the complicated one you need to deploy your app (ie save a version and deploy as webapp), for the simple one just use it "as it is". (I show the simple in the code comments).
The code :
function onOpen() {
var menuEntries = [ {name: "test download", functionName: "downloadFile"}
];
var sheet = SpreadsheetApp.getActiveSpreadsheet();
sheet.addMenu("Utils",menuEntries);
}
function downloadFile(){
var file = DriveApp.createFile('test file', 'Some content in this file to test it');
var fileID = file.getId();
var fileName = file.getName();
var ui = UiApp.createApplication().setTitle('Download');
var url = ScriptApp.getService().getUrl()+'?&ID='+fileID+'&name='+fileName;
var p = ui.createVerticalPanel();
ui.add(p);
p.add(ui.createAnchor('click to download', url));
p.add(ui.createAnchor('or use this link ',file.getDownloadUrl()));// this is the simple one, just get the file you created and use getDownloadUrl()
SpreadsheetApp.getActive().show(ui)
}
function doGet(e){
var fileId = e.parameter.ID;
var fileName = e.parameter.name;
var fileString = DocsList.getFileById(fileId).getContentAsString();
return ContentService.createTextOutput(fileString).downloadAsFile(fileName);
}
PS : I had some fun writing this, the "complicated version" is really funny imho :-)
OP's answer is deprecated (in 2021), so I made a more general purpose one based on it.
Code.gs:
// Runs when the spreadsheet starts, adds a tab at the top
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script Menu')
.addItem('Download a file!', 'dlFile')
.addToUi();
}
// Run when you click "Download a file!"
function dlFile() {
let file = DriveApp.getRootFolder().createFile('Hi.txt', 'Hello, world!');
// Create little HTML popup with the URL of the download
let htmlTemplate = HtmlService.createTemplateFromFile('Download.html');
htmlTemplate.dataFromServerTemplate = { url: file.getDownloadUrl() };
let html = htmlTemplate
.evaluate()
.setWidth(400)
.setHeight(300);
SpreadsheetApp.getUi()
.showModalDialog(html, 'Download');
};
Download.html:
<!DOCTYPE html>
<html>
<head>
<script>
let data = <?!= JSON.stringify(dataFromServerTemplate) ?>; // Stores the data directly in the javascript code
function downloadFile() {
document.getElementById("dlBtn").innerText = "Downloading..";
window.open(data.url, '_blank');
document.getElementById("dlBtn").disabled = true;
}
</script>
</head>
<body>
<button id="dlBtn" onclick="downloadFile()">Download</button>
</body>
</html>
Just adding to #dr-bracket's answer where I made some small additions to the scripts in an attempt to stop the browser from navigating away to a new tab.
I got the idea from:
Download a created Google Doc from a deployed web app (Google Apps Script)
Where #tanaike uses the google.script.run.withSuccessHandler class and method to create a popup prompt then closes and returns to your app on download. (May not popup if your browser settings are set to not pick download location.)
Code.gs:
// Runs when the spreadsheet starts, adds a tab at the top
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script Menu')
.addItem('Download a file!', 'dlFile')
.addToUi();
}
// Run when you click "Download a file!"
function dlFile() {
let file = DriveApp.getRootFolder().createFile('Hi.txt', 'Hello, world!');
// Create little HTML popup with the URL of the download. Added filename to object. ~~~~~~~~~~~
let htmlTemplate = HtmlService.createTemplateFromFile('Download.html');
htmlTemplate.dataFromServerTemplate = { url: file.getDownloadUrl(), name: file.getName() };
let html = htmlTemplate
.evaluate()
.setWidth(400)
.setHeight(300);
SpreadsheetApp.getUi()
.showModalDialog(html, 'Download');
};
// Added the following to satisfy the withSuccessHandler method: ~~~~~~~~~~~~~
function createDownloadUrl(data) {
return {
url: data.url,
name: data.name,
};
}
Download.html:
<!DOCTYPE html>
<html>
<head>
<script>
let data = <?!= JSON.stringify(dataFromServerTemplate) ?>; // Stores the data directly in
// the javascript code
function downloadFile() {
const dlBtn = document.getElementById("dlBtn");
dlBtn.innerText = "Downloading..";
// window.open(data.url);
// Replaced with:
// the url and name variables will be returned here from the
// code.gs function createDownloadEvent() after it runs successfully.
google.script.run
.withSuccessHandler(({ url, name }) => {
const a = document.createElement("a");
document.body.appendChild(a);
a.download = name;
a.href = url;
a.target = "_blank";
a.click();
})
.createDownloadEvent(data);
dlBtn.disabled = true;
}
</script>
</head>
<body>
<button id="dlBtn" onclick="downloadFile()">Download</button>
</body>
</html>
Resources:
https://developers.google.com/apps-script/guides/html/reference/run#withsuccesshandlerfunction
Download a created Google Doc from a deployed web app (Google Apps Script)