Streamlining user preferences and similar data throughout your code is undoubtedly the criterion that best distinguishes amateur from professional scripts. There are many questions and issues to address here: settings should be made easily available anywhere in your code, some require refreshment when the script starts up, others are to be dynamically changed when the user clicks OK, sometimes we expect an option to persist beyond the current session (say, for example, language preferences) while in other cases we have to attach a parameter to a particular document or object.

Settings must be declared, accessed, adjusted, saved, restored. Each key/value pair has its own live cycle, its dependencies, its purpose (not necessarily noticeable to the user.) The biggest difficulty is to decouple this special data stream from local tokens (function arguments, variables, etc).

How to keep settings administered in one place while your project keeps growing and queries more and more scattered data? IdExtenso's $$.Settings module has been designed to answer that question. Simply declare a set of keys in the onEngine handler of your project and your settings are now entirely managed by this specialized module:

Settings declaration made uniform and centralized!

This short code shows how keys are declared and, more importantly, how their respective lifespan, or ‘scope’, is specified. Here are the available scopes and the corresponding scenarios:

1. CONSTANTS. Pure read-only key-value associations, which the client code cannot even change. Useful for product name, version, user license data, and the many fixed strings or numbers that the script must keep safe. Note: CONSTANT keys can be declared using an underscore prefix, e.g. _VERSION means that the key VERSION is read-only.

2. LIVE KEYS. Volatile parameters (never saved nor restored) used to share data across different places. These are not “settings” in the usual sense; they work as on-the-fly parameters registered in $$.Settings space for convenience. Use case: the active document ID, or some current target specifier, must be shared across different parts of your script.

3. RESET KEYS. Keys that can freely evolve once activated (as the LIVE Keys), but expected to always wake up at a determined value. Here ‘always’ means, “each time the script starts up.” Note: This makes a difference in engine-persistent scripts, where LIVE keys wake up in an arbitrary state (depending of previous operations) while RESET keys are guaranteed to regain their default value.

4. ENGINE KEYS. These keys are aligned with the script engine. That is, they remember their latest value throughout the session in a persistent engine, but they reset to the default value if used in the main engine. Unlike LIVE keys, ENGINE keys have a default value so they can be forcibly reset from the client code. Unlike RESET keys, they do not automatically recover their default value if the engine is persistent. Note: ENGINE is the default scope: keys declared outside of an explicit branch behave this way.

5. SESSION KEYS. Whatever the engine in use, these keys persist throughout the whole InDesign session (unless forcibly reset, or not saved.) Note: SESSION keys are kept as JSON strings in the system environment, so they enjoy a fast backup/restore mechanism compared to DOM-based routines. Use cases: whenever you need to keep track of “what happened before” in the present InDesign session, e.g, script runcount, tasks already performed, etc.

6. OBJ KEYS. These special keys are intended to be saved in an InDesign DOM object, if supplied. They activate to a default value and behave as ENGINE keys if no DOM object is provided. Otherwise, they get the value stored in that particular object. Use case: your script builds & manages data-driven documents and needs to keep some metadata attached to each of them. When the user re-opens such document, the OBJ-scoped settings reflect the specific data attached to it.

7. APP KEYS. These are the most persistent keys: InDesign will remember them even in later sessions (after quit/restart.) Note: Technically, an APP key can be thought as an OBJ key based on OBJ==InDesign. Use this scope for managing long-term user preferences. Note: your script may then offer a “Go Back to Defaults” option, which can be done using $$.Settings.reset(null).

8. HYBRID KEYS. An HYBRID key imitates the global vs. local paradigm used in InDesign preferences. If a DOM object is supplied (typically, a Document) then the value is taken from it, otherwise the application key is considered. For example, if your script provides Swatch- or Style-related settings, you may want to handle them in a global/local fashion, as InDesign does.

A unique ID must be passed in (first argument) to $$.Settings.declare(). The MD5 hash of the project name is a possible UID, as in our example, but you can use any string that meets the uniqueness condition. Settings declaration must be done once, so we use the onEngine handler (not onLoad) of our module. Another option is to call $$.Settings.declare() from within a if($$.isBooting()){...} block. A subsequent stage, settings activation, should be performed whenever your script runs or re-runs. To give concrete meaning to these principles, let's move on to practical work!

Our Base example (Version 5)

The natural development of our project led us to add a few persistent settings that we shall make available, in the dialog window, alongside the original radius and stroke options:

Enhanced UI of the new version.

scope lets the user choose between “Selection” and “Spread”. In the latter case, the script targets all spline items available in the active spread rather than the selected object alone.

autoRunSel is a convenient option that tells $$.PathNodes to skip the dialog if a valid selection is active. The script then automatically targets the selected object.

Note: Our SESSION settings could have been made application-persistent instead. A good practive, however, is to develop your project in the SESSION lifespan until your code is stable and safe enough to declare APP settings. It's just a matter of replacing the 'SESSION' branch by 'APP' ;-)

Of course we have to implement the underlying tasks, in particular the processing of an entire spread. But this won't introduce any change in the hotProcess function, which was thought upstream to support any set of input arguments. Also, the script no longer needs to warn the user when no valid selection is active, since it then reacts by showing the UI in a mode that lets you change the preferences globally—I mean, even if no document is available in InDesign. The different scenarios are sketched below:

How the entry point of the script should deal with settings and context.

These improvements explain why the run method has been restructured a bit. But in return, a lot of unnecessary bricks were removed and the whole module now looks very consistent:

A few implementation notes:

Line 13 introduces the new essential character of our plot: $$.Settings.

Lines 19-49. Here is added the onEngine handler. As it only runs once per session, this is the ideal place for declaring settings.

Lines 63-171. Distinct methods, checkSel and checkSpread, were needed to treat separately the two possible scopes (selection vs. spread.) Each of these functions answers the question: can the hot process work in my scope? In the affirmative, the target items and spread are cached in the internal keys ITMS and SPRD.

Lines 235-263. Note how the dialog method has been flawlessly upgraded! The $$.Settings keys are now activated from the outside and passed by reference (ss argument), but the logic of the code is completely preserved. (Compare with version 4.)

Lines 265-274. Added the “Scope” dropdown and the “Autorun on Selection” checkbox. Note how the list attribute is formatted in the <DROP> element —that's one of the goodies of the $$.Dom.Dialog module!

Line 282. $$.Settings.activate() loads the keys in their respective state and returns a special object, referred to as an accessor, in direct contact with all available key/value pairs. Consider it a regular JavaScript object for read/write operations. (Behind the scenes, $$.Settings keeps an eye on it though…)

Line 313. $$.Settings.backup() must be expressly invoked when the main routine returns. Indeed, there are many circumstances where changes aren't supposed to be saved, like when the user cancels the dialog!

Don't you feel that our PathNodes script is now very close to its stable version?

(To be continued…)

STEP 1 | STEP 2 | STEP 3 | STEP 4