Event Dispatch

In Freestyle, Components are the fundamental unit of event dispatch. What does that mean? It means that whenever a user interface event occurs, whether it is caused by typing a key on the keyboard, or pressing a button on the mouse, that event will be said to have happened on a component. For example, typing a letter into a field of text, will generate a 'keydown' event on that text field, or clicking the mouse button while the pointer is over a check-box, will generate a "click" event on that check box. In fact, every event, no matter what type, always has a single component associated with it. That component is called the event target

The process of building your program's interface then becomes a simple exercise in connecting behaviors to the these events. We call this process registering event handlers. To react to a specific event, you notify a component that you want to run a bit of code every time Freestyle detects that an event of a certain type event has occurred. This bit of code is called the event handler.

On Event

The simplest way to register an event handler in Freestyle is with the onEvent() method of Component. With it, you can be notified whenever that component receives an event of a specific type. Example:

Button button = new Button("Ok");
button.onEvent("click", new MouseEventHandler() {
  public void handle(MouseEvent event) {
      Component target = event.getTarget();
      int x = event.getX();
      int y = event.getY();
      System.out.println(target+" was clicked at " + x + ", " + y);
  }
});

The handle() method of MouseEventHandler is invoked every time the "Ok" button receives a "click" event. It is important to note that when using the onEvent() method of a component, the event handler is only invoked for events which have that component as their target.

Pro Tip: There is no limit to the number of event event handlers that can be registered to receive events with onEvent() or any other event registration method. All of them will be invoked when the event occurs.

Sometimes however, the need arises to register event handlers in a more flexible way than with just a specific target as you do with onEvent().

Catch Event

Suppose you have a component with many fields for text entry. To make it more usable, you would like to add a keyboard shortcut to reset all of the fields whenever the user presses "CTRL+R". To do so, you must first implement the KeyEventHandler interface:

public class MyFormReset implements KeyEventHandler {
  private MyForm form;
  public MyFormReset(MyForm form) {
      this.form = form;
  }
  public void handle(KeyEvent event) {
      if (event.hasCtrlKey() && event.isChar('r')) {
        this.form.reset();
      }
  }
}

This defines the event handler, but where do you register it?. You could use onEvent() inside the constructor of the MyForm class, but you would have to call it once for every single field of text contained in your form. Matters would be complicated still if you were to dynamically add fields to your form at runtime: you would need to remember to register your event handler with each new field as it was added!

Rather than manage that complexity, Freestyle provides another, more flexible way of registering event handlers: catchEvent(). Registering a handler for an event type on a component with catchEvent() will invoke that event handler whenever that component or any of its descendants receive that event. In the case of our form:

public class MyForm extends Component {
  public MyForm() {
      this.catchEvent("keydown", new MyFormReset(this));
  }
  //class definition...
}

The MyFormReset event handler will be invoked whenever any component contained inside the form receives a "keydown" event, which is exactly the behavior required by this example.

Caution: Unlike onEvent(), event handlers registered with catchEvent() can be invoked for events with many different targets, so it is not safe to assume anything about which object the event target is (except that it is the invocant of catchEvent() or one of its descendants.)

Event Order

With catchEvent(), both a component and its ancestors can listen for events that are received by that component. But what happens if both that component and one or more of its ancestors have registered event handlers for the same event?

To illustrate the problem, suppose we have three elements (Fig. 1), all of which have registered "click" event handlers with catchEvent().

Whenever target receives a "click" event, all three components will have their event handler's invoked. But in what order? For event handlers registered with catchEvent(), the dispatch begins at the target and proceeds up the component hierarchy. So in this case, the "click" event handler for target will be invoked first, followed by container, followed by parent

Capture Event

To round out control over events, there is one final event registration method: captureEvent(). Event handlers registered with captureEvent() are invoked before all other event handlers, and proceed in the opposite order to those registered with catchEvent(), or down the component hierarchy, starting with the top-level component, and ending with the component target. This allows components further up in the hiearchy to intercept events before they reach their descendants

One reason to capture events might be to "disable" all the descendants of a component so that they become unresponsive to mouse input:

MouseEventHandler disable = new MouseEventHandler() {
  public void handle(MouseEvent event) {event.consume()}
};
parent.captureEvent("mousedown", disable);
parent.captureEvent("mouseup", disable);
parent.captureEvent("click", disable);

In this example, disable.handle() is invoked for every mouse event before that event is received by any of parent's descendants, including the event target itself. Thus, by consuming the event before any of its descendants handlers have a chance to react to the event, parent effectively "disables" its descendants.

The full story, explained in the following section, is that each of the event registration methods: onEvent(), catchEvent(), and captureEvent() registers an event handler to be fired during a distinct dispatch phase corresponding to its method name.

Phases of Dispatch

Freestyle event dispatch is divided into three distinct phases: capture, on, and catch, which proceed in that order.

  1. Capture: registered with captureEvent()
  2. This is the first phase of event dispatch. All "capture" handlers for an event are invoked before entering the next phase. The invocation of event handlers proceeds down the component hierarchy, beginning with the top-most component, and ending with the event target
  3. On: registered with onEvent()
  4. All "on" handlers are invoked after all "capture" handlers, but before any "catch" handlers. This only invokes handlers registered on the target of the event being dispatched. It specifically does not invoke any event handlers on any of target's ancestors.
  5. Catch: registered with catchEvent()
  6. The last phase of dispatch, "catch" handlers are dispatched after all other phases have finished. The invocation of catch handlers proceeds up the component hierarchy beginning with the event target, and ending with the top-most component.

Consuming Events

Any event handler can interrupt the normal flow of event dispatch, so that no subsequent event handler sees the event. There are many reasons you as a programmer might want to do this: from disabling a component, to improving performance (yes, the less code that gets run, the less time the dispatch cycle takes) It does this by calling the consume() method of the event being dispatched. Regardless of the dispatch phase the handler that consumes an event, is the last event handler to be invoked for that event

For example, a handler that consumes in an event in the capture phase, will prevent the entire "on" and "catch" phase from ever happening.

Releasing Event Handlers

Thus far, we have seen how to register event handlers, but what happens when you want to "unregister" a handler so that it is no longer invoked when its trigger event type is received?

Continuing with our previous example of "disabling" a component's children, to "re-enable" them at some later point, we will have to some how "undo" the quashing of all mouse events so that the disabled components can once again begin reacting to input from the mouse. This can be accomplished by releasing the consuming handler's callback binding

public interface CallbackBinding {
  void release();
}
/** Event Registration methods in Component */
public CallbackBinding captureEvent(String eventType, Handler handler);
public CallbackBinding onEvent(String eventType, Handler handler);
public CallbackBinding catchEvent(String eventType, Handler handler);

Each of the event registration methods returns an instance of CallbackBinding which associates that event handler to the component, event type, and dispatch phase of the registration. It is this object which you can use to permanently unbind that event handler from that same component, event type and dispatch phase for which it was registered, such that it will no longer be invoked under those conditions. This will not prevent it from continuing to be invoked as a result of other registrations.

Pro Tip: An event handler will not be garbage collected until its is no longer bound to any events(an event handler is considered implicity unbound if the component to which it was registered has been garbage collected), thus, any objects it references will be held until the event handler is completely unbound. Prevent memory leaks by always unbinding unnecessary, or obsolete event handlers