Access local variables while using Google Apps Script Libraries - google-apps-script

I am working with different Google spreadsheets which all use the same sheets and structure. All of them do also use an identical set of functions which are executed by using custom menu items.
To maintain the functions only once for all spreadsheets, one serves as a Library for all others. Now here is the problem: All spreadsheets have a set of individual properties which need to be passed to Library function calls. That's why I am currently doing something like this:
Sample function in the library which is made available as lib as well as the menu construction for all sheets:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Actions')
.addItem("My Library Function", "trackingHelper")
.addItem("My 2nd Library Function", "anotherFunction")
.addToUi();
}
function trackingHelper(sheetProperties) {
do something and use the sheetProperties
}
And now, in the "child" sheets I am adding something like this:
var sheetProperties = {key: value, ...}
function onOpen() {
lib.onOpen();
}
function trackingHelper() {
lib.trackingHelper(sheetProperties);
}
function anotherFunction() {
lib.anotherFunction(sheetProperties);
}
The problem is, that I always need to edit all sheets if I add new functions. That's why I'd like to do something like this with the menu in the library spreadsheet:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Actions')
.addItem("My Library Function", "lib.trackingHelper")
.addItem("My 2nd Library Function", "lib.anotherFunction")
.addToUi();
}
So I only want to add one onOpen with this menu to all child sheets to spare the need for adding all functions individually. But how can I pass my child properties now? I tried to use the PropertiesService but executing a script from a child sheet always yielded the properties scope of the library sheet.
Is there any possibility to avoid the need for remapping all local functions to library functions in order to pass some sheet-specific variables? Thanks a lot.

Are the sheet properties in the child sheets variable or are they static?
If static, store them in the property service with the child spreadsheet ID as the key, then use the getactivespreadsheet().getID to pull the properties out from within the lib file.
Much faster than a get value read off the sheet but I can’t say that I’m free of functions hat pull variables from a spreadsheet as well. It was easy at the time and just works. Unless someone edits the formatting or accidentally deletes values or the sheet.

EDIT: I have the same problem as you. I still have to remap all library functions in the child script, BUT I have figured out how to manage individual properties as below.
--
In the library, you can manage the variables for each child:
PROJECT_NAME1 = {
name: 'abc',
visibleOverride: false
};
PROJECT_NAME2 = {
name: 'xyz',
visibleOverride: true
};
In the child script, you can have a global variable like so:
PROJECT = lib.PROJECT_NAME1;
In the child script, you pass the global PROJECT variable to the library functions:
function hideSheet() {
lib.Group.toggleSheetVisibility(PROJECT, false);
}
function showSheet() {
lib.Group.toggleSheetVisibility(PROJECT, true);
}
In the library, you can access the individual settings as customised by the originating child script:
Group = {
toggleSheetVisibility: function (PROJECT, visible) {
var sheet = SpreadsheetApp
.getActive()
.getActiveRange()
.getSheet();
if (!PROJECT.visibleOverride) {
visible
? sheet.showSheet()
: sheet.hideSheet();
}
}
}

Related

When i put on global variables the trigger onOpen() doesn't work [duplicate]

This question already has answers here:
How to use global variables while avoiding permission errors?
(2 answers)
Closed 2 months ago.
When i put on global variables the trigger onOpen() doesn't work. do you know if there is a solution for that? Thank you!
/**variables globales */
var elLibro = SpreadsheetApp.openById("1pfbd7oziXmBTsOh5RhfvhsjW5wU6QF9suN7SNrE8rew");
var laHoja = elLibro.getSheetByName("Cuotas");//obtiene la hoja
var laHojaTablaFinal = elLibro.getSheetByName("Tabla final");
function onOpen(e) {
// Add a custom menu to the spreadsheet.
SpreadsheetApp.getUi() // Or DocumentApp, SlidesApp, or FormApp.
.createMenu('Custom Menu')
.addItem('First item', 'menuItem1')
.addToUi();
}
when i delete my global variables the trigger onOpen works good. i'm trying to Add a custom menu to the spreadsheet.
The onOpen(e) function is a simple trigger that will run automatically each time the spreadsheet is opened. In that context, API methods that require authorization are not available.
When a script function is called by the spreadsheet, globals get evaluated before the function runs. In the onOpen(e) context, SpreadsheetApp.openById() will error out because of the lacking authorization, and onOpen(e) never gets the chance to run.
One way to solve this is to put your globals inside a function, say getSettings_(), that returns an object that contains the often used variables, and call it from the functions that use those values. It could be something like this:
let settings;
function getSettings_() {
if (settings) {
return settings;
}
settings = {
elLibro: SpreadsheetApp.openById("1pfbd7oziXmBTsOh5RhfvhsjW5wU6QF9suN7SNrE8rew"),
laHoja: elLibro.getSheetByName("Cuotas"),
laHojaTablaFinal: elLibro.getSheetByName("Tabla final"),
};
return settings;
}
function someFunction(someParameter) {
const settings = getSettings_();
const someValue = settings.laHoja.getRange('A1');
console.log(someValue);
}
function onOpen(e) {
// ... does not call getSettings_()
// ...
}
This pattern has the benefit that functions that require those globals can get them easily, while functions that do not require those globals do not need to get them and will thus run faster.
The values in settings are only set once. When multiple functions call getSettings_() in turn, the first call fills the settings object, and subsequent calls just get a reference to that same copy of the object. Possible runtime modifications to settings are seen by all functions within the same runtime instance.
Using global variables only when you need them:
const gobj={get init(){delete this.init;this.globals=getGlobals();this.init=PropertiesService.getScriptProperties().getProperties();}};
readInit();
function readInit() {
gobj.init;
}
Using Lazy Load

