Locked History Actions

GWT/Events/EventSystem

GwtEventSystem翻訳

※これはincubator時代の文書で、現在のGWTにはこの仕様(あるいは変更仕様)が組み込まれている。

イントロ

現在のListener Widget Event Systemは理解と使用が簡単であり、これらは大きなメリットである。 が、デメリットもある。ここで我々は

  • 現在のListener Systemのデメリットを詳説する。
  • 今回提案のEvent Handler Systemを説明する。
  • 現在のListener Systemのデメリットを再訪し、Handler Systemがどのように解決するかを示す。

現在のListener Systemのデメリット

  • イベントに情報を追加することができない。
    • 理由:インターフェースを変更することは破壊変更になる。
    • 例:現在のClickあるいはchangeインターフェースにネイティブイベントを追加することはできない。
  • 現在のListenerに新規機能を追加することができない。
    • 理由:同じくインターフェースを変更することは破壊変更になる。
    • 例:TreeListenerは、より多くのTree Eventを許すように拡張することはできない。

  • 多くのウィジェットはListenerを持たないが、しかし3,4つのリスナーフィールドをnull初期化しておく必要がある(訳注:?)。
    • 例:多くのラベルウィジェットはイベントを持たないが、3つのフィールドをnull初期化しておく必要がある。
  • ウィジェットについての不必要なイベントをsink(※)する必要がある。
    • 理由:多くのイベントリスナが一つのリスナインターフェースの中に複数のイベントメソッドをバンドルしている。
    • 例:mouse downイベントをlistenするために、mouse moveイベントをsunkしなければならない。
  • ユーザは多くの空メソッドを実装しなければならない、あるいはAdapterクラスを拡張する必要がある。
    • 理由:ユーザはインターフェースに定義された一部のメソッドのみを必要とする。
    • 例:mouse upリスンたを追加するには、空のmouse down,mouse moveメソッドをも追加する必要がある。
  • 多くのウィジェットにおいて、リスナが初期化されたかをチェックし、初期化するというボイラープレートコードを記述しなければならない。Therefore, no individual listener method can ever be inlined away.
    • 例:

          if(clickListeners == null) {
            sinkEvents(Event.ON_CLICK);
            clickListeners = new ClickListenerCollection(); 
          }
          clickListeners.add(handler);
  • すべてのイベントソースはウィジェットでなければならない。
    • 理由:インタフェースハンドラメソッドはsourceをウィジェットと定義している。
  • すべてのウィジェット作成者はそれ自身のイベントをsinkしなければならない。

http://en.wikipedia.org/wiki/Sink_%28computing%29

提案するハンドラシステム

Widget Event API

全GWTユーザが使用することになるAPIである。 ユーザはウィジェットに新ハンドラを追加することができる。

クラスの概略

動作の仕方

各ハンドラは一つの引数を持つ一つのメソッドを持つ。 例えば、「void onClick(ClickEvent event)」あるいは「void onKeyDown(KeyDownEvent event)」のように。

あるイベントタイプをサポートするウィジェットは、対応するHas*Handlersインターフェースを実装する。 したがって、「form HandlerRegistration addFooHandler(FooEvent)」という形になる。 例えば、クリックハンドラをサポートするウィジェットはHasClickHandlersを実装する。

ハンドラを削除するには、ハンドラのaddメソッドが返したHandlerRegistrationインスタンスに対して、removeHandler()を呼び出す。

ハンドラの使用例

 DateBox start = new DateBox();
 start.addKeyDownHandler(new KeyDownHandler() {
      public void onKeyDown(KeyDownEvent e) {
        if (e.getNativeKeyCode() == KEY_RIGHT){
           Window.alert("I have to be right");
        }
      }
    });

ウィジェット開発者用イベントAPI

これらは、自身のウィジェットを作成してパッケージングする開発者向けのものである。 ウィジェットに新しいハンドラをhook upし、それらにイベントを送信するようにする。

