Reload Add On from Code - google-apps-script

I have a google apps-script add-on which is loaded in the side bar of a Google Spreadsheet.
How does one reload an apps script add-on ones the user switches sheets or from button click ?

The simplest way to reload an ad-on is to start/launch it again from the add-on's menu - this will re-load the sidebar. Or you can add a button to your sidebar which runs google.script.run.showSidebar() (or whatever your server-side function for showing the sidebar is called).
Since add-ons only have access to simple triggers (onInstall(), onOpen() and onEdit()) and can't (yet) tell what a user does outside the add-on, you will have to write your own javascript function in the sidebar's html page to re-set the your add-on's user interface to default state (I assume that is what you mean by "reload"), i.e to reset all form fields to default values, remove any injected help/status text, etc etc.
To have this function execute on button click is not too hard - just trigger the function from the button's onclick event. With a bit more work you can even add a 'Reset' menu item in your add-on's menu that does the same thing.
To make such function run 'automatically' when user switches sheet is also possible, but will require polling for spreadsheet changes. Basically you can write a javascript function in your add-on's sidebar page that runs on a certain interval and calls a server-side function that checks if currently active sheet is same as before (which you can store in userProperties, for example). If the sheet is different, call your js function that resets the ui of your add-on + update the userProperty with the name of currently active sheet. Keep in mind that there will be a bit of delay between user switching sheets and your add-on running its reset code and reloading its ui - if that is an issue, then reloading the ui from button click is a better option.
Here is some sample code to give you an idea of what you can do. You can view the working spreadsheet here
Code.gs
function onOpen(e) {
// Add this add-on to Add-ons menu
SpreadsheetApp.getUi().createAddonMenu()
.addItem('Start / Reset', 'showSidebar')
.addToUi();
// save current active sheet name in user properties for later checking if user switched sheets
saveSheetNameToProperty();
};
function onInstall(e) {
onOpen(e);
};
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('Sidebar').setTitle('Add-on Reset Test');
SpreadsheetApp.getUi().showSidebar(ui);
};
/**
* Saves current active sheet name to user property lastActiveSheet
* #param String sheetname Name of sheet to save to user property. If undefined (not passed), saves current active sheet name.
*/
function saveSheetNameToProperty(sheetname) {
var sheetName = sheetname || SpreadsheetApp.getActiveSheet().getName();
PropertiesService.getUserProperties().setProperty("lastActiveSheet", sheetName)
};
/**
* Checks if user has switched sheets by comparing current active sheet name to name stored in user property
* #return Boolean True/False flag denoting if sheet was switched. True=sheet was switched; False=still on same sheet
*/
function checkSheetChanged() {
var sheetChanged = false;
var sheetName = SpreadsheetApp.getActiveSheet().getName();
var lastActiveSheet = PropertiesService.getUserProperties().getProperty("lastActiveSheet");
if (sheetName!=lastActiveSheet) {
sheetChanged = true;
saveSheetNameToProperty(sheetName);
// if you'd rather just reload the whole sidebar, then un-comment the line below and delete the return statement
// showSidebar();
}
return sheetChanged;
};
Sidebar.html
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<div class="sidebar branding-below">
<form id="addonForm">
<div class="block">
<label for="selectBox">Select a value:</label>
<select name="selectBox" id="selectBox">
<option value="" selected>Select a value...</option>
<option value="Value 1">Value 1</option>
<option value="Value 2">Value 2</option>
<option value="Value 3">Value 3</option>
</select>
</div>
<div class="block">
<label for="textBox">Enter some text:</label>
<input type="text" name="textBox" id="textBox" placeholder="Enter some text...">
</div>
<div class="block" id="button-bar">
<button type="button" class="blue" id="simpleResetBtn" onclick="resetForm(true);" title="I reset the sidebar's form controls to their default state">Reset form</button>
<button type="button" class="red" id="reloadAddonBtn" onclick="google.script.run.showSidebar();" title="I completely reload the sidebar - fresh start!">Reload add-on</button>
</div>
</form>
<div class="block" id="statusText" style="color:#666; margin-top:10px;"></div>
</div>
<div class="sidebar bottom">
<span class="gray branding-text">Reset Add-on Sample by Azadi</span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
/**
* On document load, set up interval-based execution of checkSheetChanged() function to check if user has switched sheets
*/
$(function() {
// run checkSheetChanged() function every 5000 miliseconds
var sheetChecker = window.setInterval(checkSheetChanged, 5000);
});
/**
* Resets the form in the add-on's sidebar and shows status text.
* #param Boolean fromButtonClick Boolean flag denoting if form reset was triggered from button click or via timed script execution
*/
function resetForm(fromButtonClick) {
var buttonClick = fromButtonClick || false;
var form = $("#addonForm")[0];
var statusDiv = $("#statusText");
form.reset();
if (buttonClick) {
statusDiv.text("Addon UI has been reset from [Reset form] button click");
}
else {
statusDiv.text("Addon UI has been reset automatically via timed script following sheet switch");
}
};
/**
* Runs the checkSheetChanged() server-side function (in Code.gs) to check if user has switched sheets
* and executes checkSheetChangedCallback() function on success
*/
function checkSheetChanged() {
google.script.run.withSuccessHandler(checkSheetChangedCallback).checkSheetChanged();
};
/**
* Callback for checkSheetChanged() function.
* Resets the form in the sidebar if user has switched sheets.
* #param Boolean isDifferentSheet Boolean flag returned from server-side script. True=sheet was switched. False=user is still on same sheet.
*/
function checkSheetChangedCallback(isDifferentSheet) {
if (isDifferentSheet) {
resetForm();
}
};
</script>
Hope this helps!

