Changing the state of apps script webapp - google-apps-script

I'm making standalone web app in Google Apps Script. I have somekind of task flow in the app. First search something from spreadsheet, then do some selections what to do with the collected data and after that fill spreadsheet row with some userinputs. So I need to run between few states.
I'm fairly sure I don't know enough about web tech so this might be very stupid question but here it goes.
I know that the e.parameters comes from the url like this www.google.com&value=helloworld
, e.parameters.value returns helloworld.
So now the problem: How do I set parameter e values in doGet(e) and call this same page or even different page/script in same script project? In otherwords how do I call self&value=helloworld ?
Is there some other way to do this? In GWT I just store everything to database and keep session in cookies. But cookies are not allowed so how to keep the state of the webapp in google apps script?
EDIT: This is how I pass the parameters for doGet(e).
function doGet(e) {
var app = UiApp.createApplication();
var newValue = 0;
if(e.parameter == undefined || e.parameter.value == undefined){
newValue = 1;
}else{
newValue = 1+parseInt(e.parameter.value);
}
var link = 'https://script.google.com/a/macros/domain.com/s/<insertidhere>/dev';
var anchor = app.createAnchor('Next',link+'?&value='+newValue);
anchor.setTarget('_self');
app.add(anchor);
var label = app.createLabel(newValue);
app.add(label);
return app;
}
The link format has changed a bit so instead of concatenate &value=... you need to add ? first like this ?&value1=helloworld&value2=....
Failing to use ? led me to think that there is bug and I need to use old format for the link and using that old format forbit any other than the script owner using it.
Hopefully this helps somebody to solve similar problems.

You've almost answered yourself. Append the URL paramenters to the end of your web app's URL and you can access them in your script.
Let's say you have a URL like
http://script.google.com/.../exec
When you hit the URL
http://script.google.com/.../exec?value=helloworld
,
inside doGet, you can read these URL parameters as
e.parameter.value;

IMO - instead of using ? to separate multiple parameters try keeping all the values in a single anchor with some other character separator of your choice such as # since after the first ? its possibly ignoring the other one and everything afterwards. Would be great to hear back on what you find.

Related

Published google script link with Google Sheet returning printing empty variable

