Hi Sudha, [It seems to me that most of the points discussed above have already been touched by my fellows in sparse existing threads. Anyway the subject remains specially difficult to beginners and it's probably worthwhile to summarize the picture, so let's try!] The general issue in dealing with menu items (including associated actions, event listeners, etc.) is to get a clear understanding of object lifecycles in InDesign scripting model. Most of us have gradually grasped these subtleties, although there are still many secrets to discover. In short, what you MUST know before going any further is: 0. Menus and Submenus own MenuItems which are connected to InDesign processes through MenuActions (and ScriptMenuActions, but I'll leave that aside.) On the other hand, Events may be connected to custom callback functions through EventListeners. Most—if not all—InDesign entities can be the target of some event, and this includes Menus (cf beforeDisplay) and MenuActions (cf beforeInvoke), but also PageItems or even the app itself. In your example, though, MenuActions are the actual center of interest. Lifecycles: 1. As objects, native InDesign MenuActions are permanent. That is, you can't remove or change them, ever. 2. As objects, Menus, Submenus and MenuItems are application-persistent. That is, if you remove or change something in that space, these changes will persist beyond the ID session. That's a crucial and not very intuitive fact. 3. As objects, EventListeners (and ScriptMenuActions, but ok, let's really forget them) are always session-persistent. That is, changes in that space are intended to persist throughout the whole ID session, until you quit the app. 4. Finally, your event handlers—that is, the callback functions contained in your JSX code and attached to particular events via EventListeners—are at best engine-persistent (if a #targetengine directive is active) or simply “script-persistent”, in other words purely volatile, if you run in the default main engine. Note: There are in fact more persistent event handlers, those which are attached as JSX File objects. See also: Indiscripts :: How to Create your Own InDesign Menus Now you can clearly see that attaching an event handler to a MenuAction (invoked from a MenuItem) via a freshly created EventListener, may cause some issues if not carefully designed. For example, you may have an EventListener still alive—in the session—whose callback function is yet dead. In other threads we also discussed the problem of having a custom Menu and its MenuItem children (all application-persistent) waiting for an appropriate EventListener to wake up—which can be achieved with startup scripts only. So, the very first step is to clearly have in mind how these respective lifecycles have to be dealt with and connected in any project that interacts with such objects. The gold rule is, you need at least two distinct stages: (A) The Install Stage. This is the part of your code that initializes the persistent objects to be connected and used throughout the scope (usually, the engine) so that things are guaranteed to exist when you'll need them. That's typically the place to declare event handlers (but not to call them!), and to manage EventListeners to be either created or destroyed, not to mention menu structure, etc. In most projects the Install Stage is intended to run once per session, but since it could be manually executed again and again by an inquiring user, we must prevent troubles with object duplication. (B) The Script-In-Action Stage. Ideally, This Should Be an Empty Zone! Indeed, when installing event handlers, what you want is just to process outside events (that may or will happen later), hence the script itself should have nothing more to do than installing (once.) I think it important to stress this point because some scripters have trouble capturing it well: the script "in action" does basically nothing once installed. The real process is delayed to event handlers that wait patiently to take action. However, there are cases where (B) can have a job, namely, the job of reversing (A). This is what seems to emerge from reading the previous posts. The script, apart from the fact that it sets up listeners and event handlers, may be thought as a switch that toggles the whole event processing. So we create, within the Install area, a slightly enhanced function—say toggleListeners—designed to address this special feature. Now, better is to provide a (generously commented) code to illustrate the approach I recommend: //========================================================================== // We definitely need a session-persistent engine to keep our event // handler(s) alive, so we use the #targetengine directive. //========================================================================== #targetengine 'ExportAndPackageSpy' //========================================================================== // Since we have a session-persistent engine, executing the // present script shouldn't cause re-declaration of existing // data. The following conditional block takes this into // account and provides the 'installation' brick. //========================================================================== if( !$.global.hasOwnProperty('DATA') ) { // Builds a set of native MenuActions of interest. //---------------------------------- $.global.DATA = (function( o,r) { // Register package&export action specifiers, and other hosts if needed. // --- o = app.menuActions; r = { Package: { host:o.itemByName("$ID/Package...").toSpecifier(), etp:'beforeInvoke' }, Export: { host:o.itemByName("$ID/Export...").toSpecifier(), etp:'beforeInvoke' }, PdfExport: { host:o.itemByName("$ID/Export To PDF").toSpecifier(), etp:'beforeInvoke' }, }; // In addition you'd like to catch PDF 'sub-actions' which 'Export To // PDF' does not cover: [High Quality Print]..., [PDF/X-1a:2001]..., // etc. Unfortunately, the listeners of those menu actions do not work! // As a fallback mechanism you can globally spy the beforeExport event. // It occurs *after* menu actions but can still be used to prevent // the user from exporting the document by unexpected means. // --- r['AnyExport'] = { host:app.toSpecifier(), etp:'beforeExport' }; // Debug. // alert( r.toSource() ); return r; })(); // Defines the *actual function to be executed* by the // present script. In this particular case a 'toggle' feature // is wanted in order to turn listening ON/OFF. This process // is slighlty different from a pure menu installer but most // implementations use very similar tricks. //---------------------------------- $.global.toggleListeners = function(/*fct*/callback, loading,k,o,a,t) { // Even if a default callback is provided below, making it // visible as an input argument improves the modularity of // the code. (Technically, we could attach other event // handlers as well.) // --- 'function' == typeof callback || (callback=$.global.disableProcess); // Paranoid checkpoint. // --- if( 'function' != typeof callback) throw "No function callback supplied!" // Here we use the function itself (callee) as a 'flag keeper,' // taking advantage of its persistence. If loading is 1, // listeners are to be added. // --- loading = 1 - (callee.LOADED||0); // Loop in the DATA. // --- for( k in DATA ) { if( !DATA.hasOwnProperty(k) ) continue; // Collection of event listeners registered for this // menu action. // --- o = resolve(DATA .host).eventListeners; // *In any case*, remove any existing listener that matches // callback. This prevents debug or non-well-formed code from // duplicating listeners throughout the current session // (should #targetengine be missing or other bug occur...) // --- a = o.length ? o.everyItem().getElements() : []; while( t=a.pop() ) t.handler===callback && t.remove(); if( !loading ) continue; // If loading is required, add the listener. // --- o.add(DATA .etp, callback); } // Upate the internal flag. // --- callee.LOADED = loading; // Debug. alert( loading ? "Listeners LOADED." : "Listeners UNLOADED."); }; // Now comes the particular callback being invoked when any of the // spied events occurs. A critical property of this function is to // remain in memory as long as the event listeners may exist. Since // they are by nature session-persistent, we need a session-persistent // function as well (or a File, in other implementations.) // --- // This part of the code entirely depends on your specific goal. In // your example the actions have to be inhibited (likely under some // additional conditions?) Here we simply cancel the event, using // the fact that both BEFORE_INVOKE & BEFORE_EXPORT are cancelable. //---------------------------------- $.global.disableProcess = function(/*Event*/ev) { // Make sure no other hypothetical target // would catch that event (from now.) // --- ev.stopPropagation(); // Cancels the event. (In some versions, InDesign may prompt // a message telling the user that a native action has been // cancelled.) ev.preventDefault(); // Debug. alert( "Cancelled event:\r\r" + {}.toSource.call(ev.properties) ); }; } //========================================================================== // From this point the installation process is ready (data and functions // are known to the engine), but no event listener has been either added // or removed. The below instructions represent the actual finality of // executing the present script, that is, toggling event listeners. This // could be performed from a *startup script*, with the effect of turning // listeners ON (since no flag is attached to the function yet.) //========================================================================== toggleListeners(); Hope that helps. Marc
... View more