Pass array from server side function using google script and html - html

I have an html page that will be served to a google sheet app to be used as a UI. I would like to access an array from a server side function within the html file. I am having trouble accessing a returned array. Here is what I have:
in html file:
<div id="id1">
Starting 1
</div>
<div id= "id2">
Starting 2
</div>
<script type="text/javascript">
document.getElementById("id1").innerHTML = "A change";
</script>
<script type="text/javascript">
function onSuccess(numUnread) {
alert('You have ' + numUnread[0]
+ ' unread messages in your Gmail inbox.');
document.getElementById("id2").innerHTML = numUnread[0];
}
google.script.run.withSuccessHandler(onSuccess)
.getPermits();
</script>
In code.gs:
function getPermits()
{
var permits = [];
for(var i = 0; i < 10; i++)
{
permits.push('Element ' + i);
}
return permits;
}
Right now I am just trying to figure out why the div with id = "id2"
does not get changed to the first element from the passed array. Instead, it is not changed. Also, there is no alert. If I change the return of the gePermits() function to a string, both the div and the alert work as I would expect.
Thanks in advance!

Some types are not passed trough HTMLService, but you can always STRINGFY and PARSE it, try:
return JSON.stringify(permits);
and in the html:
function onSuccess(numUnread) {
numUnread = JSON.parse(numUnread);

Related

getting spreadsheet data to chart data array

Although I found similar posts elsewhere, I still cannot solve my issue.
I want to load locations on a html sidebar page on google spreadsheet, but the only example I find are hard-coded locations.
Here is an example, on HTML page (I removed API Key): this one works for me.
<body>
<div class="container">
<div id="map_div" style="width: 500px; height: 500px"></div>
</div> <!-- CLOSE CONTAINER -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load("current", {
"packages":["map"],
"mapsApiKey": "xxxx"
});
google.charts.setOnLoadCallback(drawChart);
function drawChart(arrayToData) {
var data = new google.visualization.arrayToDataTable(
[["Lat", "Long","Nom"],
[45.7660889, 4.794056299999999, "Person1"],
[45.8167227, 4.8341048, "Person2"],
[45.7796433, 4.8037871, "Person3"],
[45.7780849, 4.921768399999999, "Person4"]]
);
var map = new google.visualization.Map(document.getElementById('map_div'));
map.draw(data, {
showTooltip: true,
showInfoWindow: true
});
}//function drawChart() {
</script>
</body>
And I would like to have something looking like that, where data locations are not hard-coded but comes from spreadsheet data :
google.charts.setOnLoadCallback(drawChart);
var ss = SpreadsheetApp.openByUrl("xxxx");
var datatable = ss.getRange("listNamesAdresses");
function drawChart(arrayToData) {
var dataToArray=document.getElementById("listNamesAdresses");
var data = new google.visualization.arrayToDataTable(
dataToArray
);
var map = new google.visualization.Map(document.getElementById('map_div'));
map.draw(data, {
showTooltip: true,
showInfoWindow: true
});
}//function drawChart() {
I'm aware this is not correct, but I tried many combinations, still cannot solve it. Your help is welcome !
Here is a sharable example of what I made :
Link to a copy of my Map Test
I adapted it from my spreadsheet but went out of my quota for my API key, so I could'nt test it yet. I hope this will be fine !
Many thanks in advance
EDIT 2 :
I followed ziganotschka's suggestions (thank you very much for your time) : I couldn't apply the HtmlCreateOutputFromFil("index.html") so I stuck to my code for displaying a sidepage Html. For the rest of it : I now have a map (first victory!).
But, it says : "no data points to show".
I checked on values return by getAddresses function, seems OK. For getting easier on it, I changed the function to an easier one : getGeoCodesAndNames. This one returns, as it says, geocode latitude, longitude, and name.
Here are my new code sample and link to the new version of the spreadsheet :
Gs-code :
function getGeoCodesAndNames(){
//get addresses and names list
var namesAddresses=ss.getRange("ListNamesAddresses");
var a_values=namesAddresses.getValues();
Logger.log(a_values);
/* returns
[[Lat, Lon, Name],
[45.7660889, 4.79405629999999, person1],
[45.8167227, 4.8341048, person2],
[45.7796433, 4.8037871, person3],
[45.7780849, 4.921768399999999, person4]]
*/
return a_values;
}//function getGeoCodesAndNames(){
function testMap2(){
var template = HtmlService.createTemplateFromFile("carte2");
var html = template.evaluate();
html.setTitle("Display Map 2").setHeight(550).setWidth(550);
SpreadsheetApp.getUi().showModalDialog(html, "Locations")
}//function testCarte1(){
and HTML code :
<script type="text/javascript">
window.onload = google.script.run.withSuccessHandler(onSuccess).getGeoCodesAndNames();
function onSuccess (arrayToData){
google.charts.load("current", {
"packages":["map"],
"mapsApiKey": "AIzaSyC4WPcWGMZRoqSAfZ0F4RzvWtN6Jy7hmdE"
});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable(arrayToData);
var map = new google.visualization.Map(document.getElementById('map_div'));
map.draw(data, {
showTooltip: true,
showInfoWindow: true
});
}// function drawChart() {
}//function onSuccess (arrayToData){
</script>
And here is the link to the new spreadsheet version :
TestMap2
Do I need to publish it as a web app if I just want to have a side page ? On previous projects I had, I could add datas from a side-page to a spreadsheet without it. In the oppposite ways, can you confirm I need to do it ? I tried, did not change anything on my current result : maybe I made something wrong.
Many thanks again for your help !
EDIT 3 :
I finally got it : My geocode/address function were not returning a proper format for coordinates, because of two things :
1) I'm using French typing, ie dot are replaced with commas in numbers
2) I had to add one more """ symbol at beginning and ending of each string part in the array.
Here is the correct function (might be improved, but..does the job):
function getGeoCodesAndNames(){
//get addresses and names list
var namesAddresses=ss.getRange("ListNamesAddresses");
var a_values=namesAddresses.getValues();
for (var i=0;i<a_values.length;i++){
for (var j=0;j<a_values[0].length;j++){
var value=a_values[i][j];
if (typeof value == "string"){
a_values[i][j]="\"" + value + "\"";
}
}
}
return a_values;
}//function getGeoCodesAndNames(){
Many thanks to the people who helped me !
To combine using the Visualization API in Javascript with retrieving spreadsheet data with Apps Script - you need to use a WebApp
Use google.script.run to pass data from the serverside to clientside
Sample:
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("index.html");
}
function getDatafromSheet(){
var ss = SpreadsheetApp.openByUrl("xxxx");
// getNamedRanges()[0] works if you have only one named range
var datatable = ss.getNamedRanges()[0].getRange().getValues();
return datatable;
}
index.html
<body>
<div class="container">
<div id="map_div" style="width: 500px; height: 500px"></div>
</div> <!-- CLOSE CONTAINER -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
window.onload = google.script.run.withSuccessHandler(onSuccess).getDatafromSheet();
function onSuccess (arrayToData){
google.charts.load("current", {
"packages":["map"],
"mapsApiKey": "AIzaSyDCKzjezYeUDd2ugtFnzokCIpV1YpLmmEc"
});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.arrayToDataTable(arrayToData);
var map = new google.visualization.Map(document.getElementById('map_div'));
map.draw(data, {
showTooltip: true,
showInfoWindow: true
});
}
}
</script>
</body>

Google Calendar Authorization Apps Script - Standalone Web App

I am new to Apps Script and Web Development. I thought it would be nice to make a simple app to get started.
Goal: Display the future events of the user.
Problem: I am stuck on getting user authorization. Currently, the script is displaying my events. Instead, I want the script to display the user's (who is accessing the web app) events.
I found this sample from the documentation. This function gets the list of events of the user. https://developers.google.com/calendar/quickstart/apps-script
Then I wrote a basic index.html file to display the string populated by this above function to the user.
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<script>
function getEventsOnClick() {
google.script.run.withSuccessHandler(changeDisplay).listAllEvents();
}
function changeDisplay(display) {
var div = document.getElementById('output');
div.innerHTML = display;
}
</script>
<div id="output"> Hello! </div>
<button onclick="getEventsOnClick()">Run Function</button>
</body>
</html>
code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function listAllEvents() {
var calendarId = 'primary';
var now = new Date();
var display = ""
var events = Calendar.Events.list(calendarId, {
timeMin: now.toISOString(),
maxResults: 2500,
});
if (events.items && events.items.length > 0) {
for (var i = 0; i < events.items.length; i++) {
var event = events.items[i];
if (event.start.date) {
// All-day event.
var start = new Date(event.start.date);
var end = new Date(event.end.date);
display = display + 'Start: ' + start.toLocaleDateString() + '; End: ' + end.toLocaleDateString() + ". ";
} else {
var start = new Date(event.start.dateTime);
var end = new Date(event.end.dateTime);
display = display + 'Start: ' + start.toLocaleString() + '; End: ' + end.toLocaleString() + ". ";
}
}
} else {
display = 'No events found.';
}
Logger.log("%s", display)
return display
}
Again, nothing is wrong with the above code. It does display events as expected. The problem is that it is displaying my events rather than the user. So, if I give a user URL for the app, then I want this app to request authorization and display their event. How would I do that?
Thanks!
When you deploy the app, make sure you choose to execute as the user rather than as yourself. (as yourself is the default).

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);
}

Getting Form Data from a Sidebar in Google Apps?

I am trying to get data from using a form in a sidebar, but I can't use normal way in javascript where I get it from the document in javascript.
i.e.
var form = document.getElementById("treasurerForm");
So, how should I go about getting data from a form in a sidebar?
You have to communicate between the sidebar (client-side) and the Google Apps Script (server-side). You can do this by using google.script.run in your sidebar javascript.
function openSidebarForm() {
var htmlOutput = HtmlService.createHtmlOutputFromFile("sendForm");
htmlOutput.setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle("Form");
var ui = SpreadsheetApp.getUi();
ui.showSidebar(htmlOutput);
}
function processForm(data) {
// here you can process the data from the form
Browser.msgBox(data);
}
And add a html file to the project named sendForm.html
<script>
function sendForm() {
var data = document.forms[0].elements[0].value;
google.script.run.withSuccessHandler(ready).processForm(data);
document.getElementById("all").innerHTML = "Processing..";
}
function ready() {
document.getElementById("all").innerHTML = "Processed!"
}
</script>
<div id="all">
<form id="form"><input type="text"></form>
<button onclick="javascript:sendForm();">Submit</button>
</div>
A button within a form automatically triggers the submit of the form, and here we want only the javascript to do the work. So we have put it outside the form.
The sendForm() function takes the value of the first element of the form. Then it runs a function in your google script. By putting google.script.run. before the function name you can do this. The withSuccessHandler(functionName) will run a callback function in your sidebar after the function has completed.
You can also type in your HTML as a string and then use HtmlService, which is much less elegant, but it will also work if you have problems adding a html file to your project.
function openSidebarForm() {
var html = '<script>function sendForm() {'
+ 'var data = document.forms[0].elements[0].value;'
+ 'google.script.run.withSuccessHandler(ready).processForm(data);'
+ 'document.getElementById("processing").innerHTML = "Processing.."; }'
+ 'function ready() {'
+ 'document.getElementById("all").innerHTML = "Processed!" }'
+ '</script>'
+ '<div id="all"><div id="processing"></div>'
+ '<form id="form"><input type="text"></form>'
+ '<button onclick="javascript:sendForm();">Submit</button></div>';
htmlOutput = HtmlService.createHtmlOutput(html).setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle("Form");
var ui = SpreadsheetApp.getUi();
ui.showSidebar(htmlOutput);
}
function processForm(data) {
// here you can process the data from the form
Browser.msgBox(data);
}
Good luck !

In Javascript, is it possible to pass a variable into <script> "src" parameter?

Is it possible in Javascript to pass a variable through the src parameter? ie.
<script type="text/javascript" src="http://domain.com/twitter.js?handle=aplusk" />`
I'd like twitter.js to look and see if a "handle" was passed before doing what I need it to do and returning its response back to the originating page calling twitter.js.
I had originally created a function in twitter.js that did the following:
function getHandle() {
var vars = [], hash, username;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
if (hash[0] == 'handle')
username = hash[1];
}
return username;
}
The problem, and it makes sense, is that window.location.href is not going to work on a file that I'm calling from <script src="" />
Thanks!
I can see two solutions here.
First: you can process those GET parameters on the server where the twitter.js is hosted, so that it will dynamically change the js file. For example, you file is:
var handle = {{ handle }};
And your server somehow processes the file, replacing that twitter.js template file dependent on what request was sent.
The second option would be to set the global variables on the page where twitter.js is loaded, like this:
<script type="text/javascript">
window.twitter_js_handle = 'aplusk';
</script>
<script type="text/javascript" src="http://domain.com/twitter.js" />
And in twitter.js:
var handle = window.twitter_js_handle || null;
I use the following pattern to convert query variables from <script src="script.js?foo=bar&baz=zing"></script> to an object containing key:value pairs. Code is placed at the top of script.js:
var getVars = {};
(function(){
var scripts, currentScript, queryString;
scripts = document.getElementsByTagName('script');
currentScript = scripts[ scripts.length - 1 ];
queryString = currentScript.getAttribute('src').split("?").pop().split('&');
for(var i=0;i<queryString.length;i++){
var keyVal = queryString[i].split('=');
getVars[ keyVal[0] ] = keyVal[1];
}
}());
// console.info( getVars );
// Object { foo="bar", baz="zing"}
This probably won't work with deferred / asynchronously added script elements, as it relies on immediate code execution.
Sure. But the only way you can access that parameter though is through server-side. So, make twitter.js a PHP page (using mod_rewrite or whatever) that grabs $_GET['handle'] and then serves itself as Content-Type: text/javascript and just dump the contents of the js.
I suggest to use more safe approach - must add an ID:
<script id="myTargetScript" src="http://example.com/file.js?param=value" />
then in your .js file
function GetParams(target_id)
{
var getVars = {};
if( document.getElementById(target_id) )
{
var queryString = document.getElementById(target_id).getAttribute('src').split("?").pop().split("&");
for(var i=0;i<queryString.length;i++){
var keyVal = queryString[i].split('=');
getVars[ keyVal[0] ] = keyVal[1];
}
}
return getVars;
}
// console.log( GetParams('myTargetScript') );