I have a Web App I want to use as a library for another script I am writing and it will eventually be semi-public. I have made most of my relevant internal functions private the only way I know how - by appending an underscore to the function name.
However, this doesn't work for special reserved function names such as onOpen or onEdit. I know it's quite nitpicky of me, as anyone attempting to run these functions will just get errors anyway, but just curious if there is a way to specify these functions as private so they don't appear in the calling editor's auto-complete?
If you are using the default runtime, V8, instead of using
function onEdit(){
}
use
const onEdit = () => {
}
Apply the same any for any other simple trigger that you want to hide, as onOpen, onSelectionChange, doGet and doPost.
Instead of const you might use let, but do not use var.
Instead of () => {} y you might use function () or the name of a private function i.e. myFunction_ (note that the parenthesis aren't included).
Related
I'm trying to add libraries to my google sheets script. I can add them, but trying to use them in my sheets, I get an "unknown function" error.
For example, in my project's app script, I've added LodashGS v6. I can click on the library options and select 'open in a new tab' and can see the library's functions, in this case, LodashGS has a function called load, which loads the full lodash library. When I use LodashGS in the sheets, I get the "unknown function" error.
I've added const _ = LodashGS.load(); into the Code.gs file, which should give me access to the underscore in my sheets. But using the _ will give the the same error.
How can I access these libraries. I feel like I'm missing a minor detail but can't see to find it.
Here's a link to the google sheet I'm using - https://docs.google.com/spreadsheets/d/1vCkvohhecgSNEt5ybznDIRyzT70lDLz3TWxU-i2VwgQ/edit?usp=sharing
Issue:
You cannot use Apps Script methods (including from imported libraries) directly as custom functions. You have to define a function in Apps Script in order to use it as a custom function.
Also, you can access functions, not variables. For example, when doing const _ = LodashGS.load();, _ is a variable (actually a constant! But the point remains) and cannot be used as a custom function. Hence, _.now() is not recognized.
Custom function are actually strict with the function syntax used. As you can see in the naming guidelines:
The name of a custom function must be declared with the syntax function myFunction(), not var myFunction = new Function().
(Arrow function syntax apparently is also allowed, so this documentation could arguably be updated, but the main point remains).
Solution:
Wrap your methods inside a function if you want to use it as a custom function. You can either use the function keyword, as you already did for getTime(), or arrow functions, for example:
const getTime = () => LodashGS.load().now();
Note:
Please take into account that the name of a custom function cannot end with an underscore (_) (see the previously referenced naming guidelines).
I'm building a library within Apps Script and would like to make it pretty self sustaining. Essentially, if someone wants to use this Lib in their Google Sheet, they'd need simply to put in the following code in their Sheets Script
function onOpen(){
myLib.initialize();
}
And this would set up the sheet as needed. One of the things I do here is to actually add a menu item in the Sheet itself. Therefore, in my Library code, I'll have something like;
function initialize(){
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('First item', 'someCallback')
.addToUi();
}
The problem with this approach is that when the menu item is clicked, the "someCallback" function cannot be found. This makes sense since it resides in a separate library. It should be called using dot notation like this: myLib.someCallback
Is there some way that I can get the Library Identifier at run time so that I can create the string required to create the menu?
I can do a few things which are not my first choices such as:
pass the "myLib" string to the initialize function
type in the "myLib" to the addItem() function directly, but I"m thinking this is not good practice, what if the end user changes the identifier from myLib to something else.
Maybe there's some Runtime way that I can get the Library Identifier?
Answer:
Unfortunately at the moment this isn't possible to do.
More Information:
I tested out a few things using both the this keyword and the eval() function in order to either dynamically get the redefined identifier of myLib or calling someCallback directly in the initialize function, however even embedding the callback function inside initialize:
funciton initialize() {
var cb = function someCallback() {
// do stuff
};
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('First item', 'cb')
.addToUi();
or:
function initialize() {
function someCallback() {
//do stuff
}
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('First item', 'someCallback')
.addToUi();
}
throw the same error with either cb or someCallback not defined.
Problems with this:
To answer your direct question
Maybe there's some Runtime way that I can get the Library Identifier?
The answer is yes. Kind of.
The this keyword is pretty powerful, and at runtime you can use it to get the name of the user-set identifier in the following way:
function onOpen(){
myLib.initialize(this);
}
And in myLib.initialize():
function initialize(name) {
console.log(Object.keys(name));
}
The console showing: ['myLib', 'onOpen']
This issue is, is that while the names of imported libraries come before the list of functions defined in the user's script, if more than one library has been imported you have no way of telling which of the keys refers to your library, and which refer to the others. Resultantly just calling name[0] won't cut it.
Doing Some Digging:
I took to Google's Issue Tracker and discovered that there are some feature requests which have very similar use cases to yours:
AddMenu should be able to use anonymous functions or at least pass parameters
AddMenu should be able associate an object method to a menu entry, not just a global function
While the use-cases aren't exactly the same, the underlying issue on both is that anonymous functions nor parameters can be passed to libraries that use the UI methods of Apps Script.
There is also this related feature request which asks to implement a catch-all style method which runs a method if a library method which doesn't exist is called:
_noSuchMethod_ for Library ( undefined method in google apps script )
What to do:
Normally, I would suggest following the Issue Tracker links and clicking the star. In this case, however, as your use case isn't exactly defined in the scope of the aforelinked feature requests, I would urge you to use this link to file an Apps Script feature request and detail all the information you put here in your question.
As far as workarounds go; the best thing you can do in the meantime is to document that the initialize() function needs to be passed the identifying name of the library. It's not an elegant solution by any standard, but given the complexity of this and the feature requests that are yet to be realised this may be the easiest option.
It might be possible to loop through the Object.keys(this) array but I feel like this is a very hacky workaround which could introduce more problems than necessary (especially if, for example, another library creator also happens to have a function called someCallback...)
References:
The JavaScript this keyword - w3schools
Google's Issue Tracker
The GAS editor is able to Run function and Debug function, but only if the function isn't private (i.e. doesn't have _ appended to the end of the function name). This is a problem, because if the function name isn't private, it's publicly exposed when the script is published as an add-on.
The workaround is the remove the underscore, run/debug the function, and then re-append the underscore. This is not hard, but it smells. When I am forced to do this, I am convinced I am not doing something the correct way. What is the correct way?
When I'm doing Apps Script development in the web interface (which imposes these limits) I tend to collect some "test" functions which I use during development. In my case I'm usually trying to feed different values to functions which take arguments, but you could do the same. As a bonus, it's easy to remove these helper functions when you publish the script since you don't have to around changing the name of things.
A silly example:
// TODO(jjjjoe) remove these before publishing
function testFoo() { _foo(); }
function testBar() { _bar(); }
function testAll() {
_foo(); _bar();
}
function _foo() {
// Spend The Most Curious Thing's money
}
function _bar() {
// Send lots of emails
}
I have a script that I use as a library loading it in the spreadsheets I need.
When I add it to a spreadsheet, I define an onEdit and onLoad function that are calling the library's corresponding methods.
I need my individual sheets that are using the library, to define a global variable that should be available to the library, that variable may not be set in a sheet's cell.
I tried setting
var previd = "myid";
at the beginning of the spreadsheet script but that doesn't seem to do the trick.
How may I solve that ?
The "global scope" is not shared between scripts, it is actually a "script scope".
All variables needed for your library functions must be passed as parameters to these functions. If you make many functions call with the same parameters you should consider wrapping them in an object. Like some Apps Script built-in functions have optArguments.
You may also have a setParameters function on your library to pass these variables once (per runtime) and have them available to next library calls. Something like this:
//on the library script
var clientParams = null;
function setParameters(parameters) { clientParams = parameters; }
function exampleFunc() { return clientParams.previd; }
//on your client script
library.setParameters({previd:'myid', otherParam:'example'});
//then you can call
library.exampleFunc();
I am using sort of wrapping functions to add widgets to UiApp, like the sample below
function newButton(text, handler, callBack) {
var app = UiApp.getActiveApplication();
return app.createButton(text).addClickHandler(app.createServerClickHandler(handler).addCallbackElement(callBack));
}
it works fine when used within the same script like below
panel.add(newButton("Submit", "myHandler", panel));
but when including newButton function within a Library (ex. myLib) and it's called as myLib.newButton, I would get an error:
"Error encountered: Unknown macro myLib.myHandler"
panel.add(myLib.newButton("Submit", "myHandler", panel));
Any way of avoiding this problem, while keeping myHandler in the current script (not in myLib)?
Thanks, Fausto
Yes it can.
In your current script (wherein you imported the reference to the library)
create;
function myLib.myHandler(){
}
As your handler (string) keep passing the name 'myHandler'. The above function will be called by the button created in your library script.
It seems like apps script is using this namespace structure to avoid name collision.
You should keep in mind that this could purposely be not-documented by google for a reason. Maybe this current structure could be changed in the future. Or maybe im just being paranoid ;-)