10 Replies Latest reply on Aug 22, 2014 1:41 AM by Marc Autret

    [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?

    Andreas Jansson Level 2

      Running the following piece of code in a script will register a javascript function named "afterSelectionChanged" to be run as soon as the selection is changed – the second parameter is the function name:

      app.eventListeners.add("afterSelectionChanged", afterSelectionChanged);
      

       

      My problem concerns unregistering this event listener, or rather preventing it from being registered again. I'm using a specified "targetengine", so running the script again and again will add a new function to be called every time the script is started, resulting in the same function being called multiple times.

       

      In the main function (startup) of my script, I could loop through the app.eventListeners collection, and remove any eventListeners whose .handler == 'afterSelectionChanged()' or eventType == 'afterSelectionChanged' like this:

       

      for (var elIndex = app.eventListeners.length-1; elIndex >=0; elIndex--){
          if (app.eventListeners[elIndex].eventType == 'afterSelectionChanged'){
              app.eventListeners[elIndex].remove();
          }
      }
      

      Is this a good practice?

      Or is it unnecessary to check for specific event names at all, that is: should I rather remove all eventListeners, or could that disturb something? I have a #targetengine directive with a specified name, at the top of my script.

       

      Are you supposed to try to remove all eventhandlers and / or kill your targeted engine when a ScriptUI window is closed?

       

      Thanks,

      Andreas

        • 1. Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
          Laubender Adobe Community Professional & MVP

          @Andreas – eventListeners have several properties you can check. One is the handler you might be interested in, the function that is run, if the event is detected. You can see the whole function as value of the handler property, if you do not use jsxbin. Or at least the name of the function, if you are using jsxbin. The other one is the eventType. So you could loop through the app.eventListeners collection and check for a specific handler && eventType. You can also check for the value of "name" or the value of "label" of an eventListener, if you provided one when adding it.

           

          Look up properties and possible values here:

          Adobe InDesign CS6 (8.0) Object Model JS: EventListener

           

          Anonymus functions might be a problem to detect when asking for the value of a handler and using jsxbin files. Did not try that yet.

           

          Uwe

          • 2. Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
            Andreas Jansson Level 2

            Thanks Uwe – those are good advice to remove specific handlers.

             

            But would my eventlisteners only exist in the scope of my targeted engine name (using #targetengine at the top of my script)? If that is the case, would it not be a simpler and better idea just to remove all event listeners at startup in my script, without checking for any specific listener event types or handler functions?

            Or could other code create eventlisteners that I might accidentally remove? (Do they need to target the same engine for me to see them?)

             

            The other concern I have is the last sentence in my original message. The handler functions keep getting executed after I have shut down my ScriptUI window. Is there some generic deallocation or cleanup I could do to remove everything I created in "my" engine?

            (Since the event in my script just changes a part in the user interface it's no meaning in running it when the interface is not there.)

            • 3. Re: Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
              Marc Autret Level 4

              Hi Andreas,

               

              > would it not be a simpler and better idea just to remove all event listeners (...)

               

              IMHO this is a very bad idea, as you will also remove my own app's EventListeners ;-)

              No scripter should ever release a startup script that contains something like app.eventListeners.everyItem().remove()—except for clinical purpose!

               

              It seems to me that your question, in fact, leads to a more generic problem: How to make sure that some routine or action will be performed once (in particular in a session-persistent context)?

               

              There are many ways to reach that goal in ExtendScript. Two examples:

               

              1. At the engine level, you can prevent the entire code from being re-executed, using the following pattern:

               

              #targetengine 'setupOnce'

              var UID;

              $[UID='_'+$.engineName] || ($[UID]=function()

              {

                  // your script goes here

              })();

               

               

              2. In a complex script or library, you can 'decorate' a specific function so that it does not accidentally re-run:

               

              #targetengine 'functionOnce'

              var fOnce;

              fOnce || (fOnce = function F()

              {

                  if( !F.ONCE ) return;

               

                  // your function code goes here

                  // ...

                 

                  delete F.ONCE;

              }).ONCE=1;

               

              // calling the func somewhere

              // ---

              fOnce(); // executed

               

              // calling the func somewhere else

              // ---

              fOnce(); // noop

               

              Depending on your requirements, one or other of these strategies should help you prevent event listeners from being registered multiple times.

               

              As for removing an event listener, best is probably to backup its id somewhere—using either session-persistent data structure, app.insertLabel() or whatever—then to call app.eventListeners.itemByID(id).remove().

               

              @+

              Marc

              • 4. Re: Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                Laubender Adobe Community Professional & MVP

                @Marc – wow! Thank you for chiming in…

                 

                About using session-persistant data: I'd rather use a log file than "pollute" the app with a key/value pair.
                Also: A log file would survive a resetting of InDesign's preferences. Using app.insertLabel() might not.
                Even a total reinstall of InDesign will do no harm to a log file, if the folder it's saved to is chosen wisely.

                 

                Ok, I think that also would depend, if we run the script on a multi-user system where read/write permissions could be different per user.

                 

                Uwe

                 

                P.S. In any case your answer deserves a bookmark in my browser. Again. :-)

                P.P.S. @Andreas – Usually I'm running several startup scripts with InDesign. Definitly I would not be amused, if using your startup script and find out that it will zap all eventlisteners of app. Especially if you would use binary code for this. That wouldn't be fair at all ;-)

                • 5. Re: Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                  Andreas Jansson Level 2

                  Hi Marc!

                   

                  I notice that I used the word "startup" in my messages, which might have added some confusion. I'm not referring to a startup script, just to the starting point of a normal script that a user starts by double clicking it in the Indesign scripts panel.

                  While my script interface is showing, the listener and its registered handler has to react to every (select) event from the open InDesign document (there is a text change in a statictext, depending on selections). When my script interface (window) is closed, I no longer care about the event handlers that I registered.

                   

                  Aside from this confusion I take it that the same event, afterSelectionChanged in my case, might be registered to raise a call to another function in another engine, which I would remove by calling app.eventListeners.everyItem().remove(), right? The #targetengine directive does not set a scope in any way, that prevents my script code from removing eventlisteners created by other scripts (including startup scripts) with other engine names set?

                   

                  Thanks,

                  Andreas

                  • 6. Re: Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                    Marc Autret Level 4

                    @Uwe

                     

                    Thanks

                    Reading your note about saving data I now wonder how to preserve my bits against hdd re-formatting ;-)

                     

                    @Andreas

                     

                    To my knowledge—but maybe I should investigate further—the #targetengine directive set a clean and persistent global scope for your JS code only, so this is a pure ExtendScript feature. Alongside this, Application and other DOM objects have their own 'scope' in terms of data persistence and life cycle, as they are fully managed by InDesign or some subsystem. Hence, any script (whatever its scope) should regard the whole DOM as a live object which just behaves as a singleton. (Maybe I'm wrong about custom prototyped methods attached to DOM entities… Not sure about this…) But, at any time, there is a single InDesign app instance and therefore a unique app.eventListeners collection that every script could access and interact with. AFAIK all those listeners are session-persistent.

                     

                    @+

                    Marc

                    • 7. Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                      Andreas Jansson Level 2

                      So... The handler function can interact with my script as if it were a part of it (has this something to do with the targeted engine?), and still its code is stuck in the app object from the point in time when the eventListener was added until the InDesign application is restarted or the handler function is explicitly removed from app.eventListeners.

                      Until then, the handler code will run, even if the script is no longer running, and if the handler code depends on other variables (outside the handler function) in the script, there will be errors unless you handle them.

                      Is this an adequate description?

                       

                      [Edit:] Trying it out, obviously I get no errors, even after the window is closed. Is the script running anyway, in some sense?

                       

                      If I were to try and remove the event listener when my script window is closed, is there a suitable place to do that?

                      • 8. Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                        Marc Autret Level 4

                        Hi Andreas,

                         

                        Problem is, we don't know exactly what your script contains and how it is supposed to interact with ID.

                         

                        Here is what can be assumed so far. You have a jsx file that contains some code under a #targetengine directive. This directive creates a persistent scope—or might we say a context—where any defined variable, function, or entity, will persist during the InDesign session. That is, once the script has been executed, all those entities will keep their state or value. In particular, some functions are then available as event handlers, meaning that they can be triggered from the external world (i.e. InDesign DOM events, ScriptUI user events if a palette is active).

                         

                        Technically, your script as a program is only running when the jsx is executed. This stage might be regarded as the installation of the context. Then, code in your script is not running anymore, as long as ID (or ScriptUI) events do not occur. But, thanks to session persistence, your functions and data are available on-demand in their very last state. Now if some event occurs which is attached to some event handler defined in your code, then the corresponding function is executed. At this specific time, some part of your code will therefore be running, depending on how the called function interacts with other stuff, data, subroutines, available in the scope. That's it.

                         

                        In short, the whole script installs data and functions to be driven later by incoming events. That's event-driven programming.

                         

                        @+

                        Marc

                        • 9. Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                          Andreas Jansson Level 2

                          That was a great explanation! Thank you Marc.

                           

                          My script is showing a ScriptUI interface, and the selectionChanged event from InDesign causes a static label in my ScriptUI interface to chance its text, depending on whether there are selections or not in the document.

                           

                          And I thought it was strange that the handler could run, but with your explanation and another test it's not strange at all :-)

                          The ScriptUI window is just hidden when you close it... I can even set visible = true to show it again, from an external InDesign event.

                           

                          Can the script be removed, so that the code can no longer react to anything? (It's probably better just to remove the event handler, but I'm curious).

                          • 10. Re: [CS6/JS] Best practices checking whether eventListener already exists or preventing duplicate calls?
                            Marc Autret Level 4

                            Hi Andreas,

                             

                            AFAIK the script can't “be removed” in the sense of cleaning out the session-persistent engine that owns your code—except of course if you quit/restart ID.

                            But you can inhibit event handler(s) by removing the related listener(s):

                            • InDesign DOM: myEventListener.remove()

                            • ScriptUI: myWidget.removeEventListener(/*str*/eventName, /*fct*/eventHandler, /*bool*/capturePhaseAsDeclared)


                            Note: in a complex event-driven program it can be useful to implement something of a “smart stack” that centralize the management of event listeners through methods like register(), removeAll(), etc.

                             

                            @+

                            Marc