Related

google apps script : a select drop down list in google sheet

I have begun using google apps script in google sheets, and i want to create a dialogue box where the user will write an input that I will use later. the dialogue box shold have a drop list that will make suggestions or complete the input.
For anyone arriving here through a Google Search:
Drop downs in Google Sheets can be achieved with data validation. Create a column of entries. Let's say like this:
A4 = Apples
A5 = Tigers
A6 = Coriander
A7 = Forest
Then select a cell, say, B4. Now go to Data in the top menu. Choose Data validation. A module will open with options. Choose "List from a range." as criteria, then enter A4:A7 as the range. You will have the option to reject other input. Now hover over B4 and click the arrow. You will see that you now have an inline dropdown menu. It may be useful to know that you can add list sources an another tab and even hide that tab to users to keep the interface clean.
Now, to answer your actual question.
You want a dropdown to appear in a popup. This can be done! It's not as fast as using an inline dropdown with data validation, but it's much fancier.
Assuming you know at least the basics of Google Apps Script, here's the code:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Custom Menu')
.addItem('Multiple choice', 'dropDownModal')
.addToUi();
}
function dropDownModal() {
var htmlDlg = HtmlService.createHtmlOutputFromFile('dropdown.html')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(350)
.setHeight(175);
SpreadsheetApp.getUi()
.showModalDialog(htmlDlg, 'A Title Goes Here');
};
function writeChoice(selection) {
const writeResponseLocation = "B4";
SpreadsheetApp
.getActiveSpreadsheet()
.getSheets()[0]
.getRange(writeResponseLocation)
.setValue(selection);
}
Then create a file called dropdown.html (in addition to the code.gs file above) and input the following:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
function onSuccess() {
google.script.host.close();
}
function submit() {
const choice = document.getElementById('choice').value;
google.script.run
.withSuccessHandler(onSuccess)
.writeChoice(choice);
}
function setup() {
const button = document.getElementById('submitbutton');
button.addEventListener("click", submit)
}
</script>
<body onload="setup()">
<p>
There will be a slight delay on submission.
</p>
<form>
<select id="choice">
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="coriander">Coriander</option>
<option value="monkey">Monkey</option>
</select>
<button id="submitbutton">Submit</button>
</form>
</body>
</html>
Now save everything and reload the sheet. A menu will appear at the end of the menu bar called Custom Menu. Select that and choose Multiple choice. You'll have to give yourself permission to the run the code you entered for this to work (then choose the menu option again). That'll do it. Tweak the code to suit your needs.

