Update scriptlets in HTML without reloading page - google-apps-script

I am referring to this article about templated HTML and scriptlets with Google Apps Script:
https://developers.google.com/apps-script/guides/html/templates
It is written how to call an Apps Script function and load the data when loading the html page:
Code.gs:
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
function getData() {
return SpreadsheetApp
.openById('1234567890abcdefghijklmnopqrstuvwxyz')
.getActiveSheet()
.getDataRange()
.getValues();
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<? var data = getData(); ?>
<table>
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
</html>
Question:
Is it possible to update the scriptlets without refreshing the whole page?
For example to implement a button and call the getData() function to load new data from the spreadsheet to the html?
Update:
I have adjusted the code with a simple button and a code to call the getData() function again and update the table. But of course doing it this way the for loop gets lost. Better would be to reload the whole code.
Is there a way to re-evaluate the whole page?
Any ideas?
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<button type="button" onclick="Refresh()">Refresh</button>
<? var data = getData(); ?>
<table id="datatable">
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
<script>
function Refresh(){
google.script.run.withSuccessHandler(update).withUserObject(this).getData();
}
function update(returnValue){
document.getElementById("datatable").innerHTML= returnValue
}
</script>
</html>

If you will call getData() from the client-side, then you will need to build the table on the client-side. The easier way might be to create a string and then add it by using HtmlElement.outerHTML to replace the whole element or HtmlElement.innerHTML to replace the inner elements and content.
Client-side JavaScript code (put it between <script> tags, do not require to change your server-side code)
function refreshTable(){
google.script.run
.withSuccessHandler(updateTable)
.withFailureHandler((error) => console.error(error.message))
.getData()
}
function updateTable(data){
let html = '';
for (var i = 0; i < data.length; i++) {
html += `<tr>`;
for (var j = 0; j < data[i].length; j++) {
html += `<td>${data[i][j]}</td>`;
}
html += `</tr>`;
}
document.getElementById("datatable").innerHTML = html;
}
By the other hand, if you want to use Google Apps Script templated HTML to build the HTML table, then you need to call a function that evaluates the template to generate a HttpOutput object and then use HttpOutput.getContent() to return a string to the client side code, then you might pass this string to the corresponding element by using HtmlElement.innerHTML.
Server Side, in a .gs file
function includeFromTemplate(filename){
return HtmlService.createTemplateFromFile(filename).evaluate().getContent();
}
Server side, table.html file
<? var data = getData(); ?>
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
Client Side. Call this function from onclick attribute or use the HtmlElement.addEventlistener method.
function refreshTable(){
google.script.run
.withSuccessHandler(html => {
document.querySelector('table').innerHTML = html;
})
.withFailureHandler((error) => console.error(error.message))
.includeFromTemplate('table')
}

Related

Google Sheets table is not showing new lines "\n" correctly in App Script WebApp

I have a simple WebApp for getting a table data in a Spreadsheet.
What's the problem is, I have not getting Line Breaks in WebApp
Actual Data in Spreadsheet
Akash
Bangalore
but getting like this in WebApp
Akash Bangalore
Pls help me to do this ..
Spreadsheet URL
WebApp URL
Below is the Code.gs file
function doGet() {
template = HtmlService.createTemplateFromFile('Index.html');
return template.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
var spreadsheetId = '116XVmxdI5uQ4A2QsjLrI25pUQBCDL22KCkGXHZZfKVQ';
var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Sheet1") ;
var dataValues = sheet.getDataRange().getDisplayValues() ;
Below is the Index.html file
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>Display Google Sheet Web App</h1>
<table border="1" style="border-collapse: collapse; margin:auto" cellpadding="5px" >
<?var tableData = dataValues ?>
<?for(var i = 0; i < tableData.length; i++) { ?>
<?if(i == 0) { ?>
<tr>
<?for(var j = 0; j < tableData[i].length; j++) { ?>
<th><?= tableData[i][j] ?></th>
<? } ?>
</tr>
<? } else { ?>
<tr>
<?for(var j = 0; j < tableData[i].length; j++) { ?>
<td><?= tableData[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
<? } ?>
</table>
</body>
</html>
New lines \n in html are ignored. To get breaks in html, you should use <br/>. Use Array.map to loop:
var dataValues = sheet.getDataRange()
.getDisplayValues()
.map(row => row.map(value =>
value.replace(/\n/g, `<br/>`)
)
);
When passing a value to be displayed, i.e. as <?= tableData[i][j] ?> instead of printing scriptlets <?= ?> use force-printing scriptlets <?!= ?>
The above because when using the first, Google use contextual escaping, while with the other Google will show the data "as is".

Why is google.script.run is slow?

So I have a sheet where I added a Custom Menu (that I have searched and found in google).
The Custom Menu shows a dialog (see image below) It returns all the data validation I have in a specific cell, each of which has a checkbox so I can select multiple options and return it in a single cell.
But the FILL CURRENT CELL button runs slow!
I have here these set of codes:
code.gs
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Custom Menu')
.addItem('Show Dialog', 'showDialog')
.addToUi();
}
function showDialog() {
var html = HtmlService.createTemplateFromFile('page').evaluate();
SpreadsheetApp.getUi()
.showSidebar(html);
}
var valid = function(){
try{
return SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1Bw-CW5JZX-MEjPKhrWSNA2zWkqJTR6W3yM2yDrqjE_0/edit#gid=0').getRange('B1').getDataValidation().getCriteriaValues()[0].getValues();
}catch(e){
return null
}
}
function fillCell(e){
var s = [];
for(var i in e){
if(i.substr(0, 2) == 'ch') s.push(e[i]);
}
if(s.length) SpreadsheetApp.getActiveRange().setValue(s.join(', '));
}
page.html
<div style="font-family: arial;">
<? var data = valid(); ?>
<form id="form" name="form">
<? if(Object.prototype.toString.call(data) === '[object Array]') { ?>
<? for (var i = 0; i < data.length; i++) { ?>
<? for (var j = 0; j < data[i].length; j++) { ?>
<input type="checkbox" id="ch<?= '' + i + j ?>" name="ch<?= '' + i + j ?>" value="<?= data[i][j] ?>"><?= data[i][j] ?><br>
<? } ?>
<? } ?>
<? } else { ?>
<p>Maybe current cell doesn't have Data validation...</p>
<? } ?>
<br>
<br>
<input type="button" value="Fill Current Cell" onclick="google.script.run.fillCell(this.parentNode)" />
<input type="button" value="Clear Selections" onclick="reset()" />
</form>
</div>
Can someone please help me so the FILL CURRENT CELL button runs faster?
Just to leave an answer:
Take into account that google.script.run need to interact with server every time is called, therefore you will have some performance issues compared with running the same function directly from the editor.
So if you are experiencing little time difference like you have stated in the comments don't worry as it just normal behavior.

Google Apps Script Return HTML Syntax Error

Hey guys I'm very new to coding and I have encountered a Syntax error that I can't seem to figure out. I've looked online for the error and what's worse, I've managed to get the script to work earlier today before receiving the error. I am trying to take rows from a Google Sheet and pin them onto a map.
The error I receive is on line 9 where I try to return the array. Any input or suggestions would be much appreciated it! Thank you guys for trying to help. Specifically:
return html.evaluate().setTitle('Company Directory Map');
Here is the code that I have:
function doGet(e) {
var html = HtmlService.createTemplateFromFile('DirectoryHTML');
var ss = SpreadsheetApp.getActiveSpreadsheet();
html.ss = parseDirectory(ss.getSheetByName('Directory Address & Name').getRange('A2:R').getValues());
return html.evaluate().setTitle('Company Directory Map');
}
function parseDirectory(values) {
var locations = [];
for (var i = 1; i < values.length; i++) {
locations.push({
LocationID: values[i][1],
LocationName: values[i][4],
Address: values[i][6],
AddressDetail: values[i][7],
City: values[i][8],
State: values[i][9],
Zipcode: values[i][10],
ZipcodeExtension: values[i][11],
mapURL: getMapUrl(values[i][6,8,9,10,11]),
Phone: values[i][12],
Fax: values[i][13],
EnteralPhone: values[i][14],
EnteralFax: values[i][15],
Type: values[i][16],
Notes: values[i][17]
});
}
return locations;
}
function getMapUrl(city) {
return Maps.newStaticMap().setSize(1200, 600).setCenter(41.37132419162449,-112.13662837438801).getMapUrl();
}
Edit
Below is my HTML code for reference:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<? = for(var i = 0, i < locations.length; i++) {?>
<div>
<img src = "<? locations[i].mapUrl ?>" />
<h2><?= locations[i].LocationID ?></h2>
<!-- <h4><?= locations[i].Address ?></h4> -->
</div>
<? } ?>
</body>
</html>
The reason for syntax error is you have syntax error in your HTML code, specifically this line
<? = for(var i = 0, i < locations.length; i++) {?>
1) You are using printing scriptlet, but there is no variable/output to print. Use standard scriptlet instead <? ?>
2) The proper syntax for the loop is for(var i = 0; i < locations.length; i++). Note the use of ; instead on , in the loop.
Finally, you set the value of ss object in you htmltemplate
html.ss = parseDirectory(ss.getSheetByName('Directory Address & Name').getRange('A2:R').getValues());
However, in your HTML code, you use the object locations to run through the loop. Hence modify your HTML code like so
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<? var locations = ss ?>
<? for(var i = 0; i < locations.length; i++) {?>
<div>
<img src = "<?= locations[i].mapUrl ?>" />
<h2><?= locations[i].LocationID ?></h2>
<!-- <h4><?= locations[i].Address ?></h4> -->
</div>
<? } ?>
</body>
</html>

Very simple Google-apps script results in syntax error

I have a collection of Google App. scripts that I run as standalone apps., usually embedded into Google Sites. Yesterday I copied one of my functioning scripts and modified as I have done many times in the past but now the new scriptit does not run, it fails with a syntax error. Having stared at this extremely simple script for hours without a resolution I am thinking it may be a problem with Google, or some issue in my domain (Corp. domain).
function doGet() {
var t = HtmlService.createTemplateFromFile('index');
var ss = SpreadsheetApp.openById('sheet_ID');
var lrN = ss.getSheetByName('NCMR').getLastRow();
t.statArray = ss.getSheetByName('NCMR').getRange('Z3:Z'+lrN+'').getValues();
return t.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
snip from the execution transcript when I try to run:
[17-04-13 06:39:26:322 PDT] Sheet.getRange([Z3:Z23]) [0.072 seconds]
[17-04-13 06:39:26:381 PDT] Range.getValues() [0.058 seconds]
[17-04-13 06:39:26:382 PDT] Function.apply([[]]) [0 seconds]
[17-04-13 06:39:26:389 PDT] Execution failed: SyntaxError: Syntax error. (line 8, file "Code") [0.254 seconds total runtime]
Error from debugger:
We're sorry, a server error occurred. Please wait a bit and try again.
and then it has the following in the left hand pane of the debugger:
<Unknown file>null [0]
Code : doGet [8]
Any help or insight will be much appreciated. Apologies if this is a lay up and/or has already been answered.
EDIT to add html file:
index.html
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js"></script>
<base target="_top">
</head>
<body>
<center>
<table>
<tr><td><b> Id</b></td></tr>
<?= for (var i = 0; i < statArray.length; i++) { ?>
<?= for (var j = 0; j < statArray[i].length; j++) { ?>
<tr><td><?= statArray[i] ?></td>
<?= } ?>
</tr>
<?= } ?>
</table>
</center>
</body>
</html>
For reference: The author of the question figured out the problem/syntax error
The reason for the error was a syntax error in a scriptlet used in the html i.e modify this:
<?= for (var i = 0; i < statArray.length; i++) { ?>
to a standard scriptlet tag
<? for (var i = 0; i < statArray.length; i++) { ?>
Html code should be the following:
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js"></script>
<base target="_top">
</head>
<body>
<center>
<table>
<tr><td><b> Id</b></td></tr>
<? for (var i = 0; i < statArray.length; i++) { ?>
<? for (var j = 0; j < statArray[i].length; j++) { ?>
<tr><td><?= statArray[i] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</center>
</body>
</html>
have you tried to use index.html
function doGet() {
var t = HtmlService.createTemplateFromFile('index.html');
var ss = SpreadsheetApp.openById('sheet_ID');
var lrN = ss.getSheetByName('NCMR').getLastRow();
t.statArray = ss.getSheetByName('NCMR').getRange('Z3:Z'+lrN+'').getValues();
return t.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

script to color background row column

I'm trying to assign a cell color based on a value. I import the range from a spreadsheet. I look at each value. If it is 1 then I want to have the cell color be red, etc. Making the table works fine, but the colors get messed up. Can't see my error.
<? var data = getTheTable(); var lastRow = data.length-1; var rowsToShow=3; var mcolor='white';?>
<table class="center">
<tr>
<th>Name</th>
<th>Comment</th>
</tr>
<? for (var i = lastRow; i >(lastRow-rowsToShow); i--) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) {
if (data[i][j]=='1'){mcolor='red';}
if (data[i][j]=='2'){mcolor='yellow';}
if (data[i][j]=='3'){mcolor='green';}
else {mcolor='blue';}
?>
<td>
<span style="background-color:<?= mcolor?>;">
<?= data[i][j] ?></span> </td>
<? } ?>
</tr>
<? } ?>
</table>
You need to use "else" statements here, (or a switch statement). Without the else's, each of your ifs is being checked, even if the previous one matched. so even if you match data[i][j] == '1', you still end up with mcolor = 'blue' due to the else on your final if.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
<? for (var j = 0; j < data[i].length; j++) {
if (data[i][j]=='1'){mcolor='red';}
else if (data[i][j]=='2'){mcolor='yellow';}
else if (data[i][j]=='3'){mcolor='green';}
else { mcolor='blue'; }
...
Often if you find yourself using a bunch of if/else statements in a row, a Switch/Case is a cleaner option:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
switch(data[i][j]){
case '1':
mcolor='red';
break;
case '2':
mcolor='yellow';
break;
case '3':
mcolor='green';
break;
default:
mcolor='blue';
}