Global Variable value not usable in multiple functions - google-apps-script

Using Google Apps Script I'm trying to create a global variable (e.g. an array) that can be used in multiple functions, but I can't seem to find the answer anywhere and I need it for my Google Spreadsheet to work.
Code:
var infoSheetArray = null;
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('Fetch Info', 'grabInfo')
.addItem('Run Program', 'itemSetup')
.addToUi();
}
function grabInfo() {
var infoSheet = SpreadsheetApp.openByUrl('....');
infoSheetArray = infoSheet.getSheetValues(1, 1, infoSheet.getLastRow(), 10);
}

In your code, infoSheetArray is a variable with global scope, meaning that it is accessible by all code in your program. What isn't so obvious is that your program is running multiple times & in different "machines", and that's why it may appear that infoSheetArray is getting reset.
Each separate execution of a script is done in a new execution instance, and each of those have their own set of "globals". In your example snippet, for example, onOpen() is an automatic trigger function invoked independently when the spreadsheet is opened in the web editor. You have two other functions, grabInfo() and itemSetup() (not shown) that get invoked independently via user menu extensions.
When any of these three functions are invoked independently, an execution instance is created for it, your program is loaded, and then execution begins. All global statements (e.g. var infoSheetArray = null;) are evaluated first, followed by the specifically triggered function. Any changes made to global variables will persist only within the execution instance the change happened in, and only for as long as that instance is required. Once the function completes, the instance is blown away.
If you need to persist values between execution instances, you need to use some storage method such as the Properties Service or an External Database.

Related

Try Catch for if script is already running in sheets, prevent a 2nd instance at same time?