Since I tend to ramble, first a short version and if you need more information read the long one.
TL;DR
Why is this:
function doGet(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var jobsCreated = sheet.getRange(12,2).getValue();
Browser.msgBox(jobsCreated);
var params = JSON.stringify({number:jobsCreated});
return ContentService.createTextOutput(params);
}
returning this when I published as website and then open:
{"number":""}
when it should look more like this {"number":2451}
Full Version:
First of all, I learned to program back in uni for my Computer science degree (10 years ago) but since then I haven't done much programming so I am basically a newbie.
Now to the question. I have a very simple script that a created with the script editor from Google Sheets
function doGet(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var jobsCreated = sheet.getRange(12,2).getValue();
Browser.msgBox(jobsCreated);
var params = JSON.stringify({number:jobsCreated});
return ContentService.createTextOutput(params);
}
First I get the sheet I am working on
Then I select a cell from that sheet
now if I use a msgBox to make sure that I have the right number and run the script, it works and it shows the message.
next, I format the variable as JSON and finally I just create a text output.
Now I deploy as Web app
Execute as ME
Anyone, even anonymous
And when I access the website I can only see this:
{"number":""}
If I change the code and give jobsCreated and static value it works fine
var jobsCreated = 100;
{"number":100}
So my conclusion is that the problem is with accessing the value of the cell when running the script from the published link compare to running it directly from the editor, but I have no idea how to fix this.
A little bit more information, i am trying to use this for a counter called Smiirl, i got most of the information from here
https://medium.com/#m_nebra/bootstrapping-your-company-counter-22f5d4bc7dd4
try this:
function doGet(e) {
return ContentService.createTextOutput(Utilities.formatString('number: %s',SpreadsheetApp.getActiveSheet().getRange(12,2).getValue());
}
Ok as I replied to #Aerials, thank you again for your help btw. Seeing other codes that should work not working with my script, I decided to create a new sheet and script as a test and with the exact same code it works.
But now checking on it a little bit more, something that I didn't think it was a problem since it was getting the number without any problems. The cell it's being populated by a GoogleAnalytics add-on. Now when setting up the add-on again to get the information the script from the website returns an empty value again. SO it seems the issue is with the script getting the information from the sheet (only the published version) when its being populated by the add on
Your issue is in the use of JSON.stringify
In JSON, functions are not allowed as object values.
The JSON.stringify() function will omit or change to null any functions from a JavaScript object.
That said, you can do the following:
function doGet(e){
// Get the value of the range
var sheet = SpreadsheetApp.getActiveSheet();
var jobsCreated = sheet.getRange(12,2).getValue();
// JSON Stringify it
var params = JSON.stringify({"number" : number=jobsCreated});
// Return JSON string
return ContentService.createTextOutput(params);
}
Returns:
{"number":123} if jobsCreated is the number 123, or
{"number":"Mangos"} if jobsCreated is the string "Mangos".
See it the script deployed here, and the sheet to play with.
Note:
You should avoid using functions in JSON, the functions will lose their scope, and you would have to use eval() to convert them back into functions.

Autocomplete Not Working - Google App Script

I'm having trouble with the autocomplete feature in Google App Script.
Built-in methods like SpreadsheetApp. will provide an autocomplete menu with methods to choose from.
However, if I create my own child object, autocomplete works for a little while, and then it just stops working.
for example:
var skywardRoster = SpreadsheetApp.getActiveSheet();
skywardRoster. will produce method options for a while, and then it stops.
However, the code still functions, and methods work if I type them out manually, so I know the declarations must be right. The menu simply won't appear, and it's just very inconvenient to have to look up each method individually as I go.
I have tried: breaking the variable and retyping that line; copy and pasting the code back into the editor; using different browsers; copying the gs file itself and working within the copy; and signing out completely and signing back in. Nothing seems to get it back to work.
I'm really new to coding, and I'm not sure what can be causing this.
Does anyone know how to fix this issue?
You might want to check Built-in Google Services:Using autocomplete:
The script editor provides a "content assist" feature, more commonly called "autocomplete," which reveals the global objects as well as methods and enums that are valid in the script's current context. To show autocomplete suggestions, select the menu item Edit > Content assist or press Ctrl+Space. Autocomplete suggestions also appear automatically whenever you type a period after a global object, enum, or method call that returns an Apps Script class. For example:
If you click on a blank line in the script editor and activate autocomplete, you will see a list of the global objects.
If you type the full name of a global object or select one from autocomplete, then type . (a period), you will see all methods and enums for that class.
If you type a few characters and activate autocomplete, you will see all valid suggestions that begin with those characters.
Since this was the first result on google for a non-working google script autocompletion, I will post my solution here as it maybe helps someone in the future.
The autocompletion stopped working for me when I assigned a value to a variable for a second time.
Example:
var cell = tableRow.appendTableCell();
...
cell = tableRow.appendTableCell();
So maybe create a new variable for the second assignment just during the implementation so that autocompletion works correctly. When you are done with the implementation you can replace it with the original variable.
Example:
var cell = tableRow.appendTableCell();
...
var replaceMeCell = tableRow.appendTableCell(); // new variable during the implementation
And when the implementation is done:
var cell = tableRow.appendTableCell();
...
cell = tableRow.appendTableCell(); // replace the newly created variable with the original one when you are done
Hope this helps!
I was looking for a way how to improve Google Apps Script development experience. Sometimes autocomplete misses context. For example for Google Spreadsheet trigger event parameters. I solved the problem by using clasp and #ts-check.
clasp allows to edit sources in VS Code on local machine. It can pull and push Google Apps Script code. Here is an article how to try it.
When you move to VS Code and setup environment you can add //#ts-check in the beginning of the JavaScript file to help autocomplete with the special instructions. Here is the instructions set.
My trigger example looks like this (notice autocompletion works only in VS Code, Google Apps Script cloud editor doesn't understand #ts-check instruction):
//#ts-check
/**
* #param {GoogleAppsScript.Events.SheetsOnEdit} e
*/
function onEditTrigger(e) {
var spreadsheet = e.source;
var activeSheet = spreadsheet.getActiveSheet();
Logger.log(e.value);
}
I agree, Google Script's autocomplete feature is pretty poor comparing with most of other implementations. However the lack is uderstandable in most cases and sometimes the function can be preserved.
Loosing context
The autocomplete is limited to Google objects (Spreasheets, Files, etc.). When working with them you get autocomplete hints, unless you pass such object instance to function as an argument. The context is lost then and the editor will not give you suggestions inside the called function. That is because js doesn't have type control.
You can pass an id into the function instead of the object (not File instance but fileId) and get the instance inside of the function but in most cases such operation will slow the script.
Better solution by Cameron Roberts
Cameron Roberts came with something what could be Goole's intence or a kind of hack, don't know. At the beginning of a function assign an proper object instance to parameter wariable and comment it to block:
function logFileChange(sheet, fileId){
/*
sheet = SpreadsheetApp.getActiveSheet();
*/
sheet.appendRow([fileId]); // auto completion works here
}
Auto completion preserved

Connecting Google Apps Script to a Parse Database (ScriptDB Replacement)

With the recent news that ScriptDB is being deprecated, I'm searching for a suitable replacement. My particular use case is that I'm running Google Apps Script under Google Forms to process and store data relevant to the function of the form.
I've been through Google's migration guide (link), and I'm trying to connect a Google Apps Script running under a Google Form to a Parse Database (link). I've tried both methods listed on the migration guide (URL Fetch Service and ParseDB Library), and I can't get either to work correctly. I was able to write to the Parse Database using ParseDB, but the query function isn't working as expected. I also tried using the parseCom library from the Excel Liberation site (sorry, I'm out of links for this post, apparently), but that didn't work very well either.
I'm most interested in using Google's URL Fetch Service to connect to a Parse database, as that seems to be my most flexible option (i.e. to let me share data between forms - something I really couldn't do with scriptDB), but I feel like I'm in over my head just a bit. I'm open to other options as well. Thanks in advance!
I have the same 'query' problem using the MongoLab database as overstack-asked here. I thought Parse might work as an alternative, but then I saw this post having the same 'query' problem.
If you run your query from the browser directly to Parse it will probably work fine, just as it does for me using the MongoLab database.
Consequently, I strongly suspect the problem is in the URLFetchApp.fetch() function itself, not the Parse (nor MongoLab) database functions.
Here's what I have found to be the best way to query results using UrlFetchApp and Parse.
function query(key,value) {
var properties = getKeys();
var appId = properties.appId;
var restApi = properties.restApi;
var class = 'TestObject';
var url = 'https://api.parse.com/1/classes/' + class;
var query = 'where={"' + key + '":"' + value + '"}'
var encoded = encodeURIComponent(query);
var queryUrl = url + '?' + encoded;
var options = {
"method" : "get",
"headers" : {
"X-Parse-Application-Id": appId,
"X-Parse-REST-API-Key": restApi,
}
}
var data = UrlFetchApp.fetch(queryUrl, options);
return data;
}
I am also looking for this.
As a temp store ScriptDb has been very helpful. Sheets simply does not have the elegance of beautiful coding as in ScriptDb

Basic UiApp failing with error "ReferenceError: "UIApp" is not defined."

I am creating some google app scripts for my company to use to generate random tests for employee training. I already have the basic scripts written to grab a list of questions from a google sheet, randomize them, grab the first 10 questions, etc. That all works fine. I decided it might be better to re-do the whole thing using a UiApp instead of just separate scripts. That is where the problem comes in. I did a simple bare bones UiApp to test with, published it and tried to hit the URL and that's where I encounter this error. I searched for this error and all I could find was some discussion about this being part of google apps premiere(which should have been folded into regular google apps around 2010). I've been staring at this so long I've frustrated myself. It should be something very simple and yet it's refusing to work. I'm assuming I am doing something wrong at a basic level but I've reached the point where my brain refuses to see it.
Here is the basic script I started with:
function doGet(e) {
var app = UiApp.createApplication();
var mainPanel = app.createVerticalPanel().setId('mainPanel');
mainPanel.add(app.createLabel('test'));
return app;
}
I save it, publish it and go to the URL and that's when I get the above error message. I know it's something simple but I've reached the point of frustration and simply can't see it.
Update: to reflect comments
Another possibility for WebApps not updating is not publishing a new version and only checking the exec URL. For instant changes to the code, always check the dev URL. The exec will only change after saving a version in Manage Versions and re-publishing the app.
First Answer:
I think your question title says it all.
UIApp is not defined, but Class is UiApp. JS is case sensitive. I copied and pasted the code exactly as it is in your question and received no errors. I did have to add one line to make the label show up.
function doGet(e) {
var app = UiApp.createApplication();
var mainPanel = app.createVerticalPanel().setId('mainPanel');
mainPanel.add(app.createLabel('test'));
app.add(mainPanel); // <-- I added this line to see the label
return app;
}

Trigger Google Apps Script by email

I'm looking for examples of a pattern where a demon script running within a GoogleAppsForBusiness domain can parse incoming email messages. Some messages will will contain a call to yet a different GAScript that could, for example, change the ACL setting of a specific document.
I'm assuming someone else has already implemented this pattern but not sure how I go about finding examples.
thx
You can find script examples in the Apps Script user guide and tutorials. You may also search for related discussions on the forum. But I don't think there's one that fits you exactly, all code is out there for sure, but not on a single script.
It's possible that someone wrote such script and never published it. Since it's somewhat straightforward to do and everyone's usage is different. For instance, how do you plan on marking your emails (the ones you've already read, executed, etc)? It may be nice to use a gmail filter to help you out, putting the "command" emails in a label right away, and the script just remove the label (and possibly set another one). Point is, see how it can differ a lot.
Also, I think it's easier if you can keep all functions in the same script project. Possibly just on different files. As calling different scripts is way more complicated.
Anyway, he's how I'd start it:
//set a time-driven trigger to run this function on the desired frequency
function monitorEmails() {
var label = GmailApp.getUserLabelByName('command');
var doneLabel = GmailApp.getUserLabelByName('executed');
var cmds = label.getThreads();
var max = Math.min(cmds.length,5);
for( var i = 0; i < max; ++i ) {
var email = cmds[i].getMessages()[0];
var functionName = email.getBody();
//you may need to do extra parsing here, depending on your usage
var ret = undefined;
try {
ret = this[functionName]();
} catch(err) {
ret = err;
}
//replying the function return value to the email
//this may make sense or not
if( ret !== undefined )
email.reply(ret);
cmds[i].removeLabel(label).addLabel(doneLabel);
}
}
ps: I have not tested this code
You can create a google app that will be triggered by an incoming email message sent to a special address for the app. The message is converted to an HTTP POST which your app receives.
More details here:
https://developers.google.com/appengine/docs/python/mail/receivingmail
I havn't tried this myself yet but will be doing so in the next few days.
There are two ways. First you can use Google pub/sub and handle incomming notifications in your AppScrit endpoint. The second is to use the googleapis npm package inside your AppScript code an example here. Hope it helps.
These are the steps:
made a project on https://console.cloud.google.com/cloudpubsub/topicList?project=testmabs thing?
made a pubsub topic
made a subscription to the webhook url
added that url to the sites i own, i guess? I think I had to do DNS things to confirm i own it, and the error was super vague to figure out that was what i had to do, when trying to add the subscription
added permission to the topic for "gmail-api-push#system.gserviceaccount.com" as publisher (I also added ....apps.googleusercontent.com and youtrackapiuser.caps#gmail.com but i dont think I needed them)
created oauth client info and downloaded it in the credentials section of the google console. (oauthtrash.json)