A huge number of mouve events are constantly being fired while the user is interacting with your ScriptUI dialog or window. For example, a mousemove item is triggered whenever the mouse is just moving, and a couple mouseout/mouseover occurs each time the pointer enters a distinct area of the UI. Those events can be listened to, either from their actual target (the specific control that is receiving the event) or from any parent container, using the famous addEventListener method.

Note. — Regarding UI events and event propagation, ScriptUI partially complies with the W3C specification. A good, highly recommended introduction to the topic is still available at quirksmode.org.

In this post I only focus on a particular issue: your UI has a Group (or a Panel) container which owns at least one child—referred to as the “control”—and your user wants to click that control. Whether the control is itself a container or a simple widget (like StaticText or Button) hardly matters in what follows.

Since the control is nested in a container, the mouse event which is assumed to occur in this particular area will:
— first hit the control, that is, its target (cf event.target property),
— then propagate along the hierarchy (bubbling phase.)

In CS Environment

During the bubbling phase, the event meets the parent container, then the parent's parent, and so on, up to the Window instance. This canonical scenario is what we observed in CS4, CS5, and CS6:

A mouse event, and its bubbling phase, as observed in ScriptUI CS.

Note. — What we commonly call a “click” involves in fact three distinct events: mousedown, mouseup, and click. Many CC-specific issues and bugs have been observed in that field (and some have been fixed.) A major difference between CS and CC environments remains: during a complete click cycle, CS fires mousedown–mouseup–click (in that order), while CC uses the sequence mousedown–click–mouseup (thus click occurs before mouseup.) In the present article a single mouse event is considered—say mousedown—regardless of the click ordering problem.

The bottom line is, in CS, a single mouse event occurs at the target, then it bubbles. So, if you register an event listener at the parent level, group.addEventListener('mousedown', handler), the handler function will receive one event at the bubbling phase, and the target property of that event will refer to the child control. The event does not occur in the container, the container is just listening to it.

Of course it is still possible to click the container itself, outside the control (if it doesn't entirely fill the group), but then we would have a distinct event in a distinct target.

In CC Environment

Now let's look at what's going on in ScriptUI CC when the user clicks the control:

In ScriptUI CC, two mouse events are triggered for the same “user event.”

Two mouse events are fired! The item event1 reflects the usual, primary event, which targets the control and bubbles, as observed in CS. But a secondary event, event2, is triggered from within the group, as if the primary event was somehow duplicated while passing through its original target.

It is important to understand that event2 is not event1 in its bubbling phase; it is really a new, separate event, ready to bubble, and having as target the parent object (the group).

You may wonder why a third event does not occur in the process, at the parent window level. Luckily—but that's nothing but a happy coincidence—ScriptUI CC does not allow Window instances to trigger mouse events, that is, the top window cannot be the target of such event. But if the group, in the figure above, was in turn nested in a parent Group or Panel, then such parent would trigger an event3 with its own propagation sequence.

To summarize, what we perceive as a single event from the user perspective (“clicking the control, once”) leads in fact to a series of cascading events. This issue seems specific to ScriptUI's Drover flavor (core version 6.2.x) and likely regards all CC applications on both platforms.

Safe Strategies

To deal with such a huge change in event managers that address both CS and CC clients, you must make sure that the particular event that your code is about to manage exactly matches your specifications. Otherwise you risk to handle several times the same apparent event. (If your control behaves like a switch or requires a lot of computing time, that's a critical issue.)

The problem is often to attach a listener at the highest level to manage multiple targets in a consistent way, thus avoiding code duplication. So, the top parent is listening to a particular event type that can be fired by either its children or itself. In CS, children somehow hide the parent as a possible target, while CC sends too many instances of the same informal event. But from the parent perspective, the mouse event may be reduced to a simple fact: “something just happened in my zone”, no matter which component—including myself—is regarded as the primary target.

A possible approach is:

(1) Stop event propagation as soon as possible (good practice for limiting overcrowding.)

(2) Parent side: only take into account the AT_TARGET event phase.

(3) Children side: in CS, create and dispatch dedicated events to the parent, in order to mimick CC's mechanism.

// Typical mouse event handler for children, in ScriptUI CS.
// (Not needed in CC.)
 
function csChildMouseEvHandler(/*Event*/ev,  t,k)
{
   ev.stopPropagation();
 
   // As the event is 'masked' to the parent, let's create a clone.
   // ---
   t = new UIEvent(ev.type,ev.captures,ev.bubbles,ev.view,ev.detail);
 
   // Make sure all mouse properties are loaded in t:
   // screenX, screenY, button, clientX, clientY, ctrlKey, etc
   // ---
   for( k in ev )
      ev.hasOwnProperty(k)
      && !t.hasOwnProperty(k)
      && (t[k]=ev[k]);
 
   // Dispatch the new event to the parent.
   // ---
   this.parent.dispatchEvent(t);
}
 
 

More serious implementations are to be explored in IdExtenso.