How could I call a custom function within a QUERY formula on Google Sheets?

Would it be something like this?
=query(IMPORTRANGE("https://docs.google.com/spreadsheets/d/fsdgsgsdhdshldhsdhgvs/edit","Reference Info!A2:J"),"select * where Col2 matches '"&fileName()&"'")
Here is the fileName() function:
//Since I'm not sure fileName() will run as the recalculations happen, I've added this one
function onOpen(){
fileName();
}
function fileName() {
return SpreadsheetApp.getActiveSpreadsheet().getName();
}
Thanks for any help!
Your formula is fine, but it doesn't make sense to have
function onOpen(){
fileName();
}
as custom functions are executed when the spreadsheet is opened and when the custom function arguments change.
Related
Refresh data retrieved by a custom function in Google Sheet
get sheet names not work properly or not auto update after use loop to copy to new sheets

Using a variable from another script in the same document (apps scripting)

I am trying to access a variable from another script A.gs in script B.gs. they are both in the same document. How could I do this?
I am not sure how I should solve this problem, I am a beginner with apps scripting and I can't find anything on the internet about it.
code.gs:
ui = DocumentApp.getUi();
function onOpen () {
A = prompt('Hello');
}
code2.gs:
function onOpen () {
if (A === "123") {
ui.alert('Hello')
}
}
I want Hello to be output if 123 is entered into the prompt, but when I try to run the code I get the error:
ReferenceError: "A" is not defined. (line 3, file "code2")
In your situation, code.gs and code2.gs are in a project of a container-bound script type of Google Document.
If my understanding is correct, how about this answer? Please think of this as just one of several answers.
Modification points:
In your script, the scripts of code.gs and code2.gs are used as one project at the script editor. So in your script, there are 2 same functions of onOpen() in the project. In this case, only one of them is run. In your case, onOpen() of code2.gs is run and the error of ReferenceError: "A" is not defined. occurs.
Modified script:
If you want to modify your script and you want to work the functions when the Google Document is opened, how about the following modification?
1. Copy and paste the following script to code.gs and code2.gs of the script editor:
code.gs:
var ui = DocumentApp.getUi();
function installedOnOpen () {
A = prompt('Hello'); // or ui.prompt('Hello').getResponseText();
sample(A);
}
code2.gs:
function sample (A) {
if (A === "123") {
ui.alert('Hello')
}
}
Or, if you want to run independently 2 functions, how about the following modification? In this modification, the value is saved using PropertiesService.
code.gs:
var ui = DocumentApp.getUi();
function installedOnOpen () {
A = prompt('Hello'); // or ui.prompt('Hello').getResponseText();
PropertiesService.getScriptProperties().setProperty("key", A);
}
code2.gs:
function sample () {
var A = PropertiesService.getScriptProperties().getProperty("key");
if (A === "123") {
ui.alert('Hello')
}
}
Or, you can also modify as follows. But, in your situation, this might not be required.
function installedOnOpen () {
var ui = DocumentApp.getUi();
var A = ui.prompt('Hello').getResponseText();
if (A === "123") {
ui.alert('Hello');
}
}
2. Install OnOpen event trigger:
In order to run the function of installedOnOpen when the Google Document is opened, please install the OnOpen event trigger to the funciton of installedOnOpen as the installable trigger.
3. Run script:
In your case, there are 2 patterns for running script.
Pattern 1:
Open Google Document.
Pattern 2:
Run installedOnOpen at the script editor.
By above, installedOnOpen is run. And you can see the dialog at Google Document.
Note:
This modification supposes that the function of prompt() returns the value of 123 as the string value.
If you cannot provide the script of prompt(), as a test case, how about modifying from prompt('Hello'); to ui.prompt('Hello').getResponseText();?
References:
Access a variable across multiple script file under a GAS project
Also I think that this thread might be useful for you.
Installable Triggers
If I misunderstood your question and this was not the direction you want, I apologize.
As I can see you define onOpen twice. It does not make sense.
You also don't declare variables and this is reflected in the style of your code. Try declaring the variables and you will realize that your code has no effect.