クラス概要

  • AbstractEvent.Type

    • イベントクラスに関するメタ情報をカプセル化する。
    • 各イベントタイプについて、特定のタイプがある。
      • ClickEvent.getType()

        • クリックイベントのハンドラ登録時に使用される。
      • KeyDownEvent.getType()

        • キーダウンイベントのハンドラ登録時に使用される。
      • ...
      • 各イベントタイプは以下の情報を持つ。
        • イベントタイプについてのユニークID(ハンドラ保存時に用いられる)
        • ハンドラメソッドのfireの仕方

Extra Widget methods

動作の仕方

ハンドラのウィジェットへの登録時、ウィジェットはイベントタイプとハンドラを指定してaddHandlerを呼び出す。

     public void addKeyDownHandler(KeyDownHandler handler) {
        addHandler(handler,KeyDownEvent.getType());
     } 

ウィジェットのハンドラを使う(訳注:イベントを通知する?)には、イベントオブジェクトを作成し、ウィジェットのfireEventを呼び出す。

      @Override
      public void onBrowserEvent(Event e){
        ...
        case ON_KEYDOWN:
          fireEvent(new KeyDownEvent(e)); 
      }

ウィジェットのイベントハンドリングコード例

以下のコードは、DateBoxKeyDownHandlerをハンドリングさせている。 言い換えれば、上に述べたDataBox.addKeyDownEvent機能を実装している。

public class DateBox extends Composite implements FiresKeyDownEvents {
...

 // implements FiresKeyDownEvents.addKeyDownHandler
 public void addKeyDownHandler(KeyDownHandler handler) {
    // Widget's addDomHandler method. 
    // Registers the current handler as a KeyDownEvent handler.
    addDomHandler(handler,KeyDownEvent.getType());
 }

ウィジェットは現在、デフォルトのonBrowserEvent実装を持つため、明示的にキーダウンイベントをfireする必要はない。

上級ウィジェット生成法

このAPIはカスタムイベントの生成が必要な場合や、あるいはWidgetクラスを継承しないウィジェットの生成が必要な場合のためのものである。

クラス概要

  • HandlerManager

    • ウィジェットに対するすべてのイベントハンドラを管理する。ウィジェットはaddHandlerが呼び出されたらHandlerManagerをlazilyに生成する。

    • HandlerManagerはただ一つのウィジェットに関連づけられている。マネージャに渡されるすべてのイベントのソースとしてそのウィジェットが設定される。

Protectedメソッド

  • AbstractEvent

    • abstract AbstractEvent.Type getAssociatedType()

      • イベントインスタンスからスタティックなイベントタイプを取得する
  • AbstractEvent.Type

    • protected abstract void fireEvent(EventHandler handler, EventType.Type type)

      • ハンドラマネージャに正しいハンドラメソッドを呼び出させるためのもの。

カスタムイベントの作成ステップ

以下は、ユーザがhappyになったときにトリガーされるイベントである。

  • 1.新規のイベントクラスを作成する。イベントクラスには任意のメタデータを含ませることができるが、簡単のためにここでは何も追加しない。

    public class HappyEvent extends GwtEvent{
       ...
    }
  • 2.そのイベントクラス用の新規ハンドラとマーカーインターフェースを作成する。

    interface HappyHandler extends EventHandler {
      public void onHappiness(HappyEvent event);
    }

    interface HasHappyEvents {
      public HandlerRegistration addHappyHandler(HappyHandler handler);
    }
  • 3.ユニークなイベントタイプを指定する。

    class HappyEvent extends AbstractEvent{
      public static AbstractEvent.Key KEY = new AbstractEvent.Key(){...}
     
      public GwtEvent.Key getKey(){
        return KEY; 
      }
      ...
    }
  • 4.ハンドラのfireメソッドをwire upする。

    class HappyEvent extends GwtEvent {
      static Key<HappyEvent,HappyHandler> KEY = new Key<HappyEvent,HappyHandler>(){
        protected void fire(HappyHandler handler, HappyEvent event) {
           handler.onHappiness(event);
        };
       ...
    }

Happy Handler生成の全コード

public interface HappyHandler extends EventHandler {
  public void onHappiness(HappyEvent event);
}

public interface HasHappyEvents {
  public HandlerRegistration addHappyHandler(HappyHandler handler);
}

public class HappyEvent extends GwtEvent { 
  public final static Key<HappyEvent,HappyHandler> KEY = new Key<HappyEvent,HappyHandler>(){
    protected void fire(HappyHandler handler, HappyEvent event) {
       handler.onHappiness(event);
    }

  public GwtEvent.Key getKey(){
    return KEY; 
  } 
}

実際にはどのように動作させるのか

