Porting your InDesign Script into IdExtenso — Step 3
September 24, 2021 | Tips | en
In this episode, you will learn how to arrange your code so that it fits into IdExtenso's “modular space.” Modularity is a key term when it comes to maintaining a large-scale project. Even if our sample script is not in itself of pharaonic complexity, we will transform it, as an exercise, into a pure module…
Preamble
Here is a picture of our code as we left it in the previous step:
(1) is the INCLUDING
stage.
(2) is the LOADING
stage.
(3) declares and implements, as a single function, the CORE ROUTINE
.
(4) executes the function (applying some particular settings.)
(5) unloads IdExtenso (and shows the log file.)
What you can notice is steps (3) and (4) are somehow disconnected from the framework. Of course the pathNodes
function invokes various IdExtenso methods ($$.trace
, $$.parentSpread
, $$.JSON
…), so it already depends on it. But it is not a part of it, it is not actually known and integrated as a $$ component.
Note. — At this point, the pathNodes
function belongs to the [[global]]
scope, its reference is outside $$.
New users of the framework usually stop at this point, and that's fine, because they only wish to access certain features and use IdExtenso as a library. Many InDesign scripts rely on a single JSX
file, written from scratch with a limited task in mind. Those scripts have no dependencies, they work in a well-defined, rigid environment and are not intended to evolve much. Since they only contain a few hundred lines of code or so, maintaining and debugging the program is relatively easy. You don't have to manage functional interactions between many components.
However, at some level of development, you may need more modularity, that is, distinct components that communicate through a well-defined API. For example, you will quickly realize that your core routine should not have to validate user options. Its job is to take these parameters for a specific task, not to deal with their validity (or storage.) Likewise, the main function should not have to check whether the InDesign context (active document, selection, etc.) is compatible with its execution. These questions could be addressed upstream, so that the process is only executed with definite, valid targets and arguments.
Our roadmap today is to restructure “PathNodes” (the original core routine) as a true $$ component and to define the various methods it should expose. Schematically, we shall end up with the following scenario:
The main change is inserted between the INCLUDING STAGE
(1) and the LOADING STAGE
(3), in a region that still allows you to define new $$ components. The purpose of (2) is to transform our original function into a full module ($$.PathNodes
), exposing its own interface. Then, (4) and (5) are just commands addressed to that module: the first applies some arbitrary settings (that's a temporary simplification), the second runs the InDesign process.
Note. — Settings management and user interface will be deepened in next episodes.
Our Base Example (Version 3)
Before entering the code, you have to familiarize yourself with a few syntactic rules uniformly used in the framework. As long as $$.load()
is not executed, you can declare personal modules using the generic template
// All the lines below form a single command (without ';') eval(__(MODULE, $$, 'MyModuleName', <YYMMDD>, 'myAutomaticMethod')) [PRIVATE] ({ // private stuff }) [PUBLIC] ({ // public stuff (API) })
• The special keywords MODULE
, PRIVATE
, PUBLIC
(and some others we don't mention here) are purely syntactical and will remain available until $$ is loaded.
• The string 'MyModuleName'
is the name of your module, in CamelCase form (recommended.) It must be unique in the scope of its parent, usually $$ (the root module), specified as 2nd argument of the template.
• <YYMMDD>
denotes a date, formatted as a 6 digits integer (e.g. 210923
). It should match the last-modified date of your module, and acts as a version number.
• The last argument, 'myAutomaticMethod'
, is optional. It selects a method, among the [PUBLIC]
keys, that should be invoked by default if the client code uses the shortcut $$.MyModuleName()
instead of the regular $$.MyModuleName.someMethod()
.
• Then, the [PRIVATE]
and [PUBLIC]
blocks contain the members of your module, either methods or properties. The [PRIVATE]
area is for experienced developers who need to hide internal data or functionalities from the public API. For now, we will limit ourselves to the [PUBLIC]
area (see below.)
The magical side of the whole eval(...)...
template it that it actually builds a full IdExtenso component, ready to run, without polluting the [[global]]
scope or introducing any JavaScript closure. Behind the scenes, your module is declared as a function and registered as $$.MyModuleName
. And since any function is also a JS object, it can host its own members, including subroutines. The [PUBLIC]
block finally contains a literal object that declares all these public members.
Let's take a look to the new implementation:
Let's focus on the syntactic aspects that might frighten you:
• Line 10. The conditional form $$.hasOwnProperty('PathNodes')||...
anticipates the possibility that the script be executed in a persistent engine (#targetengine
). In such case, $$.PathNodes
would already exist at this point and we must not re-declare it. A more agnostic condition could be:
if( $$.isBooting() ){ ... }
However, $$.hasOwnProperty(<ModuleName>)||...
is the recommended syntax used in every extra module.
• Lines 14, 23, 43, etc. All public methods of your module are defined using the pattern
myMethod: function myMethod_Signature(args) { // etc },
where myMethod_Signature
is formatted with respect to the normative guidelines defined in $$.casting.jsxres. These naming rules are not mandatory —your script will still work anyway— but they greatly improve the autodocumentation of your API from $$.help()
(see below.)
• Line 14. If defined, the method named onUnload
is automatically called when the module is unloaded, that is, during $$.unload()
. This is a callback mechanism. We use it in the sample code to illustrate the principle: some internal data are then reset to false
, which tends to clean up memory. (Other possible callbacks are onLoad
and onEngine
, but we won't study them in this tutorial.)
• Line 19. The esoteric callee.µ
syntax is a magic reference to the module itself, viz. $$.PathNodes
in this particular case. Every public (and private) method can use callee.µ
for that purpose. You may regard callee.µ
as strictly equivalent to this
, since $$.PathNodes
is the de facto context of anyMethod
when $$.PathNodes.anyMethod()
is invoked. However, there are circumstances where the function $$.MyModule.anyMethod
could be called from another context, typically at the turn of a callback mechanism, or even explicitly:
$$.MyModule.anyMethod.call($.global)
.
Hence IdExtenso provides an internal reference that can always access the outer module from any of its methods, regardless of the calling context.
Note. — The difference between callee.µ
and this
is more noticeable when implementing a CLASS
, variant of the MODULE
scheme that also supports instantiation. A comprehensive example of a class is $$.Complex.
• Line 38. The special Object.prototype.setup
method, available to any object and therefore to any function, is defined until $$ is loaded. That's a powerful tool. It adds a set of properties to any entity and returns that entity. This trick is used almost everywhere in IdExtenso's source code.
• Line 117. The hotProcess
method clearly performs the essential task, the one we called until then the core routine. It now expects three valid and well-defined arguments: an array of SplineItems (items
), a Spread object (spd
) and the settings
. All other methods around this one take care of the stewardship. Thanks to this new division of labor, we can now rethink the script as an interaction between separate entities: application context, user parameters and main process.
• Line 179. The run
method becomes the new entry point of $$.PathNodes
. Its logic is extremely readable: if µ.canRun()
then µ.hotProcess(...)
, else µ.displayWarning()
(where µ refers to the module.) Note that 'run'
is declared as the automatic method, so the statement $$.PathNodes.run()
at Line 208 could be abbreviated $$.PathNodes()
.
• Line 200. Icing on the cake, $$.help()
displays the whole IdExtenso API, including the public methods of your own module:
All the information you see above is synthesized from the conventional naming and signature of your [PUBLIC]
methods. While you're at it, you can also explore the API of all IdExtenso core modules: $$ (root), $$.Dom, $$.Env, $$.File, $$.JSON, and $$.Log. Have fun!