Skip to main content
Andreas Jansson
Inspiring
August 19, 2014
Answered

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

  • August 19, 2014
  • 1 reply
  • 5541 views

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

This topic has been closed for replies.
Correct answer Marc Autret

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).


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

1 reply

Community Expert
August 19, 2014

@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

Andreas Jansson
Inspiring
August 20, 2014

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.)

Marc Autret
Brainiac
August 20, 2014

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