Using google.visualization.Query in google web app - google-apps-script

I have a functioning google web app that is identical to the app presented [here]
You'll note in code.gs that SpreadsheetApp.openByID......getRange().getValues() is used to retrieve an Array that is later converted into a DataTable in the Dashboard-JavaScript.html
Working Code.gs:
function doGet(e) {
var template = HtmlService.createTemplateFromFile('Index');
// Build and return HTML in IFRAME sandbox mode.
return template.evaluate()
.setTitle('Dashboard demo')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
/**
* Return all data from first spreadsheet as an array. Can be used
* via google.script.run to get data without requiring publication
* of spreadsheet.
* Returns null if spreadsheet does not contain more than one row.
*/
function getSpreadsheetData() {
var sheetId = '-key-';
var data = SpreadsheetApp.openById(sheetId).getSheets()[0].getRange("A1:D8").getValues();
return (data.length > 1) ? data : null;
}
I would like to use google.visualization.query instead of .getRange.
Does not work - currently returns "Google" not defined
function doGet(e) {
var template = HtmlService.createTemplateFromFile('Index');
// Build and return HTML in IFRAME sandbox mode.
return template.evaluate()
.setTitle('Dashboard demo')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function getSpreadsheetData() {
var opts = {sendMethod: 'auto'};
var sheetId = '-key-';
var query = new google.visualization.Query('http://spreadsheets.google.com?key=' + sheetId, opts);
query.setQuery('select A, B, C, D');
query.send(draw)
}
function draw(response) {
if (response.isError()) {
alert('Error in query');
}
alert('No error')
}
I'm certain there are several issues - but I can't get any helpful errors returned to debug the issue.
My questions are:
Is is possible to use google.visualization.query in code.gs?
(I've read a post that leads me to believe that perhaps it cannot be used server side??/why)
If yes - how do I avoid "google not defined" errors
If no - is there an alternative method to "query" a google sheet from server side (the end goal is to have the flexibility to omit columns, perform aggregate functions, etc. when the datatable is retrieved). It is not possible to change the underlying spreadsheet (ie. sharing and publishing)
Finally- I apologize if any of this is formatted poorly or not clear. This is my first post here. In addition, I have limited experience and less expertise with javascript/apps script and google web apps.

No, it cannot be done server side, because google is a client side API.
Same reason as with google.script.run. (For a better understanding, you can check the whole code yourself, it resides here, a code that needs to be embedded within <script> tags, on the Html side).
As an alternative for the server side, you should be able to use the URLFetchApp.
The URL to compose should look something like:
var BASE_URL = "https://docs.google.com/a/google.com/spreadsheets/d/";
var SS_KEY = "stringSS_Key";
var BASE_QUERY = "/gviz/tq?tq=";
var partialUrl = BASE_URL + SS_KEY + BASE_QUERY;
var queryToRun = 'select dept, sum(salary) group by dept';
var finalUrl = partialUrl + encodeURIComponent(queryToRun);
And call URLFetchApp.fetch on it.

Related

Use Google Web App URL Parameter As Part Of Function

I'm trying to use a parameter in a Google web app url to select which tab of a Google Sheet I want to extract data from. The request for data is made via a Chatfuel JSON GET request, which pulls data from the spreadsheet tab and returns it as formatted json code into the chatbot.
When I run the request without passing a parameter and manually enter the sheet tab name into the doGet function of the Google Sheet script as below it works fine.
URL - https://script.google.com/macros/s/xxx-xxx-xxx/exec
function doGet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Retail");
var data = sheet.getDataRange().getValues();
elements = create_elements(data)
if (elements.length != 0) {
return buildImageGallery(elements);
} else {
return notFound()
}
}
I just can't quite figure out how to take a parameter from a url (e.g "type"), and then use that as part of the doGet function - this is what I'm roughly trying to do...
URL - https://script.google.com/macros/s/xxx-xxx-xxx/exec?type=Retail
function doGet() {
var type = e.parameter.type;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(type);
var data = sheet.getDataRange().getValues();
elements = create_elements(data)
if (elements.length != 0) {
return buildImageGallery(elements);
} else {
return notFound()
}
}
I'm sure it's soemthing simple, but I'm just getting started with this coding stuff so I'm not sure what I need to change. Thanks in advance for your help.
Does function doGet(e) work?
Is it a typo or you missed it? The e parameter inside the function.

Triggering GAS function via URL

Very new to this, but giving it a shot. I am trying to set up an Arduino motion sensor to trigger a script. At this point, my goal is to trigger a script via URL. I found this code below that I am working through, but I continue to get this error when running/debugging.
TypeError: Cannot read property "parameter" from undefined. (line 4, file "Code")
I have been looking into e.parameter object, but have not been able to make any headway
function doGet(e) {
Logger.log(e)
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'tylog') {
whatToReturn = tylog(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
var mns = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Monster")
var tyl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyLog")
var tyd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyData")
var twl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twLog")
var twd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twData")
var tym = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyMaster")
var twm = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twMaster")
var test = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test")
var tydate = tyd.getRange('A2');
var tydur = tyd.getRange(2, 2);
// Start functions
function start() {
tyl.getRange('A1').setValue(new Date());
twl.getRange('A1').setValue(new Date());
}
//Log Typhoon ride
function tylog() {
tyl.getRange(tyl.getLastRow() + 1, 1).setValue(new Date());
}
//Log Twister ride
function twlog() {
twl.getRange(twl.getLastRow() + 1, 1).setValue(new Date());
}
//Send Data to both logs and clear
function tyclear() {
tyd.getRange('A2:H2').copyTo(tym.getRange(tym.getLastRow() + 1, 1), {contentsOnly: true});
twd.getRange('A2:H2').copyTo(twm.getRange(twm.getLastRow() + 1, 1), {contentsOnly: true});
tyl.getRange('A1:A100').clearContent();
twl.getRange('A1:A100').clearContent();
}
URL Request:
https://script.google.com/macros/s/AKfycbxC5zYevR1IhfFcUMjmIqUaQ1dKNHTm4mhmWBq_Rc9HgemJQ6Q/exec?searchStringName=tylog
I put this into a new project by itself and it still returned undefined​.
function doGet(e) {
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'functionOne') {
whatToReturn = functionOne(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
function functionOne() {
var something;
return ContentService.createTextOutput("Hello, world!"); }
I believe that your URL should be https://script.google.com/macros/s/AKfycbxC5zYevR1IhfFcUMjmIqUaQ1dKNHTm4mhmWBq_Rc9HgemJQ6Q/exec?searchStringName=functionOne
After pondering this question for a while it makes no sense to require a return from functionOne. I was getting the client server communication mixed up with the Get request process. For most Get requests the request suggests some type of response since in general we're looking for some type of content to be displayed. In this situation that may not be required since the requestor is a machine.
The use of e.parameter.paramname; just enables us to send key/value pairs from within our querystring that we can recover to redirect our server actions.
2020 UPD:
Upon revisiting the question, I noticed that the OP runs the doGet trigger in the context of script editor, hence the e becoming undefined (as it is only constructed when a request hits the URL with an HTTP request with GET method).
Thus, the answer to the debugging part is:
When running a trigger manually from the script editor, event object will be unavailable
The answer to the running part is as a result of an extended discussion:
When assigning a result of the function, one has to put the return statement inside the function, and the tylog function did not return anything.
Also note that any change to a Web App code, unless accessing it via /dev endpoint (i.e. via /exec endpoint), won't be available until after redeployment.
References
Web Apps guide

Is GAS/google sheet an alternative option to PHP/mySQL?

I keep finding forum results that refer to the google visualiser for displaying my query results. But it just seems to dump the data in a pre-made table. I haven't found a way to write my own custom table dynamically.
In the past, when I have hacked together PHP to make use of mySQL DB, I would simply connect to my DB, run a query into an array, then cycle through the array and write the table in HTML. I could do IF statements in the middle for formatting or extra tweaks to the displayed data, etc. But I am struggling to find documentation on how to do something similar in a google script. What is the equivalent work flow? Can someone point me to a tutorial that will start me down this path?
I just want a simple HTML page with text box and submit button that runs a query on my Google sheet (back in the .gs file) and displays the results in a table back on the HTML page.
Maybe my understanding that GAS/google sheets is an alternative to PHP/mySQL is where I'm going wrong? Am I trying to make a smoothie with a toaster instead of a blender?
Any help appreciated
Welcome David. Ruben is right, it's cool to post up some code you have tried. But I spent many months getting my head around Apps-script and love to share what I know. There are several ways to get the data out of a Google sheet. There is a very well document Spreadsheet Service For GAS. There is also a client API..
There is the approach you mention. I suggest converting the incoming data to JSON so you can do with it as you like.
Unauthenticated frontend queries are also possible. Which needs the spreadsheet to be published and set to anyone with the link can view, and uses the Google visualisation API
var sql = 'SELECT A,B,C,D,E,F,G where A = true order by A DESC LIMIT 10 offset '
var queryString = encodeURIComponent(sql);
var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/'+ spreadsheetId +'/gviz/tq?tq=' + queryString);
query.send(handleSampleDataQueryResponse);
function handleSampleDataQueryResponseTotal(responsetotal) {
var myData = responsetotal.getDataTable();
var myObject = JSON.parse(myData.toJSON());
console.log(myObject)
}
Other Approaches
In your GAS back end this can get all of your data in columns A to C as an array of arrays ([[row1],[row2],[row3]]).
function getData(query){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range = sheet.getRange('A1:C');
var data = range.getValues();
// return data after some query
}
In your GAS front end this can call to your backend.
var data = google.script.run.withSuccessHandler(success).getData(query);
var success = (e) => {
console.log(e)
}
In your GAS backend this can add data to your sheet. The client side API will also add data, but is more complex and needs authentication.
function getData(data){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range = sheet.getRange('A1:C');
var data = range.setValues([[row1],[row2],[row3]]);
return 'success!'
}
In your GAS frontend this can send data to your backend.
var updateData = [[row1],[row2],[row3]]
var data = google.script.run.withSuccessHandler(success).getData(updateData);
var success = (e) => {
console.log(e)
}
Finally
Or you can get everything from the sheet as JSON and do the query in the client. This works okay if you manipulate the data in the sheet as you will need it. This also needs the spreadsheet to be published and set to anyone with the link can view.
var firstSheet = function(){
var spreadsheetID = "SOME_ID";
var url = "https://spreadsheets.google.com/feeds/list/" + spreadsheetID +"/1/public/values?alt=json";
return new Promise((resolve,reject)=>{
$.getJSON(url, (data)=>{
let result = data.feed.entry
resolve(result)
});
})
}
firstSheet().then(function(data){
console.log(data)
})

Calling a bound script's method using the Execution API

I'm using PropertiesServices as variables, specifically Document Properties , in order to replace some tokens like "{client name}". Since those properties are scoped to the bound script only, I'm looking for a way to modify their values from my PHP application.
Is it possible to call a bound script's function using the Execution API, or maybe from a standalone script? Otherwise, should I instead use the Script Properties instead (although the docs make me think you can't use them if the script isn't 'standalone).
It looks like if the user that the Execution API is running under has permission to the doc that bound script ran by the execution api can read document properties.
Here is my test:
Create a new spreadsheet. Create a new script. Add some data using the menu from onOpen. Run executeAPI inside the script. The log successfully shows the document properties.
function onOpen() {
var testMenu = SpreadsheetApp.getUi().createMenu("test")
testMenu.addItem("Add some data", "addData").addToUi();
testMenu.addItem("Preview data", "getData").addToUi();
}
function getData(){
var keys = PropertiesService.getDocumentProperties().getKeys();
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().clear().appendRow(keys)
}
function returnData(){
return PropertiesService.getDocumentProperties().getKeys();
}
function addData(){
var DT = new Date().toString()
PropertiesService.getDocumentProperties().setProperty(DT,DT);
}
function executeAPI(){
var url = 'https://script.googleapis.com/v1/scripts/'+ScriptApp.getProjectKey()+':run';
var payload = JSON.stringify({"function": "returnData","parameters":[], "devMode": true});
var params={method:"POST",
headers:{Authorization: "Bearer "+ ScriptApp.getOAuthToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
Logger.log(results)
}

Google Picker - Return the File ID to my Google Script

I have a fairly basic spreadsheet that uses some Google Scripts to accomplish various tasks. I was trying to cleanup the interface for the end user, and decided to implement the Google Picker. Originally the user had to manually import a CSV into the spreadsheet. The new goal here is to select the CSV via the Google Picker, upload it, import it, then delete it. I already have all the code working to import it and delete it. I just worked up the code for the picker, and it seems to work fine. However, and I think I'm just missing something small, how do I pass the File ID back from the Picker.html to my Google Scripts in order to continue my process?
If it helps, I'm using the basic callback provided in the Google documentation right now. I'm assuming this is where the change will be made. Just not sure what to do.
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: ' + title + '<br>ID: ' + id;
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
This should probably work:
In your pickerCallback(data) function:
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
google.script.run
.withSuccessHandler(useData) // this will call the google apps script function in your Code.gs file
.doSomething(fileId); // this is a function in your JavaScript section where you will do something with the code you got from your apps script function
}
function useData(data) {
// do something with the data
}
In Code.gs, create a function to handle the input from the picker:
function doSomething(fileId) {
// do an operation in Drive with the fileId
var file = DriveApp.getFileById(fileId);
var fileName = file.getName();
return fileName;
}
First of all, open the chrome developer console when you are running this so you can see any errors that happen client side (when the picker is active). You can also use console.log to report any variable values in the Chrome console.
secondly, the call to the server works asynchronously, so it means that in your code, you'll get your message 'script was run', when it fact it hasn't yet. All that's happened is that google.script.run has asked for your server side function to execute.
That's why you have withSuccessHandler and withFailureHandler.
so you should do
google.script.run
.withSuccessHandler (function (response) {
document.getElementById('result').innerHTML = 'it worked'
})
.withFailureHandler (function (err) {
document.getElementById('result').innerHTML = err;
})
.justatest (fileId);
and back in the server script
function justatest(fileId) {
Logger.log (fileId);
}
If you then go back and look in the script log file, you should see the fileId.