I have a working Google apps script set up to send an automated email (GmailApp.sendEmail) when I click a button in the workbook.
However, I am trying to convert the script to Google Web App so that any users within my organization will have authorization to run the script and trigger the automated email by pressing the button.
I'm a little lost on how to adapt the local code from getui(). I know that I need to add a function such as doget(e) and deploy as a web app, but I'm not well-versed enough in Web App to edit the code.
Here is my working local code:
// This constant is written in column E for rows for which an email
// has been sent successfully.
var EMAIL_SENT = 'E-MAIL SENT';
var ui = SpreadsheetApp.getUi();
/**
* Sends non-duplicate emails with data from the current spreadsheet.
*/
function email(){
var rng = SpreadsheetApp.getActiveSheet().getRange('A2:F2')
var checkvalue = SpreadsheetApp.getActiveSheet().getRange('E2').getValue();
var email = rng.getValues()[0];
var data = rng.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailSent = checkvalue; // emailSent confirmation cell
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
GmailApp.sendEmail(email[0], email[1], email[2]);
SpreadsheetApp.getActiveSheet().getRange('E2').setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
Any help is much appreciated!
Try this:
Code.gs:
function email(){
var ss=SpreadsheetApp.openById("SpreadsheetId");//need spreadsheet id
var sh=ss.getSheetByName("SheetName");//when you open up a spreadsheet like this the active sheet is alway ss.getSheets()[0] the left most sheet so you should user get sheet by name instead.
var rg=sh.getRange('A2:F2');
var email=rg.getValues()[0];
if (email[4]!="EMAIL_SENT") {
GmailApp.sendEmail(email[0], email[1], email[2]);
sh.getRange('E2').setValue("EMAIL_SENT");
}
}
function doGet() {
return HtmlService.createHtmlOutputFromFile('html filename without extension');
}
html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input type="button" value="Send" onClick="google.script.run.email();" />
</body>
</html>
Client to Server Communication
Related
I am a student who learn to code Apps Script. I want to create a form with 2 number input, let say A & B, with a button. When user submit the form, the script will search column A & B in active Google Spreadsheet sheet that match with 2 input and query a result in the column C on the same row. Finally, the result C will appear below the form.
The problem is that, when the form appear, I input 2 values but the result don't work.
I wrote two file code like this in Apps Script:
The HTML file
<form onsubmit="handleFormSubmit(event)">
<label for="inputA">height:</label><br>
<input type="number" id="inputA" name="inputA"><br>
<label for="inputB">weight:</label><br>
<input type="number" id="inputB" name="inputB"><br>
<input type="submit" value="Submit">
</form><br>
<script>
function handleFormSubmit(event) {
// Prevent the form from refreshing the page
event.preventDefault();
// Get the input values from the form
var inputA = document.getElementById("inputA").value;
var inputB = document.getElementById("inputB").value;
// Search the Google Sheet for a matching row
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var result = "";
for (var i = 0; i < data.length; i++) {
if (data[i][0] == inputA && data[i][1] == inputB) {
result = data[i][2];
break;
}
}
// Display the result on the page
var resultContainer = document.getElementById("result");
resultContainer.innerHTML = result;
}
</script>
<label for="resultA">result is:</label><div id="result"></div>
<!-- Result will be added here -->
The script file:
function doGet(e) {
return HtmlService.createTemplateFromFile('searchForm.html')
.evaluate();
}
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
The sheet link is here: https://docs.google.com/spreadsheets/d/1DqjBbU4b0uDTtjYsBFVFDuvA9fthR1N3KUCtkvwRowc/edit?usp=sharing
I think the problem is the code in script tag but I don't quite sure.
Modification points:
In your script, it seems that you are using Google Apps Script in the Javascript on the HTML side. Google Apps Script can be used on the server side. So, in this case, google.script.run is used.
When these points are reflected in your script, how about the following modification?
Modified script: searchForm.html
HTML & Javascript:
<form>
<label for="inputA">Input A:</label><br>
<input type="number" id="inputA" name="inputA"><br>
<label for="inputB">Input B:</label><br>
<input type="number" id="inputB" name="inputB"><br>
<input type="submit" value="Submit" onclick="handleFormSubmit(event)">
</form>
<div id="result">
</div>
<script>
function handleFormSubmit(event) {
event.preventDefault();
var inputA = document.getElementById("inputA").value;
var inputB = document.getElementById("inputB").value;
google.script.run.withSuccessHandler(result => {
var resultContainer = document.getElementById("result");
resultContainer.innerHTML = result;
}).sample(inputA, inputB);
}
</script>
Google Apps Script: code.gs
function doGet(e) {
return HtmlService.createTemplateFromFile('searchForm.html').evaluate();
}
function sample(inputA, inputB) {
var sheet = SpreadsheetApp.getActiveSheet(); // or var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var data = sheet.getDataRange().getValues();
var result = "";
for (var i = 0; i < data.length; i++) {
if (data[i][0] == inputA && data[i][1] == inputB) {
result = data[i][2];
break;
}
}
return result;
}
Note:
As an important point of this modification, google.script.run is run with the asynchronous process. Please be careful about this.
When you modified the Google Apps Script of Web Apps, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in my report "Redeploying Web Apps without Changing URL of Web Apps for new IDE (Author: me)".
Thit is a sample modification. So, please modify this for your actual situation. If you want to know about google.script.run more, you can also see various sample scripts at Stackoverflow.
Reference:
Class google.script.run (Client-side API)
I have a google form to where I want to populate with some data from a spreadsheet when user is opening the form.
I have a ListItem which I populate it with spreadsheet data. Until now I used the Open(e) function and a trigger, but I just found out that this method is triggered only on form edit not on form open.
Do you have an idea how can I do that?
To have an idea on what I want, I have two files, Code.gs contains the main functions like onOpen and onFormSubmit, and ItemClass where I get my data and create the UI.
I set a console log to Open(e) function, but never triggers.
Code.gs
function onOpen(e) {
console.log({message: 'onOpen', initialData: e});
let items = getItems();
let form = FormApp.openById(PARAMS.formID);
form.setTitle('New Form')
createUI(form, items);
}
ItemsClass.gs
function getItems() {
var email = Session.getActiveUser().getEmail();
var allItems = SpreadsheetApp.openByUrl(PARAMS.sheetURL).getSheetByName("Items Stream").getDataRange().getDisplayValues();
var headers = allItems.shift();
var items = new Array;
for (var i = 0; i < allItems.length; i++) {
var first = allItems[i][1]
var second = allItems[i][2]
items.push(first + "&" + second)
}
return items;
}
The on Open trigger Google Forms works only when opening the form editor, not the actual form that the user fills out
To return to the user the updated data whenever he opens the form and allow him to modify the data, you should create a custom HTML form with Web polling.
Web Polling with setInterval allows to pull fresh data from the spreadsheet and update it in specified intervals
Apps Script WebApps allow you to combine Apps Script and HTML/Javascript which allows you easy interaction between serverside and UI - useful for creation of a custom HTML form
Use google.script.run to communicate between the two sides.
Simple sample pulling updated data from column A in a spreadsheet and allowing the user to modify the values:
code.gs:
var sheet = SpreadsheetApp.openById('XXX').getSheetByName("YYY");
function doGet(){
var html=HtmlService.createTemplateFromFile('index');
return html.evaluate();
}
function getValues() {
//get data from the first column
var data = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
var table = "";
for (var i = 0; i < data.length; i++) {
table +='<tr><td>' + data[i][0] + ' </td><tr>';
}
return table;
}
function writeToSheet(newValues) {
newValues = newValues.split(",");
var range = sheet.getRange(1, 1, newValues.length, 1);
newValues = newValues.map(function(row){return [row]});
range.setValues(newValues);
}
index.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
function onSuccess(values){
document.getElementById("data").innerHTML=values;
}
function polling(){
//modify the interval of 2000 ms to any desired value
setInterval( function(){google.script.run.withSuccessHandler(onSuccess).getValues()},2000);
}
function updateValues(){
var newValues= document.getElementById("newValues").value;
google.script.run.writeToSheet(newValues);
}
</script>
<body onload="polling()">
<div> Values: </div>
<table id="data">
</table>
<div> If you want to modify the values in the spreadsheet, type in new values comma separated: </div>
<input type="text" id="newValues" ><br><br>
<input type="button" value="Confirm" onclick="updateValues()">
</body>
</html>
Deploy this WebApp and described in the documentation and paste the WebApp URL into a browser address bar.
I hope this is well explained. First of all, sorry because my coding background is zero and I am just trying to "fix" a previously written script.
Problem The script does not populate sheet after parsing retrieved data if the function is triggered by timer and the sheet is not open in my browser .
The script works OK if run it manually while sheet is open.
Problem details:
When I open the sheet the cells are stuck showing "Loading" and after a short time, data is written.
Expected behavior is to get the data written no matter if I don't open the sheet.
Additional info: This is how I manually run the function
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [
{name: "Manual Push Report", functionName: "runTool"}
];
sheet.addMenu("PageSpeed Menu", entries);
}
Additional info: I set the triggers with Google Apps Script GUI See the trigger
Before posting the script code, you can see how the cells look in the sheet:
Script code
function runTool() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Results");
var rows = activeSheet.getLastRow();
for(var i=3; i <= rows; i++){
var workingCell = activeSheet.getRange(i, 2).getValue();
var stuff = "=runCheck"
if(workingCell != ""){
activeSheet.getRange(i, 3).setFormulaR1C1(stuff + "(R[0]C[-1])");
}
}
}
// URL check //
function runCheck(Url) {
var key = "XXXX Google PageSpeed API Key";
var strategy = "desktop"
var serviceUrl = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=" + Url + "&key=" + key + "&strategy=" + strategy +"";
var array = [];
var response = UrlFetchApp.fetch(serviceUrl);
if (response.getResponseCode() == 200) {
var content = JSON.parse(response.getContentText());
if ((content != null) && (content["lighthouseResult"] != null)) {
if (content["captchaResult"]) {
var score = content["lighthouseResult"]["categories"]["performance"]["score"];
} else {
var score = "An error occured";
}
}
array.push([score,"complete"]);
Utilities.sleep(1000);
return array;
}
}
You can try the code using the sheet below with a valid Pagespeed API key.
You only need to add a Trigger and wait for it's execution while the sheet is not open in your browser
https://docs.google.com/spreadsheets/d/1ED2u3bKpS0vaJdlCwsLOrZTp5U0_T8nZkmFHVluNvKY/copy
I suggest you to change your algorithm. Instead of using a custom function to call UrlFetchApp, do that call in the function called by a time-driven trigger.
You could keep your runCheck as is, just replace
activeSheet.getRange(i, 3).setFormulaR1C1(stuff + "(R[0]C[-1])");
by
activeSheet.getRange(i, 3, 1, 2).setValues(runCheck(url));
NOTE
Custom functions are calculated when the spreadsheet is opened and when its arguments changes while the spreadsheet is open.
Related
Cache custom function result between spreadsheet opens
I'm trying to use google script to display a bunch of data in a HTML file, however, my data doesn't seem to make it to the HTML file and I have no idea why. Can someone please tell me what I'm missing here?
Path: htmlList.html
<!DOCTYPE html>
<html>
<head>
<base target="_top" />
</head>
<body>
My HTML page
<? for(var i = 0; i <= (users.length -1); i++) { ?>
<p><?= users[i].firstName ?></p>
<? } ?>
</body>
</html>
Path: Code.js
function doGet(users) {
var html = HtmlService.createTemplateFromFile("htmlList");
html.users = users;
return html.evaluate().setTitle("Test my app");
}
function generateLinks() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rr = spreadSheet.getLastRow();
var users = [];
for (var i = 3; i <= rr; i++) {
var firstName = spreadSheet.getRange(i, 1).getValue();
var user = {
firstName: firstName
};
users.push(user);
}
doGet(users);
}
You want to open new tab for own browser using the created HTML data, when you run the function at the script editor.
You are using the container-bound script.
If my understanding is correct, how about this modification?
Modification points:
In this modification, I used the following flow. Please think of this as just one of several answers.
By running runScript(), a dialog is opened.
The opened dialog runs a Javascript for opening new tab of the browser and open the URL of Web Apps.
At this time, generateLinks() is run from doGet(), and the values are retrieved and put to HTML data.
Close the dialog.
By this flow, when you run the function at the script editor, the created HTML is opened as new tab of your browser.
Modified script:
Please copy and paste the following script to the container-bound script of Spreadsheet. And then, please redeploy Web Apps as new version. At that time, as a test case, please set Execute the app as: and Who has access to the app: as Me and Anyone, even anonymous, respectively. In this case, you are not required to modify the script of HTML side.
function doGet() {
var html = HtmlService.createTemplateFromFile("htmlList");
html.users = generateLinks(); // Modified
return html.evaluate().setTitle("Test my app");
}
function generateLinks() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rr = spreadSheet.getLastRow();
var users = [];
for (var i = 3; i <= rr; i++) {
var firstName = spreadSheet.getRange(i, 1).getValue();
var user = {
firstName: firstName
};
users.push(user);
}
return users; // Modified
}
// I added the following function. Please run this function.
function runScript() {
var url = ScriptApp.getService().getUrl();
var script = "<script>window.open('" + url + "', '_blank').focus();google.script.host.close();</script>";
var html = HtmlService.createHtmlOutput(script);
SpreadsheetApp.getUi().showModalDialog(html, 'sample');
}
When var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); is used, the 1st sheet of Spreadsheet is used. So when you want to retrieve the values from the specific sheet, for example, please modify to SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheetName").
Note:
If you modified the script of Web Apps, please redeploy Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.
References:
HTML Service: Create and Serve HTML
HTML Service: Templated HTML
Taking advantage of Web Apps with Google Apps Script
Assuming that your data on the spreadsheet looks something like this -
And the desired output looks something like this (you're free to modify the CSS in your .html file) -
You can achieve this by using the following code -
For Code.gs:
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function getUsers() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var users = ss.getRange(1, 1, ss.getLastRow(), 1).getValues();
return users;
}
For Index.html file:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess1(users) {
var div = document.getElementById('userFirstNames');
div.innerHTML = users;
}
google.script.run.withSuccessHandler(onSuccess1).getUsers();
</script>
</head>
<body>
<div id='userFirstNames'></div>
</body>
</html>
Hope this helps.
I would like, through Google Appscript, to (a) programmatically publish to the web selected sheets within a Google Sheets document, (b) obtain programmatically the URL where each sheet is published, and (c) have the published version of each sheet automatically update whenever the corresponding sheet is updated (this should happen automatically, right?). Right now, I can accomplish this only through File/Publish to Web...
The following question and answer is highly related to this question:
Google Sheets API: How to "publish to web" for embeddable sheet?
However, it appears to apply only to publishing an entire Google Sheets document, not a single sheet within a Google Sheets document. Any solution ideas would be most appreciated.
I have gained some insight into this question. It is possible to obtain a URL to a published HTML version of a single sheet in a Google Sheets document simply by modifying the URL used to access that sheet.
For example, here is the URL of a sheet I'm working on in Google Sheets, copied directly from my browser's URL bar:
https://docs.google.com/spreadsheets/d/1fTx3dUsvdbVKgP2nXs1LcyG_7oBp-MoFZTXn7MtdEZg/edit#gid=1711661074
I can then modify the URL as follows to get a published HTML version of that single sheet:
https://docs.google.com/spreadsheets/u/0/d/1fTx3dUsvdbVKgP2nXs1LcyG_7oBp-MoFZTXn7MtdEZg/htmlembed/sheet?gid=1711661074
Summary of URL modifications I made:
Replace "/d" after "spreadsheets" with "/u/0/d"
Replace "edit#" with "htmlembed/sheet?"
Other inferences one can make:
The long string after "/u/0/d" is the ID of the Google Sheets document.
The shorter string after "sheet?" is the ID of the single sheet within that document.
These new insights transform my question into a new one: namely, how can I programmatically obtain (through Google Appscript) the ID of the Google Sheets document I'm working on, together with the ID of the spreadsheet I'm working on?
Here's the answer:
To get the ID of the current Google Sheets document within Appscript:
var ss = SpreadsheetApp.getActiveSpreadsheet().getId();
To get the ID of the current sheet within the current Google Sheets document:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetId();
I can now build a URL for a published html version of any single sheet within a Google Sheets document through string concatenation as follows:
var publishedURL = "https://docs.google.com/spreadsheets/u/0/d/" + ss + "/htmlembed/sheet?gid=" + sheet;
There's still one lingering issue, though: It appears that users of this published URL must manually refresh the browser in order to sync the HTML with the spreadsheet. At the present time, I do not have a solution to this problem, other than to request that users of the URL install an auto URL refresher or manually refresh the page periodically. I'd welcome any ideas on this.
It looks like you can publish individual sheets according to these dialogs:
It does update the published sheets although I've noticed quite a bit of delay in the process occasionally.
Since the Publish to the Web simply shows a readonly version of an html table that contains sheet values then you could do that with one webapp. Here's an example below that displays all sheets in tabular form.
A Webapp to display all sheets:
function publishAllSheets()
{
var ss=SpreadsheetApp.getActive();
var allShts=ss.getSheets();
var s='All my Sheets';
for(var i=0;i<allShts.length;i++)
{
var sh=allShts[i];
var rg=sh.getDataRange();
var vA=rg.getValues();
s+=Utilities.formatString('Sheet: %s <br /><table border="1">',allShts[i].getName());
for(var j=1;j<vA.length;j++)
{
s+='<tr>';
for(var k=0;k<vA[j].length;k++)
{
s+=Utilities.formatString('<td>%s</td>', vA[j][k]);
}
s+='</tr>';
}
s+='</table>';
}
return s;
}
function showAllMySheets()
{
var ui=HtmlService.createHtmlOutputFromFile('allsheets').setWidth(1000);
SpreadsheetApp.getUi().showModelessDialog(ui, 'All My Sheets')
}
function doGet()
{
var ui=HtmlService.createHtmlOutputFromFile('allsheets');
return ui.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
allsheets.html
<!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(updateDiv)
.publishAllSheets();
});
function updateDiv(hl)
{
document.getElementById('c1').innerHTML=hl;
}
</script>
</head>
<body>
<div id="c1"></div>
</body>
</html>
Here's the code for getting any one of your sheets:
function getSheetNames()
{
var ss=SpreadsheetApp.getActive();
var allShts=ss.getSheets();
var shts=[];
for(var i=0;i<allShts.length;i++)
{
shts.push(allShts[i].getName());
}
return shts;
}
function getOneSheet(name)
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(name);
var rg=sh.getDataRange();
var vA=rg.getValues();
var s='';
s+=Utilities.formatString('Sheet: %s <br /><table border="1">',sh.getName());
for(var j=1;j<vA.length;j++)
{
s+='<tr>';
for(var k=0;k<vA[j].length;k++)
{
s+=Utilities.formatString('<td>%s</td>', vA[j][k]);
}
s+='</tr>';
}
s+='</table>';
return s;
}
onesheet.html
<!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(updateSelect)
.getSheetNames();
});
function updateDiv(hl)
{
document.getElementById('c1').innerHTML=hl;
}
function updateSelect(vA)
{
var select = document.getElementById("sel1");
select.options.length = 0;
for(var i=0;i<vA.length;i++)
{
select.options[i] = new Option(vA[i],vA[i]);
}
}
function getSelectedSheet()
{
var name=$('#sel1').val();
google.script.run
.withSuccessHandler(updateDiv)
.getOneSheet(name);
}
console.log('MyCode');
</script>
</head>
<body>
<select id="sel1">
<option value="" selected></option>
</select>
<input type="button" value="Select" onClick="getSelectedSheet();" />
<div id="c1"></div>
</body>
</html>