How do I edit the calling cell of a custom function with google apps script from within the custom function?

After searching through various questions and the documentation I still cannot edit the calling cell of a custom function. According to the docs:
Returns the range of cells that is currently considered active. This
generally means the range that a user has selected in the active
sheet, but in a custom function it refers to the cell being actively
recalculated.
Although this description is for the getActiveRange() function one assumes the getActiveCell() functions in the same way.
I am trying to call a helper function from within my custom function which should return the 'active cell' which, to my knowledge, should be the calling cell of the custom function. Not working code:
// returns the current active (calling) cell for use within custom functions
function callingCell() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getActiveRange()
}
// change background colour of calling cell
function getInfoFromPublicApi(type) {
... // get prices using UrlFetchApp
callingCell().setBackground('green')
return averagePrice(prices, type)
}
I have also tried using the SpreadsheetApp from within the custom function directly, as well as using:
SpreadsheetApp.getActiveSpreadsheet().getActiveCell()
SpreadsheetApp.getActiveSpeadsheet().getActiveRange()
You can go about it two ways. First, set calling cell as a variable. You cannot chain methods together like you're trying to do in the larger function. Or, you can drop the callingCell() function because the information is handled in the event object of onEdit().
Method 1
// returns the current active (calling) cell for use within custom functions
function callingCell() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getActiveRange();
}
// change background colour of calling cell
function getInfoFromPublicApi(type) {
var active = callingCell();
... // get prices using UrlFetchApp
active.setBackground('green')
return averagePrice(prices, type)
}
Method 2
function onEdit(e) {
// Not sure what your `type` param is, so you'd need to test for that somehow.
... // get prices using UrlFetchApp
e.getRange().setBackground('green');
...
}

Calling a Google App Script library from a Google spreadsheet cell

Trying out the new library feature in a Google spreadsheet. I have include a library with the identifier of "Test" and the library implements the function "foo()"
entering =Test.foo() into a spreadsheet cell gives the error : "unknown function name TEST.FOO"
If I create a function in my spreadsheet to wrap the library function:
function foo()
{
return Test.foo();
}
then use =foo() in my speadsheet cell, all is well. Creating wrapper functions for all library functions so they can be used in a spreadsheet cell makes using libraries less than ideal. Is there a way to call a library function from a spreadsheet cell?
There's not currently a way to call these library functions directly as a custom function from a cell. Creating a wrapper, as you've done, is the way to do this currently. If you'd like to be able to call library functions directly as custom functions, please raise that as an issue on the Issue Tracker.
Here is a workaround that allows you to call any library function if you paste in this one generic wrapper function. Then you can call this from the spreadsheet.
For example, if I had a library called MyLib with a function add(x, y) (pretend x is in cell A1 and y is in cell A2) I could call it like this:
=LIB_FUNC("MyLib", "add", A1, A2).
It's a little ugly but at least allows me to only have to paste this one function and then access any library function. Note that this depends on undocumented structure of the "this" object that is in scope when calling the wrapper function. Small chance this could break over time. Might see if I can publish this as an add on.
function LIB_FUNC(libraryName, functionName) {
var result;
var lib = this[libraryName];
var extraArgs = [];
if (lib) {
var func = lib[functionName];
if (func) {
if (arguments.length > 2) {
extraArgs = Array.apply(null, arguments).slice(2);
}
result = func.apply(this, extraArgs);
} else {
throw "No such function: " + functionName;
}
} else {
throw "No such library: " + libraryName;
}
return result;
}