I have a script that does some basic copy and pasting of data, creates a sheet as a pdf and attaches to an email.I have a button to start it for my less-tech-experienced colleagues embedded in a sheet:
We share the spreadsheet doc and I'd like to keep it that way for version control. What's the best way to set the script up so that if an instance of the script is already running in the spreadsheet doc it rejects a 2nd attempt until the first one is completed? Some sort of try catch?
I don't think the code is as relevant for this question, here's the beginning of the function in question:
function failedSettlementsEmailz() {
var contacts;
var toastMembers="";
....more code...
It calls two other functions in my script document that is bound to the Google Sheet spreadsheet.
Thanks
Take a look at the LockService.
For example in your code have:
var lock = LockService.getScriptLock();
if (lock.tryLock(1000)) { // Wait for 1s
// Do stuff ...
lock.releaseLock()
} else {
// This script is already running, try again later ...
}
I understood that when the script is run by clicking a button, you don't want to run the script more while the script is already running. If my understanding is correct, how about this workaround? Please think of this as one of several answers.
In this workaround, I selected to use CacheService. "CacheService allows you to access a cache for short term storage of data." The default expiration is 10 minutes. This is over the maximum execution time of GAS (6 minutes). So I selected this.
Flow :
Retrieve the key of "script" from CacheService.
If there is the key, it means that the script is running.
If there is no key, it means that the script is not running.
At this time, put the key of "script" with a value.
The process you want is run.
After the process was finished, the key is removed. This means the script is not running.
Sample script :
function failedSettlementsEmailz(){
var cache = CacheService.getScriptCache();
if (!cache.get("script")) {
cache.put("script", "running"); // you can use various value for the value of "running".
// do something
cache.remove("script");
}
}
Reference :
CacheService
If I misunderstand your question, I'm sorry.

Service Invoked Too Many Times (Google Apps Script)

I want to use Google Apps Script to make custom functions for a spreadsheet. I've made an extremely simple function:
function foo(){
return "bar";
};
The problem is that I need this function in a couple hundred cells. When I paste the function =foo() into all of these cells, the function works in a few of the cells, but in most I get this error: "Service invoked too many times: spreadsheet. Try Utilities.sleep(1000) between calls."
[Screenshot here]
I guess I don't understand why this function, simple as it is, is considered an invocation of the Spreadsheet Services. I'm not even requesting any data (except for the function itself). Is that the problem? And if so, is there a workaround? Custom functions could make Google Spreadsheets infinitely more powerful, but this problem hamstrings the possibility of using a custom function in multiple cells. Suggestions?
(P.S. -- Using the Utilities.sleep() function as suggested by the error message doesn't help at all when all of the cells call their functions simultaneously; it only slows the rate at which individual cells repeatedly call the function.)
According to the Optimization section on the Apps Script Function Guide:
Each time a custom function is used in a spreadsheet, Google Sheets makes a separate call to the Apps Script server. If your spreadsheet contains dozens (or hundreds, or thousands!) of custom function calls, this process can be quite slow.
Consequently, if you plan to use a custom function multiple times on a large range of data, consider modifying the function so that it accepts a range as input in the form of a two-dimensional array, then returns a two-dimensional array that can overflow into the appropriate cells.
To do this, pass in an input that represents the size of the array you'd like to return. When you start executing your function check if the input parameter is an array with input.map. If it is, you can call the the function on each item and return that entire collection.
So in your case like this:
function foo(){
return "bar";
};
You can update the function like this:
function foo(input){
if (input.map) { // Test whether input is an array.
return input.map(foo); // Recurse over array if so.
} else {
// do actual function work here
return "bar";
}
};
And then call it like this:
By calling the function in the spreadsheet, you are invoking the Spreadsheet service by asking it to go round-trip to the server to run the results of your function. As a result, you have make a couple hundred requests in a very short period of time.
One work around might be to add your function a few cells at a time. Of course, when you subsequently open the sheet again, you will probably run into the same problem.
Depending on what your function is trying to accomplish, it might be worth using the built in spreadsheet functions. There is a lot of power there. Writing a function that acts on a range of values instead of a single cell might be another, better, option. It could be triggered through a custom menu item, or by using the script manager.
Keep in mind, batch actions are your best friend when it comes to working with spreadsheets.

function onOpen() is not running

My function includes adding a menu and toast to the document. I have verified that the trigger (onOpen) is set as well. It only works when a user goes into Tools, Script Manager, Run. We have too many users with too many backgrounds to expect then to know how to do this. Why isn't it working? (Using Chrome)
function onOpen()
{
var menus = [{name: "Advance in Workflow", functionName:"sendEmail"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu("Auto Advance FG Workflow", menus);
//sheet.toast(Notify/Remind users);
sheet.toast("While you are here we kindly ask that you do not add, modify or remove any columns.","Welcome - " + username,8);
}
Thanks,
I was having the same issue.
I realized, sometimes Google create some kind of cache of the scripts (I'm used to have a "test" script and I usually alter it's content, and, sometimes, the script runs as if I didn't).
So, what I did that solved the onOpen() not working was changing the function name and ading a trigger manually.
Go to "Resources -> Current script's triggers…"
Choose the function to run on open
It worked like a charm here!
Updated Location Information:
or
Then
This is an old post but I just had this problem and find out why it was not working correctly in my case:
I had, at the top of my script file a variable that required some authorisations and that prevented the script to correctly run. I saw that OP called var username = Session.getActiveUser().getUsername(); (that requires authorisations, and it's may be the cause).
eg:
this code won't work:
function onOpen(){
SpreadsheetApp.getUi()
.createMenu("Exportation")
.addItem("Lancer l'exportation", "exportationMenu")
.addToUi();
}
var stConsCons= SpreadsheetApp.openById(sgcid).getSheetByName("Consultant");
but this one will work:
function onOpen(){
SpreadsheetApp.getUi()
.createMenu("Exportation")
.addItem("Lancer l'exportation", "exportationMenu")
.addToUi();
}
function whatever(){
var stConsCons= SpreadsheetApp.openById(sgcid).getSheetByName("Consultant");
...}
It turns out that you need to add the onOpen(e) function to Triggers!
In my case, onOpen wasn't working because I had a variable, outside of a function, opening a sheet with SpreadsheetApp.openById() rather than SpreadsheetApp.getActiveSpreadsheet(). I guess onOpen doesn't work with openById() even if the sheet you are opening is bound to the script. onOpen() won't work with this kind of a variable outside of a function:
var sheet = SpreadsheetApp.openById("1b_PQD...").getSheetByName("demos")
If your script is bound to the sheet, you can solve this problem by using the getActiveSpreadsheet() function. Otherwise, you can solve it by putting your openById() call into a function.
In my case there was a reference error, that while did not stop the script entirely, it did stop the menu from appearing.
I was only able to detect that error after I run a debug on the script.
It looks like the problem may be that "sheet" isn't defined, which is why the toast is failing.
I know this is a really old question, but for any one finding this now, I may have a solution. The onOpen function often runs with the authorization mode none instead of limited when being used as an event trigger. This may cause errors in things that are related to the specific file or user data. For example:
function onOpen(e) {
SpreadsheetApp.getActivePresentation(); //Will error out if permissions are not set to limited.
SpreadsheetApp.getUi(); //This will always run even if the AuthMode is set to NONE
}
Additionally it is worth noting that if you have any variable used or initialized before onOpen(e), basically any global variables that access sensitive info fail if the AuthMode is set to NONE.
var ss = SpreadsheetApp.getActivePresentation(); //bad
var ss;
...
function init() {
ss = SpreadsheetApp.getActivePresentation(); //good because now that the function is already run we should have full permissions
}
Simple triggers silently fail if they lack permission. I ran into this with an onOpen() in a script that initialized File objects of non-bound files. I moved all File object instantiation to menu functions that do have permission.
In other words, this will not work
function onOpen(e) {
...
let file = DriveApp.getFileById(nonBoundFileId);
...
}
but this will work
function menuFunction() {
...
let file = DriveApp.getFileById(nonBoundFileId);
...
}
because menuFunction can be given permission(s) that simple triggers lack.

How to Access ScriptDB from Spreadsheet Custom Function?

In this post, I suggested to use the ScriptDB as an intermediate storage for global data of a Container Extension code. I wrote a sample code for my answer but the sample throws the error: You do not have permission to call query (line X) exception in a ScriptDb.getMyDb().query(...); line. I created the following simpler example demonstrating the problem. The code, both getDBSize and getSource functions, is permitted to use the ScriptDB by running it in the editor. The getDBSize function is executed without any problem by pressing the Run button in the Spreadsheet Script Manager Dialog. The getSource function works everywhere.
I published the Spreadsheet for the example - link. It is impossible to share the code for view, but it is possible to output it in a cell, the cell B3 contains exactly bellow code.
How is possible to permit the Spreadsheet Code to have access to the ScriptDB?
function getDBSize() {
var db = ScriptDb.getMyDb();
var result = db.query({});
var count = result.getSize();
return count;
}
function getSource() {
return this.toSource();
}
The problem is that you're trying to run this function as a spreadsheet custom function, and custom functions are way more limited than all examples on the Container Extension page you linked.
But, from a theoretical point of view, custom functions as well as simple event handlers (e.g. onEdit, onOpen), can not access anything that requires the user account or is associated with the user, only "generic" methods. For example, UrlFetchApp, open the current spreadsheet, or read ScriptProperties.
So, in thesis, querying a ScriptDb should be possible, since it's a generic call and has nothing to do with the active user, it's analogous to ScriptProperties. I don't see a workaround that would actually let you query the db, but you could use ScriptProperties instead. You can easily save and retrieve any object you would save on ScriptDb by using JSON.stringify and .parse. Of course, I'm not comparing ScriptDb capabilites with ScriptProperties, it's just a workaround.
Anyway, this seems like a good candidate for an enhancement request on our issue tracker.

What is the scope of variables available to google.script.run.myFunction()

I'd like a little sanity check if I may.
Can Javascript code in an HtmlOutput window floated over a Google spreadsheet alter variables and call methods in the gs code that created the HtmlOutput in the first place?
I have a Google spreadsheet with a floating form created like this:
someCode.gs
var theForm;
var theSpreadsheet;
function makeForm() {
:
theForm = HtmlService.createTemplateFromFile('aForm').evaluate();
theSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
theSpreadsheet.show(theForm);
:
}
function recordTheForm(jsonFormData) {
:
theForm.clear();
:
}
Attached to a button in the HTML of "aForm.html" I have a function call back into the file "someCode.gs".
aForm.html
:
google.script.run.recordTheForm(jsonTheForm);
:
Am I right that, even though it was instantiated from someCode.gs, the caller of recordTheForm() can have no knowledge of the contents of that originating memory space?
Is there anyway to get it? such as passing a "context" back and forth?
Variables in GAS:
Browser button clicks and other browser events produce a call to GAS. Every call from the browser to GAS causes your GAS code to re-run, re-initializing all variables. This is a problem for global variables. Global variables need to be passed back from the Browser to your GAS code or reloaded from the Database or Script Properties.