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
}
Related
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).
So, I am working on an MVVM-based core SDK for use any time I am developing some Google Apps Script based software, called OpenSourceSDK. It contain core business logic, including base classes to extend. For example, the file Models/BaseModel.gs in it is defined to be:
class BaseModel {
static FromObject(obj) {
let model = new this()
return model.unmarshal(obj)
}
/**
* Creates a deep-copy of this, as plain Object
*/
marshal() {
return JSON.parse(JSON.stringify(this));
}
/**
* Writes the target object into this and returns this
* #param {*} obj
*/
unmarshal(obj) {
return Object.assign(this, obj);
}
}
I follow the instructions to create this library, and make sure to import it into the project that's using it. When I attempt to use it, things go sideways: I am able to access the functions, but not the classes nor the constants!! I start typing in OpenSourceSDK.BaseModel, and IntelliSense is like "idk what to do". I sanity test via Logger.log(typeof OpenSourceSDK.BaseModel), only to get undefined.
This is unacceptable.
What is the most efficient way to make sure these classes (and constants) are shared from that library?
NOTE: I do not accept the following "solutions":
factory method per class. Why not? Because:
maintenance nightmare. Imagine making any change to the constructor signature of the class. You'd have to update the factory function on every change!
extra boilerplate that is wrapper around something that is so simple.
what happen if we want to extend a base class coming from the library?
going all function-based. That defeats the whole purpose of this project. There's a reason I am doing this MVVM, and hence OOP, style.
In ES6 let, const, and class do not add themselves to the global variable (unlike var and function).
My favorite way of exporting them on Apps Script is to assign all exported objects to globalThis:
class BaseModel { /* [...] */ }
class AnotherType { /* [...] */ }
const ExampleConstant = 420
Object.assign(globalThis, {
BaseModel,
AnotherType,
ExampleConstant,
})
This allows you to choose what to "export" and follows a nice enough syntax.
Update
You can also mimic the individual export syntax with the following function:
function export_(exported) {
const { name } = exported
if (!name)
throw new Error(`To export a function or a class, it needs to have a name.`)
if (name in globalThis)
throw new Error(`A feature with the same name already exists on globalThis.`)
globalThis[name] = exported
return exported
}
And you use it like this:
export_ (class Example {
// [...]
})
Unfortunately the final underscore is necessary to not clash with the keyword and the parenthesis are not my cup of tea (specially the one at the end). But I agree that this would be more maintainable as you don't need to manually update the list.
References
globalThis (MDN)
Object.assign() (MDN)
I was able to get it resolved, but the solution is...hacky.
So, apparently, Google Apps Script exports only what is in globalThis of a project: just the functions and variables. No classes, no constants, ...
Probably has a lot to do with how ES6 works, with its globalThis behavior. One can see that in action, by creating a dummy function, a dummy variable, and a dummy class in their local developer console:
function aFunction() { return 42; }
class AClass {}
var a = 41;
When they log globalThis, they'll find some massive object with a, aFunction in it, but no class:
Also, it turns out that globalThis is immutable, so you can't hack the class in via some IIFE...
The only recourse is to go prepend var [ClassName] = in front of every class that is to be made public. That would assign the [ClassName] to a publicly exposed var.
I can't wait til Google Apps Script comes up with a better way to expose classes!!
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
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 ;-)
I have a large application that needs to ensure that various items are loaded (at different times, not just at startup) before calling other routines that depend on said loaded items. What i find problematic is how my architecture ends up looking to support this: it is either littered with callbacks (and nested callbacks!), or pre populated with dozens of neat little
private function SaveUser_complete(params:ReturnType):void
{
continueOnWithTheRoutineIWasIn();
}
and so forth. Right now the codebase is only perhaps 2500 lines, but it is going to grow to probably around 10k. I just can't see any other way around this, but it seems so wrong (and laborious). Also, i've looked into pureMVC, Cairngorm, and these methods seem equally tedious,except with another layer of abstraction. Any suggestions?
Well asynchronous operations always have this affect on code bases, unfortunately there's not really a lot you can do. If your loading operations form some sort of 'Service' then it would be best to make a IService interface, along with the appropriate MVC Style architecture and use data tokens. Briefly:
//In your command or whatever
var service:IService = model.getService();
var asyncToken:Token = service.someAsyncOperation(commandParams);
//some messaging is used here, 'sendMessage' would be 'sendNotification' in PureMVC
var autoCallBack:Function = function(event:TokenEvent):void
{
sendMessage(workOutMessageNameHere(commandParams), event.token.getResult());
//tidy up listeners and dispose token here
}
asyncToken.addEventListener(TokenEvent.RESULT, autoCallBack, false, 0, true);
Where I have written the words 'workOutMessageNameHere()' I assume is the part you want to automate, you could either have some sort of huge switch, or a map of commandParams (urls or whatever) to message names, either way best get this info from a model (in the same command):
private function workOutMessageNameHere(commandParams):String
{
var model:CallbackModel = frameworkMethodOfRetrivingModels();
return model.getMessageNameForAsyncCommand(commandParams);
}
This should hopefully just leave you with calling the command 'callService' or however you are triggering it, you can configure the callbackMap / switch in code or possibly via parsed XML.
Hope this gets you started, and as I've just realized, is relevant?
EDIT:
Hi, just had another read through of the problem you are trying to solve, and I think you are describing a series of finite states, i.e. a state machine.
It seems as if roughly your sequences are FunctionState -> LoadingState -> ResultState. This might be a better general approach to managing loads of little async 'chains'.
Agreeing with enzuguri. You'll need lots of callbacks no matter what, but if you can define a single interface for all of them and shove the code into controller classes or a service manager and have it all in one place, it won't become overwhelming.
I know what you are going through. Unfortunately I have never seen a good solution. Basically asynchronous code just kind of ends up this way.
One solution algorithm:
static var resourcesNeededAreLoaded:Boolean = false;
static var shouldDoItOnLoad:Boolean = false;
function doSomething()
{
if(resourcesNeededAreLoaded)
{
actuallyDoIt();
}
else
{
shouldDoItOnLoad = true;
loadNeededResource();
}
}
function loadNeededResource()
{
startLoadOfResource(callBackWhenResourceLoaded);
}
function callBackWhenResourceLoaded()
{
resourcesNeededAreLoaded = true;
if(shouldDoItOnLoad)
{
doSomething();
}
}
This kind of pattern allows you to do lazy loading, but you can also force a load when necessary. This general pattern can be abstracted and it tends to work alright. Note: an important part is calling doSomething() from the load callback and not actuallyDoIt() for reasons which will be obvious if you don't want your code to become out-of-sync.
How you abstract the above pattern depends on your specific use case. You could have a single class that manages all resource loading and acquisition and uses a map to manage what is loaded and what isn't and allows the caller to set a callback if the resource isn't available. e.g.
public class ResourceManager
{
private var isResourceLoaded:Object = {};
private var callbackOnLoad:Object = {};
private var resources:Object = {};
public function getResource(resourceId:String, callBack:Function):void
{
if(isResourceLoaded[resourceId])
{
callback(resources[resourceId]);
}
else
{
callbackOnLoad[resourceId] = callBack;
loadResource(resourceId);
}
}
// ... snip the rest since you can work it out ...
}
I would probably use events and not callbacks but that is up to you. Sometimes a central class managing all resources isn't possible in which case you might want to pass a loading proxy to an object that is capable of managing the algorithm.
public class NeedsToLoad
{
public var asyncLoader:AsyncLoaderClass;
public function doSomething():void
{
asyncLoader.execute(resourceId, actuallyDoIt);
}
public function actuallyDoIt ():void { }
}
public class AsyncLoaderClass
{
/* vars like original algorithm */
public function execute(resourceId:String, callback:Function):void
{
if(isResourceLoaded)
{
callback();
}
else
{
loadResource(resourceId);
}
}
/* implements the rest of the original algorithm */
}
Again, it isn't hard to change the above from working with callbacks to events (which I would prefer in practise but it is harder to write short example code for that).
It is important to see how the above two abstract approaches merely encapsulate the original algorithm. That way you can tailor an approach that suites your needs.
The main determinants in your final abstraction will depend on:
Who knows the state of resources ... the calling context or the service abstraction?
Do you need a central place to acquire resources from ... and the hassle of making this central place available all throughout your program (ugh ... Singletons)
How complicated really is the loading necessities of your program? (e.g. it is possible to write this abstraction in such a way that a function will not be executed until a list of resources are available).
In one of my project, I build custom loader which was basically wrapper class. I was sending it Array of elements to load and wait for either complete or failed event(further I modified it and added priority also). So I didn't have to add so many handlers for all resources.
You just need to monitor which all resources has been downloaded and when all resources complete, dispatch a custom event-resourceDownloaded or else resourcesFailed.
You can also put a flag with every resource saying it is necessary or compulsory or not, If not compulsory, don't throw failed event on failing of that resource and continue monitoring other resources!
Now with priority, you can have bunch of file which you want to display first, display and continue loading other resources in background.
You can do this same and believe me you'll enjoy using it!!
You can check the Masapi framework to see if it fulfills your needs.
You can also investigate the source code to learn how they approached the problem.
http://code.google.com/p/masapi/
It's well written and maintained. I used it successfully in a desktop RSS client I developed with Air.
It worked very well assuming you pay attention to the overhead while loading too many resources in parallel.