Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
581 lines (475 sloc) 19.4 KB
/*******************************************************************************
Name: BasicScript
Desc: Facade of a simple Yalt/Settings/Dialog script.
Path: /etc/$$.BasicScript.jsxlib
Require: Yalt, Settings, Dom.Dialog (forcibly included.)
Encoding: ÛȚF8
Core: NO
Kind: Module.
API: =run() init() changeLocaleTo()
onEngine() onLoad() onUnload()
DOM-access: ---
Todo: ---
Created: 180307 (YYMMDD)
Modified: 180605 (YYMMDD)
*******************************************************************************/
#include '$$.Yalt.jsxlib'
#include '$$.Settings.jsxlib'
#include '$$.Dom.Dialog.jsxlib'
;if( $$.ModalScript ){ alert(__("%1 cannot work with the %2 module.",'BasicScript','ModalScript')); exit(); }
;$$.hasOwnProperty('BasicScript') || eval(__(MODULE, $$, 'BasicScript', 180605, 'run'))
//==========================================================================
// NOTICE
//==========================================================================
/*
The present module offers a full template system for creating InDesign scripts.
It integrates the localization engine (Yalt), the settings manager (Settings),
and the Dom.Dialog so your UI is easily declared and ready to run.
BasicScript is a 'facade' in that it hides many IdExtenso intricacies and just
shows you the essential bricks to get your script working fast, safely, and
with all $$ features still available if need be (log, JSON, etc.)
THE 'CONTEXT/UI/SERVER' PARADIGM
BasicScript implements a Context/UI/Server paradigm. It fits well a
wide variety of scripts, under the following requirements:
(a) The program primarily needs to check a Context,
which highly determines what can/may be done.
In DOM terms this refers to any condition on the current selection,
existence or status of the active document, constraints on some object,
layer, spread, layout, style name(s), preference, units, coordinates.
Anything that is now visible, or selected, from the user perspective.
Any data, parameter, or condition that can be found in the environment.
REM As an abstraction the Context is responsible for extracting, reading,
parsing data. It isn't intended to create things or perform 'actual'
tasks altering the DOM (unless it can restore the initial state.)
(b) Before processing, the program opens a simple dialog (UI),
from which the user validates options and tasks, or cancel.
The dialog has to be *simple* in the sense it only handle native
DOM widgets (checkboxes, dropdowns, edit fields, radio options...)
and does not require additional interactions, nor dynamic update.
Basically, it gathers both contextual data and user preferences
for this particular program. The UI makes the Context and the User
meet in order to define a process.
(c) Once properly defined and requested, the process can be 'served'
as a consistent, airtight, set of tasks.
The process must be 'strongly decoupled' from the previous stages.
Ideally, it just has to take as input the current settings (incl.
some contextual data) and run. It is the heavy component, the one
that acts (layout processing, font/style report, preflight, XML,
graphic adjustments, text cleaning, etc.)
ABSTRACT COMPONENTS
BasicScript handles and interacts with three child modules: Context,
UserInterface, and Server. They are built and loaded as abstract
components, that is, formal placeholders ready to receive specific
features.
The constant part of each abstract component is coded in its
respective `jsxlib` file (`etc/BasicScript/<Component>.jsxlib`).
You don't want to change any code there. Instead, you will extend
each module from your own project (as we shall see) by defining
callback functions, also called 'hooks', which the system invokes
when needed. If a certain hook is missing, then the default
scenario goes on to the next step.
For example, the Context module supports a `beforeActivate` hook.
If you define it, it is automatically called before settings
activation so you can provide a specific DOM host for those
settings that require to wake up from DOM data.
COMPONENT PURPOSE AND SUPPORTED HOOKS
------------------------------------------------------------------
Context Manage settings (activate, backup) and contexual data.
Hooks: beforeActivate, onActive, beforeBackup, onQuit.
------------------------------------------------------------------
UserInterface Run and close the UI dialog (if available).
Hooks: beforeShow, onClose.
------------------------------------------------------------------
Server Full container of the 'hot' process.
Hook: run.
------------------------------------------------------------------
BasicScript exposes a public `run` function that performs the
following steps:
1. Load IdExtenso now ($$.load).
2. Get the Context active (in particular, activate the settings.)
3. If all is fine, call the UserInterface (show/close steps).
4. If all is fine, call the Server (run step).
5. Quit the Context (in particular, save the settings if needed.)
BOILERPLATE
Below is a template that shows how your final program (the main JSX)
will look like:
----------------------------------------------------------
// Use "main" to discard session-persistence.
#targetengine "MyGreatScript"
#include 'path/to/idextenso/$$.jsxinc'
#include 'path/to/idextenso/etc/$$.BasicScript.jsxlib'
// #include. . . (additional $$ modules you may need)
;if( $$.isBooting() )
{
$$.BasicScript.init
(
<yourSettings>,
<yourYaltPackage>,
<yourXmlUI>
)
;
// Your specific Context API.
// ---
($.global.µ=$$.BasicScript['Context'])
#include '<yourContext>.jsxinc'
;
// Your specific UserInterface API.
// ---
($.global.µ=$$.BasicScript['UserInterface'])
#include '<yourUI>.jsxinc'
;
// Your specific Server API.
// ---
($.global.µ=$$.BasicScript['Server'])
#include '<yourServer>.jsxinc'
}
$$.BasicScript();
----------------------------------------------------------
Use `$$.BasicScript.init(...)` to load (once for all) the fixed
data, or 'resources,' of your program:
<yourSettings>, if supplied, is the Object that
declares all settings key/value pairs.
-> See $$.Settings for further detail.
<yourYaltPackage>, if supplied, is a String that
encodes all L10N strings used in your project,
formatted as a Yalt package.
-> See $$.Yalt for further detail.
<yourXmlUI>, if supplied, is the XML object that
describes your dialog, as specified in Dom.Dialog.
-> See $$.Dom.Dialog for further detail.
[REM] Each of these three optional arguments can be loaded
through a local `#include ...` directive, which
helps keep your 'resource' distinct from the program.
Finally, you provide the local implementation of each abstract
component (if used) in your own `jsxinc` files:
. . .
#include '<yourContext>.jsxinc'
. . .
#include '<yourUI>.jsxinc'
. . .
#include '<yourServer>.jsxinc'
[REM] The lines of the form `($.global.µ=$$.BasicScript[...])`
in the template may look obscure. Keep them as written!
They just allow to inject your `jsxinc` files in the
corresponding module space while preserving the general
syntax and conventions used in the framework.
Now, what is left to you? Only implementing the desired 'hooks'
(see above) in the respective `jsxinc` files. For example,
<yourContext>.jsxinc may look like:
----------------------------------------------------------
[PRIVATE]
({
MY_FUNC: function()
// ----------------------
// For some reason you need a private method...
{
$$.trace("Just to say HELLO to the log!"):
},
// etc
})
[PUBLIC]
({
onActive: function onActive_O_(ss)
// ----------------------
// This is the `onActive` hook. Great news is,
// it provides the active settings as 1st arg.
{
$$.success(__("Script name is %1",ss.$NAME));
},
// etc
})
----------------------------------------------------------
Note that the code has the form we already see anywhere else
in IdExtenso modules. You will use the [PRIVATE] and [PUBLIC]
clusters to separate inner members from public API. You can use
`.setup(...)` on incoming functions, and so on. Strictly
speaking, your own `jsxinc` code becomes a *part* of the
respective modules, it is injected at including time in either
Context.jsxlib, UserInterface.jsxlib, or Server.jsxlib.
In fact, when your program is very simple in terms of settings
and UI, all you have to do is to create the `<yourServer>.jsxinc`
snippet and implement the `run` hook!
*/
//==========================================================================
// PRIVATE INITIALIZERS
//==========================================================================
[PRIVATE]
({
YALT: false,
DECL: function( $$,q,v,o)
//----------------------------------
// (Declare-Settings.) Declare and normalize settings.
// [REM] Called by `onEngine`. See also `init()`.
// Normalized constant settings (the starting `_` is the contant shortcut.)
// ---------------------------------------------------------------------
// _$IDRQ :: (num) InDesign version required ; e.g 7.5
// _$SUID :: (str) Unique ID ; e.g "d7d1fbece5d803ed5c9bf50eb68580cd"
// ---------------------------------------------------------------------
// _$NAME :: (str) Script name ; e.g "MyScript"
// _$VSTR :: (str) Script version ; e.g "2.027"
// _$TYPE :: (str) Script type (A,B,F,T,P) ; e.g "B" (for beta.)
// _$COPY :: (str) Copyright line ; e.g "© 2018, myserver.com"
// _$HEAD :: (str) Full script title ; e.g "MyScript 2.027[beta]"
// ---------------------------------------------------------------------
// => undefined
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
// Script file name.
// ---
callee.FNME=$$.Env.runningScript;
// Raw data.
// ---
q = callee.DATA||(callee.DATA={});
// Required ID version ($IDRQ).
// ---
if( q.hasOwnProperty('_$IDRQ') )
{
v = q._$IDRQ;
'number' == typeof v || (v=parseFloat(String(s)));
6 < v && (callee.IDRQ=v);
}
// Script name ($NAME).
// ---
callee.NAME = q._$NAME || (q._$NAME=File(callee.FNME).nudeName);
// Script version ($VSTR). Fallback: "1.0".
// ---
callee.VSTR = q._$VSTR || (q._$VSTR='1.0');
// Script type ($TYPE) among 'A'|'B'|'F'|'T'|'P' or empty.
// ---
callee.TYPE = {A:' alpha', B:' beta', F:' free', T:' trial', P:' pro'}[q._$TYPE] || '';
// Copyright line ($COPY). May be empty.
// ---
callee.COPY = q._$COPY || "";
// Script head ($HEAD) served as title in dialogs etc.
// Unless specified, mix of NAME, VSTR, TYPE and COPY.
// ---
callee.HEAD = q._$HEAD || (q._$HEAD=__("%1 %2%3 %4"
, callee.NAME
, callee.VSTR
, callee.TYPE
, callee.COPY
).trim());
// Settings Unique ID ($SUID).
// ---
callee.SUID = q._$SUID = String(q._$SUID) || ($$.MD5||String)(callee.NAME);
// Default LIVE keys:
// hasDocument ; hasSelection
// ---
( (o=q.LIVE) && o===Object(o) ) || (o=q.LIVE={});
o.hasOwnProperty('hasDocument') || (o.hasDocument=void 0);
o.hasOwnProperty('hasSelection') || (o.hasSelection=void 0);
// Default RESET keys:
// endMessage
// ---
( (o=q.RESET) && o===Object(o) ) || (o=q.RESET={});
o.hasOwnProperty('endMessage') || (o.endMessage="");
// Declare the settings.
// ---
$$.Settings.declare(callee.SUID,q);
}
.setup({ DATA:false, IDRQ:6, FNME:'', NAME: '', VSTR:'', COPY:'', SUID:'', TYPE:'', HEAD:'' }),
DIAL: function( x,$$,xk,k,o,pfx,p)
//----------------------------------
// (Declare-Dialog.) Normalize and build the dialog (if wanted.)
// 1. Creates a default title unless the XML provides a `name` attr.
// 2. Calls Dom.Dialog to get a Dialog instance ; saves its spec.
// 3. Keeps track of the dialog<->settings key mapping, allowing
// a few key prefixes to get a special treatment:
// ------------------------------------------------------------
// `%MyKey` (XML) is mapped to `MyKey` (Settings) w/ %-prefix
// Useful to perform special op. regarding percentage boxes.
// ------------------------------------------------------------
// `µMyKey` (XML) is mapped to `MyKey` (Settings) w/ µ-prefix
// Useful to perform special op. regarding unit boxes.
// ------------------------------------------------------------
// More detail in UserInterface.jsxlib.
// ---
// [REM] This method is called by `onLoad`. See also `init()`.
// => undefined
{
if( !(x=callee.DXML) ) return;
// Default dialog title (if missing.)
// ---
if( !x.attribute('name').toString() ) x.@name=this.DECL.HEAD;
// Create the dialog and save its spec.
// ---
callee.SPEC = ($$=$.global[callee.µ.__root__]).Dom.Dialog(x).toSpecifier();
// Want to keep Settings-To-Dialog Keys quickly accessible.
// ---
o = callee.KEYS;
pfx = callee.PRFX; // Special prefixes.
xk = x..@key;
for each( k in xk )
{
if( !(k=k.toString()) ) continue;
(0 <= pfx.indexOf(p=k.charAt(0)) || (p='')) && (k=k.substr(1));
$$.Settings.hasKey(k) && (o[k]=p);
}
}
.setup({ DXML:false, SPEC:false, PRFX:'', KEYS:{/*ssKey=>prefix|''*/} }),
})
//==========================================================================
// API
//==========================================================================
[PUBLIC]
({
init: function init_õs$XML$_õs$XML$_õs$XML$_$this$(/*?obj|str|xml*/a,/*?obj|str|xml*/b,/*?obj|str|xml*/c, t,x,$$,I)
//----------------------------------
// Initialize settings and/or yalt pack and/or xml dialog,
// these arguments being *passed in no particular order*.
// This function is invoked at including time, before $$.load(),
// to get various engine persistent data already available.
// (a) Any object-type arg is interpreted script Settings
// (Normalized settings properties are detailed in ~.DECL.)
// (b) Any string-typed arg is interpreted Yalt package.
// (c) Any xml-typed arg is interpreted XML dialog.
// ---
// => this
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
I = callee.µ['~'];
for( t=[a,b,c] ; t.length ; )
{
x = t.pop();
switch( typeof x )
{
case 'string' :
I.YALT || -1==x.indexOf('#') || $$.Yalt(I.YALT=x);
break;
case 'xml' :
I.DIAL.DXML || (I.DIAL.DXML=x);
break;
default :
I.DECL.DATA || (x!==Object(x)) || (I.DECL.DATA=x);
}
}
return callee.µ;
},
onEngine: function onEngine_( )
// ---------------------------------
// Executed once if the script uses a persistent #targetengine.
{
callee.µ['~'].DECL();
},
onLoad: function onLoad_( s)
// ---------------------------------
// Executed every time you run the script.
{
// Make sure the dialog is OK. This can't be done through
// onEngine due to a CC bug with persistent dialogs :-(
// ---
if( (s=callee.µ['~'].DIAL.SPEC) && resolve(s).isValid ) return;
callee.µ['~'].DIAL();
},
onUnload: function onUnload_( s,$$,t)
// ---------------------------------
// If the script uses a Dialog AND runs in a *non-persistent* engine,
// we always have to destroy the object to free up memory (since dialogs
// are session-persistent.)
// [WARN] In CS4-CS6 keeping a dialog in memory is OK, but in CC dialog
// persistence is buggy so we have to clear the ref *even in non-main engine*.
{
if( !(s=callee.µ['~'].DIAL.SPEC) || !(t=resolve(s)).isValid ) return;
$$ = $.global[callee.µ.__root__];
// In CC, forcibly destroy the dialog in any case (and removes DIAL.SPEC.)
// ---
if( $$.inCC )
{
+$$.trace && $$.trace(__("%1 > Destroying %2 due to CC environment.",callee.µ,s));
t.destroy();
delete callee.µ['~'].DIAL.SPEC;
return;
}
// Taking advantage of persistent engine in CS4..CS6, only
// destroy the reference if engine state is non-persistent (-1).
// ---
if( $$.Env.engineState() < 0 )
{
+$$.trace && $$.trace(__("%1 > Destroying %2 due to non-persistent engine.",callee.µ,s));
t.destroy();
return;
}
},
changeLocaleTo: function changeLocaleTo_i$locale$_B(/*uint|LocaleID=auto*/iLocale, I,$$,t)
// ---------------------------------
// [ADD180325] Re-activate Yalt to a different locale, then make
// sure the Dialog is accordingly rebuilt. This method should be
// called before Dialog values be populated, typically in an onLoad
// handler or at onActive stage (Context.)
// ---
// E.g: µ.changeLocaleTo(Locale.GERMAN_LOCALE);
{
I = callee.µ['~'];
$$ = $.global[callee.µ.__root__];
I.YALT && $$.Yalt && $$.Yalt.activate(iLocale);
if( (t=I.DIAL.SPEC) && (t=resolve(t)).isValid )
{
t.destroy();
delete I.DIAL.SPEC;
I.DIAL();
}
},
run: function run_ï_(/*int=0*/runMode, µ,$$,I,r)
//----------------------------------
// Actual entry point of the script.
{
µ = callee.µ; // the present module
$$ = $.global.__root__]; // agnostic reference
I = µ['~']; // private zone
runMode || (runMode=0);
$$.load(runMode);
r = void 0;
try
{
$$.idVersion(I.DECL.IDRQ)
|| $$.error( __("Unsupported InDesign version. Should be at least %1.",I.DECL.IDRQ) );
// Go to the context (activate settings.)
// ---
$$.trace(__("%1 > Starting up the Context in runMode %2...",µ,runMode));
r = µ.Context['~']._GO_(runMode);
$$.trace(__("%1 > Returned value: <%2>.",µ,r));
// Go to the UI.
// ---
false===r || (true===r&&(r=1)) ||
(
$$.trace(__("%1 > Calling the UserInterface...",µ)),
(r=µ.UserInterface['~']._GO_(I.DIAL,runMode)),
$$.trace(__("%1 > Returned value: <%2>.",µ,r))
);
// Go to the Server.
// ---
false===r || (true===r&&(r=1)) ||
(
$$.trace(__("%1 > Calling the Server...",µ)),
(r=µ.Server['~']._GO_(runMode)),
$$.trace(__("%1 > Returned value: <%2>.",µ,r))
);
// Quit the context (backup if non-FALSE.)
// ---
$$.trace(__("%1 > Quitting the Context (backup=%2)...",µ,false!==r));
r = µ.Context['~']._QT_(false!==r,runMode);
}
catch(e)
{
// [CHG180605]
// ---
if( $$.yesNo(__("An error occured: %1. Do you want to forcibly reset script preferences?",e)) )
{
$$.Settings.reset(null);
$$.Settings.backup();
alert(__("Preferences have been reset to defaults. You may consider to run the script again."));
}
else
{
$$.receiveError(e);
}
}
// [CHG180406] The returned value from Context._QT_() is used
// as the KEEP_DORMANT arg of $$.unload.
// ---
$$.unload(r);
},
})
#include 'BasicScript/$$.Context.jsxlib'
#include 'BasicScript/$$.UserInterface.jsxlib'
#include 'BasicScript/$$.Server.jsxlib'