Where can I read documentation concerning the execution order rules for GS files?
To dimension the problem I created two trivial objects, each in their own file.
1_File.gs
var ObjB = new Object();
ObjB.sayName = "[" + ObjA.sayName + "]";
0_File.gs
var ObjA = new Object();
ObjA.sayName = " I'm A ";
A call such as ...
Logger.log(ObjA.sayName + " : " + ObjB.sayName);
... gets the error ...
TypeError: Cannot read property "sayName" from undefined.
If I move the code from 1_File.gs into 0_File.gs, and vice versa, then there is no error and the log shows correctly ...
I'm A : [ I'm A ]
Renaming 0_File.gs to 2_File.gs doesn't affect execution order either, so I assume that order depends on which file gets created first.
Is there no concept of "include" or "import" that would allow me to make order of execution explicit?
Where can I read documentation concerning the execution order rules for GS files?
There is no such documentation and I think will not be any time published. In similar way, an initialization order of the static variables in C++ is also undefined and depends on compiler/linker.
Is there no concept of "include" or "import" that would allow me to make order of execution explicit?
Yes, there is no "includes", "imports" and even "modules", but there are libraries.
Also there is a workaround by using a closure. Bellow is a sample code. By executing the test function the log contains c.d. The idea is to have in all gs files a function started with init. In these functions all global variables are instanced. The anonymous closure is executed during the Code.gs file instancing and calls all "init" functions of all gs files.
Code.gs
var c;
function callAllInits_() {
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var funcName = keys[i];
if (funcName.indexOf("init") == 0) {
this[funcName].call(this);
}
}
}
(function() {
callAllInits_();
c = { value : 'c.' + d.value };
})();
function test() {
Logger.log(c.value);
}
d.gs
var d;
function initD() {
d = { value : 'd' };
};
I tackled this problem by creating a class in each file and making sure that each class is instantiated in the original Code.gs (which I renamed to _init.gs). Instantiating each class acts as a form of include and makes sure everything is in place before executing anything.
_init.gs:
// These instances can now be referred to in all other files
var Abc = new _Abc();
var Menu = new _Menu();
var Xyz = new _Xyz();
var Etc = new _Etc();
// We need the global context (this) in order to dynamically add functions to it
Menu.createGlobalFunctions(this);
function onInstall(e) {
onOpen(e);
}
function onOpen(e) {
Menu.build();
}
And classes usually look like this:
menu.gs:
function _Menu() {
this.build = function() {
...
}
...
}
If you have more than one level of inheritance, you need to give the init functions names like init000Foo, init010Bar, and init020Baz, and then sort the init functions by name before executing. This will ensure init000Foo gets evaluated first, then Bar, then Baz.
function callAllInits() {
var keys = Object.keys(this);
var inits = new Array();
for (var i = 0; i < keys.length; i += 1) {
var funcName = keys[i];
if (funcName.indexOf("init") == 0) {
inits.push(funcName);
}
}
inits.sort();
for (var i = 0; i < inits.length; i += 1) {
// To see init order:
// Logger.log("Initializing " + inits[i]);
this[inits[i]].call(this);
}
}
The other answers (i.e., don't write any top-level code which references objects in other files) describe the ideal way to avoid this problem. However, if you've already written a lot of code and rewriting it is not feasible, there is a workaround:
Google App Script appears to load code files in the order they were created. The oldest file first, followed by the next, and the most recently created file last. This is the order displayed in the editor when "Sort files alphabetically" is unchecked.
Thus, if you have the files in this order:
Code.gs
1_File.gs (depends on 0_File.gs)
0_File.gs
An easy fix is to make a copy of 1_File.gs and then delete the original, effectively moving it to the end of the list.
Click the triangle next to 1_File.gs and select "Make a copy"
Code.gs
1_File.gs
0_File.gs
1_File copy.gs
Click the triangle next to 1_File.gs and select "Delete"
Code.gs
0_File.gs
1_File copy.gs
Click the triangle next to 1_File copy.gs and select "Rename", then remove the " copy" from the end.
Code.gs
0_File.gs
1_File.gs
Now 0_File.gs is loaded before 1_File.gs.
This works for me as of December 2021. Quite likely, the other answers are outdated.
You can easily fix this. When you look at the scripts in the "Files" section of the web editor, you see they have an order. Files are evaluated in the order they appear there. Clicking on the three dots to the right of a file name brings up a menu that allows you to move a file up or down.
There is no such order in Google Apps Script. It purely depends on where you have these objects declared and how your function is invoked.
Can you explain a bit about how and when your Logger.log() code will be invoked.
Also, when do you declare your objects objA and objB ?
These will help us provide a better answer
here is how I would do this...
main
function include(filename) {
return ContentService.createTextOutput(filename);
}
function main() {
include('Obj A');
include('Obj B');
Logger.log(ObjA.sayName + " : " + ObjB.sayName);
}
Obj A
var ObjA = new Object();
ObjA.sayName = " I'm A ";
Obj B
var ObjB = new Object();
ObjB.sayName = "[" + ObjA.sayName + "]";
Related
I'm having real trouble trying to access the symbol dynamically, I have 9 buttons that all call this method, and they pass in their location (tl, t, tr, etc.) I've tried this method before on another program and it works without a problem, but in this program it fails.
I am attempting to access a symbol call s_tl (example location), but all I'm getting is undefined (see results).
function turn(btn : String):Function {
return function(e:MouseEvent) {
var players_turn : int;
var chosen : String = "s_" + btn;
trace(this);
trace(this[chosen]);
trace(chosen);
trace(this[chosen]);
// if crosses turn 0 else 1
if (s_c.currentFrame == 1) {
players_turn = 0;
} else {
players_turn = 1;
}
// check who's turn it is if it's been pressed before
if (players_turn == 0 && this[chosen].visible == false) {
this[chosen].gotoAndStop(1);
this[chosen].visible = true;
} else {
this[chosen].gotoAndStop(2);
this[chosen].visible = true;
}
};
}
Results:
[object global]
undefined
s_br
undefined
TypeError: Error #1010: A term is undefined and has no properties.
at MethodInfo-6()
Your problem is the bad code style. You define unnamed unbind function inside function turn() and that's where the root of your problem is. Unbind function exist, as your trace shows, in global addressing context and, unlike function turn(), is not bind to any specific display object. Your buttons probably exist on the same addressing context with turn(). Argument btn is available inside unnamed function because ECMA standard instructs so (if function A creates function B then local variables, including arguments, of A are available as local variables in B), but it is a very very very bad practice that makes code messy and induce headaches.
Please explain what you tried to achieve with that code so we could untangle it and rewrite in not-so-twisted way.
Okey, I basically figured you're doing Tic Tac Toe. Now, guideline. A cell must contain 3 frames: 1st frame for the button graphics, 2nd and 3rd for X and O. Name them your way: s_1, s_2, etc.
for (var i:int = 1; i < 10; i++)
{
var aCell:MovieClip = getChildByName("s_" + i) as MovieClip;
aCell.addEventListener(MouseEvent.CLICK, onTic);
}
function onTic(e:MouseEvent):void
{
var playersTurn:int = s_c.currentFrame;
var aCell:MovieClip = e.currentTarget as MovieClip;
trace(aCell.name);
// Now, the magic.
aCell.gotoAndStop(playersTurn + 1);
aCell.removeEventListener(MouseEvent.CLICK, onTic);
}
I'm attempting to gradually refactor existing code. I have a set of functions that are defined, and only differ by one of the internal arguments:
function loadGame1():void
{
loadGame("save1");
}
function loadGame2():void
{
loadGame("save2");
}
function loadGame3():void
{
loadGame("save3");
}
//... snip many, many lines
// Note- I cannot pass function arguments at this time!
picker(loadGame1, loadGame2, loadGame3 ...);
I'm trying to refactor at least part of this (I can't completely replace the whole thing yet, too many interdependencies).
Basically, I want to be able to generate a big set of functions with the difference between the functions being a internal parameter:
var fNames:Array = new Array("save1", "save2", "save3");
var funcs:Array = new Array();
for (var i = 0; i < fNames.length; i += 1)
{
trace("Creating function with indice = ", i);
funcs.push(
function() : void
{
saveGame(fNames[i]);
}
)
}
picker(funcs[0], funcs[1], funcs[2] ...);
However, as I understand it, closure is causing the state of i to be maintained beyond the scope of the for loop, and any attempt to call any of the generated functions is failing with an out-of-bounds error, which is what you would expect given that i will reach fNames.size + 1 before i < fNames.size evaluates to false.
So, basically, given that I need to generate functions that are passed as arguments to a pre-existing function that I cannot change currently. How can I dynamically generate these functions?
Try to use IIFE:
for (var i = 0; i < fNames.length; i += 1)
{
(function(i){
trace("Creating function with indice = ", i);
funcs.push(
function() : void
{
saveGame(fNames[i]);
}
)
})(i);
}
I have tried numerous iterations of the code below, with limited success. The logger seems to show the commands correctly, but the form doesn't seem to EXECUTE the commands. After executing, the form just generates a single non defined checkbox.
Given that I have to repeat this checkbox question multiple times with slightly different question phrasing, I'm trying to reduce my code footprint, and hopefully become more efficient.
Here is the snippet of code I have that's failing:
var storerangestart = 9901;
var storerangeend = 9999;
page402_cbitem1.setTitle('What stores do you have allocated for this project?');
var page402array = 'page402_cbitem1.setChoices([\n';
for (var i = storerangestart; i < storerangeend; i++) {
var storerangecurrent = i + "";
page402array += 'page402_cbitem1.createChoice(' + storerangecurrent + '),\n';
}
page402array += 'page402_cbitem1.createChoice(' + storerangeend + ')\n]);';
Logger.log(page402array);
page402array();
The Logger isn't showing you commands, it's showing you a string. Your code has created a string that contains text that looks like code, but there is no way to execute the content of that string directly in Google Apps Script.
You've got a good idea - you're just stuck trying to manhandle the wrong object types.
Look at the definition of CheckboxItem.setChoices(), you'll see it takes an array of choices as a parameter. The example code may have thrown you because it created the item elements inside the .setChoices() method call:
item.setChoices([
item.createChoice('Cats'),
item.createChoice('Dogs')
]);
You could realize the same result this way:
var choices = []; // create a new array of choices
choices.push( item.createChoice('Cats') ); // Add 'Cats'
choices.push( item.createChoice('Dogs') ); // Add 'Dogs'
item.setChoices( choices ); // set the array of choices
That sets up the opportunity for the loop that you're thinking about. Instead of building a string that contains code, you need to call the various object methods to first build up an array of choices, and then to assign that array as the choices for your form item:
var storerangestart = 9901;
var storerangeend = 9999;
page402_cbitem1.setTitle('What stores do you have allocated for this project?');
var page402array = [];
for (var i = storerangestart; i < storerangeend; i++) {
var storerangecurrent = i + "";
page402array.push( page402_cbitem1.createChoice( storerangecurrent ) );
}
Logger.log( page402array );
page402_cbitem1.setChoices( page402array );
I want to get all arguments names of a function inside the function
example:
function fct(var1:string,var2:string){
var names:Array=...
trace(names);
}
must trace : var1,var2
Thanks!
Simply put, this is not possible. The closest you can get is the argument number and value. See below:
function fct( ... args ):void {
for ( var v:Object in args ) {
trace( v + ": " + args[v] );
}
}
var str1:String = "this is a test";
var str2:String = "this is another test";
fct( str1, str2 );
//output
//0: this is a test
//1: this is another test
For future reference, you can use ... + a variable name to allow for as many arguments as you need. Regardless, you should just need to access args[ INDEX ] rather than the actual variable name, which you wouldn't be able to access anyway because there would be no way to apply scope (such as variableName[ "propertyName" ])
It is impossible like native method, but you can use metadata tag to set arguments names. I create simple example. But i don't understand how it can help you in real projects:
[Arguments(param1="arg1",param2="arg2")]
public function test(arg1:Number, arg2:Number):void {
var desc_xml:XML = describeType(Object(this).constructor);
var metas_xml:XMLList = desc_xml.factory.method.(#name == "test");
var args_xml:XMLList = metas_xml.metadata.(#name == "Arguments");
for each (var argx:XML in args_xml.arg)
{
trace(argx.#value.toXMLString());
}
};
I use flex 4.6. Don't forget add each existing Metadata tags to the compiler argument with “-keep-as3-metadata+=Arguments”. It need for compile release versions.
Method UiInstance.getElementById(ID) always returns GenericWidget object, even if ID does not exist.
Is there some way how to find out that returned object does not exist in my app, or check whether UI contains object with given ID?
Solution for UI created with GUI builder:
function getSafeElement(app, txtID) {
var elem = app.getElementById(txtID);
var bExists = elem != null && Object.keys(elem).length < 100;
return bExists ? elem : null;
}
It returns null if ID does not exist. I didn't test all widgets for keys length boundary, so be careful and test it with your GUI.
EDIT: This solution works only within doGet() function. It does not work in server handlers, so in this case use it in combination with #corey-g answer.
This will only work in the same execution that you created the widget in, and not in a subsequent event handler where you retrieve the widget, because in that case everything is a GenericWidget whether or not it exists.
You can see for yourself that the solution fails:
function doGet() {
var app = UiApp.createApplication();
app.add(app.createButton().setId("control").addClickHandler(
app.createServerHandler("clicked")));
app.add(app.createLabel(exists(app)));
return app;
}
function clicked() {
var app = UiApp.getActiveApplication();
app.add(app.createLabel(exists(app)));
return app;
}
function exists(app) {
var control = app.getElementById("control");
return control != null && Object.keys(control).length < 100;
}
The app will first print 'true', but on the click handler it will print 'false' for the same widget.
This is by design; a GenericWidget is a "pointer" of sorts to a widget in the browser. We don't keep track of what widgets you have created, to reduce data transfer and latency between the browser and your script (otherwise we'd have to send up a long list of what widgets exist on every event handler). You are supposed to keep track of what you've created and only "ask" for widgets that you already know exist (and that you already know the "real" type of).
If you really want to keep track of what widgets exist, you have two main options. The first is to log entries into ScriptDb as you create widgets, and then look them up afterwards. Something like this:
function doGet() {
var app = UiApp.createApplication();
var db = ScriptDb.getMyDb();
// You'd need to clear out old entries here... ignoring that for now
app.add(app.createButton().setId('foo')
.addClickHandler(app.createServerHandler("clicked")));
db.save({id: 'foo', type: 'button'});
app.add(app.createButton().setId('bar'));
db.save({id: 'bar', type: 'button'});
return app
}
Then in a handler you can look up what's there:
function clicked() {
var db = ScriptDb.getMyDb();
var widgets = db.query({}); // all widgets
var button = db.query({type: 'button'}); // all buttons
var foo = db.query({id: 'foo'}); // widget with id foo
}
Alternatively, you can do this purely in UiApp by making use of tags
function doGet() {
var app = UiApp.createApplication();
var root = app.createFlowPanel(); // need a root panel
// tag just needs to exist; value is irrelevant.
var button1 = app.createButton().setId('button1').setTag("");
var button2 = app.createButton().setId('button2').setTag("");
// Add root as a callback element to any server handler
// that needs to know if widgets exist
button1.addClickHandler(app.createServerHandler("clicked")
.addCallbackElement(root));
root.add(button1).add(button2);
app.add(root);
return app;
}
function clicked(e) {
throw "\n" +
"button1 " + (e.parameter["button1_tag"] === "") + "\n" +
"button2 " + (e.parameter["button2_tag"] === "") + "\n" +
"button3 " + (e.parameter["button3_tag"] === "");
}
This will throw:
button1 true
button2 true
button3 false
because buttons 1 and 2 exist but 3 doesn't. You can get fancier by storing the type in the tag, but this suffices to check for widget existence. It works because all children of the root get added as callback elements, and the tags for all callback elements are sent up with the handler. Note that this is as expensive as it sounds and for an app with a huge amount of widgets could potentially impact performance, although it's probably ok in many cases especially if you only add the root as a callback element to handlers that actually need to verify the existence of arbitrary widgets.
My initial solution is wrong, because it returns false exist controls.
A solution, based on Corey's answer, is to add the setTag("") method and here is ready to use code. It is suitable for event handlers only, because uses tags.
function doGet() {
var app = UiApp.createApplication();
var btn01 = app.createButton("control01").setId("control01").setTag("");
var btn02 = app.createButton("control02").setId("control02").setTag("");
var handler = app.createServerHandler("clicked");
handler.addCallbackElement(btn01);
handler.addCallbackElement(btn02);
btn01.addClickHandler(handler);
btn02.addClickHandler(handler);
app.add(btn01);
app.add(btn02);
return app;
}
function clicked(e) {
var app = UiApp.getActiveApplication();
app.add(app.createLabel("control01 - " + controlExists(e, "control01")));
app.add(app.createLabel("control02 - " + controlExists(e, "control02")));
app.add(app.createLabel("fake - " + controlExists(e, "fake")));
return app;
}
function controlExists(e, controlName) {
return e.parameter[controlName + "_tag"] != null;
}