GlobalThis
An often misunderstood topic in JavaScript is the use of global objects. This article will seek to make some clarifications on this topic, show how aspects of this issue applies within EA JavaScript scripting, and describe one of the building blocks on which the Peso framework is built on.
In Javascript there is an implicit object hierarchy where every object is related to another object through a parent relationship. The standard way of referring to the parent (or enclosing) object is through the this reserved word. At the top of the object hierarchy the root object can be found.
Depending on the JavaScript environment, there may be well known names available which refer to this root object. For web browsers you can often rely on window, self or frames, and on Node.js you can refer to global. However, in EA, there is no well known name for the root object.
At this point, you might well ask
For most purposes in EA knowing the root object is not important. You can get a lot done without needing to know about it. If however you are doing something a bit more exotic, like building a scripting framework, then it makes sense to be able to place components in a well known and global location.
There doesn't seem to be an idiomatic way of describing these concepts, and it is usual to refer to the root object as the global object as well which can be confusing. In this article I have tried to be consistent and I will refer to the term root object when referring to the object at the top of the JavaScript object hierarchy, and I will refer to global variables/objects to those objects accessible in a global context (i.e. attributes on the root object).
The above code fragment produces the following output
Note that Repository, Session and App are all properties of the root object and hence global variables. As an aside, note that 'Authors', 'BatchAppend' are Repository attributes that have also been defined for the root object. This could lead us to conclude that 'this' is equivalent to the Repository object, however 'Session' and 'App' are not available on the Repository object, hence they are not the same object.
Using 'this' to refer to the root object in EA works for the normal traditional JavaScript scripting - in previous versions as well as the current version. However, in the context of Model Add-Ins (which was made available in version 15) the same mechanism does not work. The reason for this is that the entry point (the JavascriptAddin stereotyped element) is already in the scope of an object, and hence 'this' will correspond to the JavascriptAddin object.
Note, that there is currently discussions in the JavaScript community to have a standard variable name called 'globalThis' to refer to the root object - see link for further information. This would serve our purposes, but it is not available as yet within EA.
Luckily there is an unorthodox way (my qualifier) to get a reference to the root object by using a side effect of manually declaring a 'Function' object. Functions that have been created using the function constructor run in the global scope. Hence returning 'this' from it, will return a reference to the root object. The article here gives a thorough explanation of the issue.
Below is an example illustrating the mechanism, and how we could use it to define our own 'globalThis' global variable to refer to the root object.
Having gotten a reference to the root object, here's a few things we could do with it, and which are baked into Peso.
In Javascript there is an implicit object hierarchy where every object is related to another object through a parent relationship. The standard way of referring to the parent (or enclosing) object is through the this reserved word. At the top of the object hierarchy the root object can be found.
Depending on the JavaScript environment, there may be well known names available which refer to this root object. For web browsers you can often rely on window, self or frames, and on Node.js you can refer to global. However, in EA, there is no well known name for the root object.
At this point, you might well ask
Ok, but how is this significant?
For most purposes in EA knowing the root object is not important. You can get a lot done without needing to know about it. If however you are doing something a bit more exotic, like building a scripting framework, then it makes sense to be able to place components in a well known and global location.
Root Objects and Global Variables
At this point we need to make sure that it is clear that the root object in JavaScript actually serves two purposes. As described previously, the root object fills the top level in the JavaScript object hierarchy, but also any properties defined for the root object are accessible globally, without referring to this or to the root object. These attributes in effect become global variables, available in any scope. An example within EA, is the Repository variable - it is accessible in every scope. It is defined as an attribute on the root object, but note that it is not the root object itself.There doesn't seem to be an idiomatic way of describing these concepts, and it is usual to refer to the root object as the global object as well which can be confusing. In this article I have tried to be consistent and I will refer to the term root object when referring to the object at the top of the JavaScript object hierarchy, and I will refer to global variables/objects to those objects accessible in a global context (i.e. attributes on the root object).
Access to the Root Object in EA
As discussed previously there isn't a well known name for the root object within EA. However, the usual way to get access to the root object is to refer to 'this' on script entry (i.e. before entry into any other scope), and 'this' will in fact refer to the root object. For example, the following code fragment will display all the global variables.Repository.EnsureOutputVisible("Script"); for (var property in this) { Repository.WriteOutput('Script', property, 0); }
The above code fragment produces the following output
Session Repository property App Authors BatchAppend Clients ConnectionString <output continues...>
Note that Repository, Session and App are all properties of the root object and hence global variables. As an aside, note that 'Authors', 'BatchAppend' are Repository attributes that have also been defined for the root object. This could lead us to conclude that 'this' is equivalent to the Repository object, however 'Session' and 'App' are not available on the Repository object, hence they are not the same object.
Using 'this' to refer to the root object in EA works for the normal traditional JavaScript scripting - in previous versions as well as the current version. However, in the context of Model Add-Ins (which was made available in version 15) the same mechanism does not work. The reason for this is that the entry point (the JavascriptAddin stereotyped element) is already in the scope of an object, and hence 'this' will correspond to the JavascriptAddin object.
Access to the Root Object in EA Model Add-in
From my experiments, there doesn't seem to be a way to execute code outside of an object scope within the Model Add-in functionality. Given that using 'this' in the normal way, is not viable and that there isn't a well known name for the root object then we need to come up with another way of getting to it.Note, that there is currently discussions in the JavaScript community to have a standard variable name called 'globalThis' to refer to the root object - see link for further information. This would serve our purposes, but it is not available as yet within EA.
Luckily there is an unorthodox way (my qualifier) to get a reference to the root object by using a side effect of manually declaring a 'Function' object. Functions that have been created using the function constructor run in the global scope. Hence returning 'this' from it, will return a reference to the root object. The article here gives a thorough explanation of the issue.
Below is an example illustrating the mechanism, and how we could use it to define our own 'globalThis' global variable to refer to the root object.
function getGlobalObjectReference() { return (new Function('return this;')()); } var globalObjectReference = getGlobalObjectReference(); globalObjectReference.globalThis = globalObjectReference;
globalThis What Is It Good For?!
Well hopefully, not nothing.Having gotten a reference to the root object, here's a few things we could do with it, and which are baked into Peso.
- Define a globally accessible short form debugging function which outputs to the script output tab, without having to write all the code to manage tabs, whether they are visible, etc. Below is example code on how to do this.
globalThis.dbg = function (str) { Repository.CreateOutputTab('Test'); Repository.EnsureOutputVisible('Test'); Repository.WriteOutput('Test', str, 0); }; globalThis.log = globalThis.dbg; globalThis.print = globalThis.dbg;
Note, that 'log' and 'print' have also been added as common synonyms. - Define a global object to keep track of module namespaces. An explanation of this feature in Peso will be described in a future blog post.
- Define mock, or functional equivalents for other usually well known objects. For example, 'console' or 'document'. These would to assist porting of third party JavaScript modules to run within EA that have dependencies on these names. How to port third party JavaScript modules to EA will be described in a future blog post.
[Originally posted here.]
Comments
Post a Comment