Unable to pass HTML data into Code.gs function

When I click on the run button (See my HTML code), I am unable to paste the data into sheet.
When I run the makeRequest function from the editor, I get an error - TypeError: Cannot read property 'startDate' of undefined.
HTML
<div class="block form-group">
<label for="select">Date Range</label>
<select id="select">
<option selected id="default" value="default">None</option>
<option selected id="today" value="today">Today</option>
<option selected id="yesterday" value="yesterday">Yesterday</option>
</select>
<div class="form-group">
<button id="btn">Run it</button>
</div>
</div>
Javascript. HTML
<script>
document.getElementById('btn').addEventListener("click",retrieveFacebookData);
function retrieveFacebookData () {
var facebookAccountData = {};
//I will be adding more key value pairs in this object later
facebookAccountData.startDate = document.getElementById('select').value;
google.script.run.makeRequest(facebookAccountData);
}
</script>
Code.gs
function makeRequest(facebookAccountData) {
console.log(facebookAccountData.startDate);
const row = [facebookAccountData.startDate,newDate()];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
Modification points:
From When I run the makeRequest function from the editor, I get an error - TypeError: Cannot read property 'startDate' of undefined., in this case, if you directly run makeRequest with the script editor, the argument of facebookAccountData is not declared. By this, such error occurs. In your script, it is required to run the Javascript at the sidebar, dialog and Web Apps.
About your HTML & Javasript side, I think that there is a modification point. In your Javascript, document.getElementById('btn').addEventListener("click",retrieveFacebookData); is used. But <button class="btn">Run it</button> has no ID.
When above points are reflected to your script, it becomes as follows.
Modified script 1:
When you want to directly run the function of makeRequest with the script editor, how about the following modification?
function makeRequest(facebookAccountData) {
facebookAccountData = {startDate: "sample"};
console.log(facebookAccountData.startDate);
const row = [facebookAccountData.startDate, "newDate()"];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
Modified script 2:
When you want to run the function of makeRequest with HTML & Javascript side, how about the following modification?
Google Apps Script side:
function openDialog() {
const html = HtmlService.createHtmlOutputFromFile("index");
SpreadsheetApp.getUi().showModalDialog(html, "sample");
}
function makeRequest(facebookAccountData) {
console.log(facebookAccountData.startDate);
const row = [facebookAccountData.startDate, "newDate()"];
SpreadsheetApp.getActiveSheet().appendRow(row);
}
HTML & Javascript side: index.html
<div class="block form-group"> <label for="select">Date Range</label> <select id="select">
<option selected id="default" value="default">None</option>
<option selected id="today" value="today">Today</option>
<option selected id="yesterday" value="yesterday">Yesterday</option>
</select>
<div class="form-group"> <button id="btn">Run it</button> </div>
</div>
</div>
<script>
document.getElementById('btn').addEventListener("click",retrieveFacebookData);
function retrieveFacebookData () {
var facebookAccountData = {};
//I will be adding more key value pairs in this object later
facebookAccountData.startDate = document.getElementById('select').value;
google.script.run.makeRequest(facebookAccountData);
}
</script>
In this case, please run openDialog() with the script editor. By this, a dialog is opened and HTML can be seen. When you select it and click the button, makeRequest is run with facebookAccountData. By this, the value is appended to the Spreadsheet.
Reference:
Dialogs and Sidebars in Google Workspace Documents

Creating a dialogue box to capture booking details in Google sheets App Script

I am using Google sheets with app script to build a reservations chart for a hotel
Can someone please tell me if there is a way to add a Dialogue box to a google sheet that can ask multiple questions? I have found the Prompt Dialogue box but that seems to allow only one text box for data entry. I have something like this
var result = ui.prompt(
"Let's get to know each other!",
"Please enter your name:",
ui.ButtonSet.OK_CANCEL
);
// Process the user's response.
var button = result.getSelectedButton();
var text = result.getResponseText();
if (button == ui.Button.OK) {
// User clicked "OK".
ui.alert("Your name is " + text + ".");
} else if (button == ui.Button.CANCEL) {
// User clicked "Cancel".
ui.alert("I didn't get your name.");
} else if (button == ui.Button.CLOSE) {
// User clicked X in the title bar.
ui.alert("You closed the dialog.");
}
If there isnt something pre-built, can you please recommend how else I can capture data which would then feed a second sheet within the same spreadsheet .
many thanks
You need to use the HTML service
The method you are using is quite limited. To go further than that you would need to create your own HTML file and serve it from Apps Script. The flow of that is:
Create an HTML file in the script editor
Create your HTML form
Write a script on the HTML that calls a function on your gs script.
Sample code
Code.gs
// Creates form on UI
function form() {
var htmlOutput = HtmlService
.createHtmlOutputFromFile('form')
.setWidth(250)
.setHeight(300);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Add your info');
}
// Uses info passed from rendered HTML to add data to sheet.
function addForm(data){
console.log(data)
SpreadsheetApp.getActiveSpreadsheet().getRange("A1:C1").setValues([data])
}
form.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
// function to run when server-side script is successful
function onSuccess(){
google.script.host.close()
}
// function to run when form is submitted
function sendForm(){
console.log("RUNNING")
let name = document.getElementById("name").value
let country = document.getElementById("country").value
let DOB = document.getElementById("DOB").value
let data = [name, country, DOB]
// call server side function
google.script.run.withSuccessHandler(onSuccess).addForm(data)
}
</script>
</head>
<body>
<form id="form" onsubmit="sendForm()">
<label for="name">First name:</label><br>
<input type="text" name="name" id="name">
<label for="country">Country:</label><br>
<input type="text" name="country" id="country">
<label for="DOB">DOB:</label><br>
<input type="text" name="DOB" id="DOB">
<input type="submit">
</form>
</body>
</html>
Explanation
When the function form() is run from the script editor, it displays your HTML in the Spreadsheet UI.
This shows a form with three text inputs and a submit button.
The submit button has a onsubmit="sendForm()" which is a function defined within the HTML.
It gets all the info from the form, and then calls google.script.run.withSuccessHandler(onSuccess).addForm(data). This is an asynchronous function that sends a request to the gs file to run the addForm function and then when successful, to run the onSuccess function in the HTML.
The onSuccess simply closes the form.
addForm adds the info to a range in the spreadsheet.
Reference
HTML service
Show Modal Dialog
google.script.run

embed html inside google sheet

I want to display HTML at the top of my spreadsheet by creating an html element and putting it at the top of my spreadsheet sheet.
For example, if I created one large cell at the top of my sheet by merging A1:G5, would it be possible to embed html within it:
<div>
<h1>"Hello World"?</h1>
</div>
I notice from within script editor you can go file > new > html file.
But I don't really get it's purpose.
I just tried this:
From script editor new script:
function addSomeHTML() {
var html = HtmlService.createHtmlOutputFromFile('cabbages')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
Cabbages is an html file:
<div>
<h1>Hello, world!</h1>
</div>
I then saved and navigated to my sheet. I selected a cell and typed =addSomeHTML()
The "loading" message appeared then an empty cell was shown. I was hoping to see "Hello World!" within the cell.
I've looked at the following documentation:
https://developers.google.com/apps-script/guides/html/templates#printing_scriptlets
https://developers.google.com/apps-script/guides/dialogs
You can use either a Modal or Modeless dialog box.
The Modal dialog uses the showModalDialog() method of the Ui Class.
Guide to Dialogs
Google Documentation - Modal Dialog
Add a custom menu to the spreadsheet
// This will run when the spreadsheet is opened or the browser page is refreshed
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Custom Menu')
.addItem('Open Dialog Box', 'openDialog')
.addToUi();
}
Create the function that runs from the menu
function openDialog() {
var html = HtmlService.createHtmlOutputFromFile('index');
SpreadsheetApp.getUi()
.showModalDialog(html, 'Correct Postcode Errors');
}
index.html
<div>
<h1>"Hello World"?</h1>
</div>

HtmlService form submit opens new tab with foo bar URL

I am attempting to build a UI for a spreadsheet using GAS HtmlService. The HTML below is a very simple form with a single text box that pulls a value ("Kristina") from the sheet, successfully. However, when I try to submit the form a new tab is opened in Chrome that attempts to open the URL "bffc95ee-ff64-4d2c-xxxx-19d9824eb4b4.foo.bar/?fname=Kristina" with "xxxx" replacing more random letters and numbers (just in case). At no point do I use the words "foo.bar" in my code, so I'm pretty sure that that part isn't coming from me. It does not change each time or after logging out and back in. I'm getting the same result on two different computers.
<html>
<body>
<div>
<form id="formtest1">
<label>First Name</label>
<input name="fname" type="text" maxlength="255" value="<?= fname ?>"/>
<input type="submit" value="Submit"
onclick="google.script.run.processForm(document.getElementById('formtest1'));
google.script.host.close()"/>
</form>
</div>
</body>
</html>
The above is being displayed using the following function:
function htmltest(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sht = ss.getActiveSheet();
var html = HtmlService.createTemplateFromFile("HTML");
html.fname = sht.getRange(2, 3).getValue();
ss.show(html.evaluate());
};
If I understand correctly, the "google.script.run.processForm(...)" script in the HTML should trigger the following function, as set up in the projects triggers:
function onFormSubmit(){
Browser.msgBox("Test");
};
But it doesn't appear to do so as the form doesn't close and the msgBox doesn't appear. Only the foo bar URL in a new tab.
Hopefully I've explained the issue clearly and am not making an embarrassing mistake.
You cannot use a real "submit" button with google.script.run (this is a documented restriction in the user guide). Change it to "button" and it should work fine.
The project trigger onFormSubmit() will be triggered by a submission via the Forms Service. There is no relationship between this trigger and your HTML code; they are two different ways to interact with users.
An html forms pattern is shown in the HTML Service documentation here, and the script below is an adaptation of it.
Code.gs
The only real change from your original is that onFormSubmit() has been replaced with processForm(form), which includes a parameter, for the object that will be passed from the html code.
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "htmltest",
functionName : "htmltest"
}];
sheet.addMenu("Custom Menu", entries);
};
function htmltest(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sht = ss.getActiveSheet();
var html = HtmlService.createTemplateFromFile("HTML");
html.fname = sht.getRange(2, 3).getValue();
//Logger.log( html.getCodeWithComments() );
ss.show(html.evaluate());
};
function processForm(form){
var fname = form.fname;
Browser.msgBox("Test - " + fname);
};
HTML.html
This is a modification of your original, echoing the pattern from the documentation. The form submission SuccessHandler is a one-liner, which closes the dialog. Once it completes, the server-side function is invoked with the form content, retrieved using this.parentNode (to refer to the form).
There are other ways - see Get value of html text box in Apps Script function for a different approach.
<html>
<script type="text/javascript">
// SuccessHandler to close form
function close() {google.script.host.close();}
</script>
<body>
<div>
<form>
<label>First Name</label>
<input name="fname" type="text" maxlength="255" value="<?= fname ?>"/>
<input type="button" value="Submit" onclick="google.script.run
.withSuccessHandler(close)
.processForm(this.parentNode)"/>
</form>
</div>
</body>
</html>
Just add this to your script tag on your html file.
// 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);
Source: HTML Service: Communicate with Server Functions