  • MyEventのハンドラとしてmyHandlerというイベントハンドラを登録する。

    1. ウィジェットにて
      • HandlerManagerのインスタンスhandlerManagerの存在を確認する。

      • handlerManager.addHandler(MyEvent.KEY, myHandler)を呼び出す。

    2. HandlerManagerにて

      • eventキーにてインデックスしてマップ中にmyHandlerを格納する。
      • このタイプのハンドラが初めてであれば、`source.subscribeToEvent(myEvent.KEY)'を呼び出してイベント特有のセットアップを行う。
  • MyEventイベントのfire

    1. ウィジェットにて
      • MyEventのインスタンスmyEventを作成する

      • handlerManager.fireEvent(myEvent)を呼び出す。
    2. HandlerManagerにて

      • myEvent中のsourceフィールドを正しく設定する
      • myEventから正しいイベントキーを取得する。つまり、指定されたMyEvent.KEY

      • MyEvent.KEYに紐づいたハンドラのリストを取得する

      • リスト中の各ハンドラについてmyEvent.fireEvent(handler)を呼び出す。
    3. MyEventにて

      • 現在のハンドラをMyEventHandlerにキャストする

      • 現在のハンドラの正しいハンドラメソッドを呼び出し、現在のイベントを渡す。

ハンドラシステムの問題解決

Listener Problem

Handler solution

(1) Cannot add extra information about an event

Can add metadata to event

(2) Cannot add extra features

Each handler represents a single method, so more can always be added

(3) Excess unused listener fields

A single manager field is used

(4) Excess events are sunk

Only the events that are needed are sunk

(5) Extra blank methods

No extra methods needed

(6) Boiler plate code

Between Widget enhancements and HandlerManager classes, most boiler plate code removed. Remaining boiler plate code will be inlined

(7) All event sources must be widgets

sources simply must implement the Fires* interface

(8) All widgets must manage sinking of events

All standard widgets have events sunk automatically

Migration Path

Removing all the listener methods within a single iteration of a library would be very disruptive. Instead, the listeners methods will be deprecated. In order to avoid supporting both sets of machinery within each widget, we will introduce wrapper classes to convert listeners to handlers.

Default OnBrowserEvent implementation

We already have a significant amount of boiler plate code in the libraries code to fire native dom events to listeners. The conversion of listeners --> handlers makes this boiler plate code event larger, which would lead to more code overall in GWT applications, a prospect we would rather avoid.

Additionally, by adding addHandlerAndSink() to Widget, we have successfully hidden half the complexity of dealing with native browser events. If the default onBrowserEvent just did the right thing, then it would be much easier for beginning GWT developers to create widgets directly, which leads to tighter, smaller code.

Solution

We wire up DomEvent so it can fire a native event directly on the handler manager.

We register dom event keys lazily, so that unused event types (i.e. ones where no handler is ever constructed) are still dead-stripped in the compiled output.

How it works

In Widget

 public void onBrowserEvent(Event e){
     DomEvent.fireEvent(e,getHandlerManager());
 }

In DomEvent

We introduce a specialized Type subclass with the following information:

  • A single native event type.
  • A global mapping which registers each type based on the native event type.

For instance, here is the DomEvent.Key for click events:

  /**
   * Event Key for Click.
   */
  public static final Key<ClickEvent, ClickHandler> KEY = new Key<ClickEvent, ClickHandler>(
      Event.ONCLICK) {
    @Override
    protected void fire(ClickHandler handler, ClickEvent event) {
      handler.onClick(event);
    }

    @Override
    public ClickEvent wrap(Event nativeEvent) {
      return new ClickEvent(nativeEvent);
    }
  };

In DomEvent, then we have the fire event method.

   public static void fireEvent(Event e, HandlerManager m) {
     DomEvent.Key eventKey = registeredDomKeys.get(e.getEventType());
     if(m.hasHandlersFor(eventKey)) {
        m.fireEvent(eventKey, eventKey.wrap(e));
     }
  }