【ZK-0002】ZK開発者リファレンス:イベントハンドリング

 このコンテンツは下記のサイトのコンテンツを私的に翻訳したものです。

 イベントは、ユーザーやアプリケーションからの通知など抽象的な"活動(Activity)"を通知するのに使われます。アプリケーションでは、イベントの種類と発生元を判別し、処理を切り分ける事ができます。通常、開発者はメッセージキューといわれる方法で、イベントを管理します。ここでは、イベントをコントロールする方法を学びます。

Event Handler(イベント・ハンドラー)を使用する

 イベント・ハンドラーはZKのページやそのメンバーとなるコンポーネントのクラスにイベント属性として定義されているメソッドです。

◆Event Handler(イベント・ハンドラー)をZUMLで宣言する

 イベント属性の定義を使用することで、ZULページでイベント・ハンドラ―を宣言する事ができます。例えは次のようになります。

1:<button label="hi" onClick='alert("Hello")'/>

 イベントハンドラ―にの中にはJavaのコードが書かれています。イベントハンドラ―は、BeanShellを使用して、実行時に解釈されます。もし、あなたが他の言語を使用したい場合は、その言語を前に記述する事ができます。例えばGroovyを使用する場合は次のように記述します。

1:<button label="hi" onClick="groovy:alert('Hi, Groovy')"/>

  • この例では、イベントを受け取るコンポーネントはボタンを想定しています。
  • この例では、発動されたイベントの種類はMouseEventを想定しています。

この方法は、実行時にコードが解釈されることに注意して下さい。そのため、インタープリタ型のプログラム言語のメリットとデメリットがそのまま当てはまります。

【メリット】

  • 変更の際にコンパイルやアプリケーションの再起動を行う必要がありません。
  • コードスニペット(code snippet)であり、とても短いので、保守するのが簡単です。

【デメリット】

  • 実行速度が遅いです。
  • 前もってコンパイルエラーを知ることができません。
  • 複雑なビジネスロジックをもつUIの場合、保守するのが大変になる場合があります。

【提案】

  • 一般的な使い方として、1.プロトタイプに使用する。 2.簡単なイベントのハンドリングに使用するが提唱されています。

 

◆Event Handler(イベント・ハンドラー)をJavaで宣言する

 他の方法として、コンポーネントのクラスでイベントハンドラ―を宣言する事ができます。例えば次のように記述します。

public class MyButton extends Button {

    public void onClick() {

        Messagebox.show("Hello");

    }

}

 もし、イベント・ハンドラ―がイベントのハンドリングを行う必要がある場合、次のように引数を宣言します。

public class MyButton extends Button {

    public void onClick(MouseEvent event) {

        Messagebox.show("Hello, "+event.getName());

    }

}

Event Listener(イベント・リスナー)によるイベントのリスニング

◆Event Listener(イベント・リスナー)

 イベント・リスナーはEventListenerクラスを実装したクラスです。

public class MyListener implements EventListener {

    public void onEvent(Event event) {

        Messages.show("Hello");

    }

}

 イベントを受け取ることのできるコンポーネントに" Component.addEventListener(String, EventListener)"のように記述する事で、イベント・リスナーを登録する事ができます。例えば次のように記述します。

button.addEventListener("onClick", new MyListener());

 これは、イベントをハンドリングする典型的な方法です。しかしながら、イベント・リスナーが沢山ある場合、ひとうひとつ登録していくには、記述が少し冗長です。そのため、次に説明するComposer(コンポ―サー)を使用する方法を提唱します。

 

◆Composer(コンポーザー)を使用して、 Event Listener(イベント・リスナー) と自動的に結びつける(Autowiring)

 ZK Developer's Reference/MVCでは、通常イベント・リスナーをわざわざ手で登録する必要はありません。コンポ―ザーの機能である、"auto-wiring(オートワイアリング)"により、イベント・リスナーの登録は自動化されます。例えば次のように記述します。

public class MyComposer extends SelectorComposer {

    @Listen("onClick = button#hi")

    public void showHi() {

        Messsagebox.show("Hello");

    }

    @Listen("onClick = button#bye")

    public void showBye() {

        Messsagebox.show("Bye");

    }

    @Listen("onOK = window#mywin")

    public void onOK() {

        Messsagebox.show("OK pressed");

    }

}

 上記の例では、Listenのアノテーション(注釈)が付けられて、メソッドが呼び出されるようになっています(他の例はSelectComposerを参照して下さい)。イベント・リスナーが自動選択できるように、コンポーザーには、アノテーションが付された各々のメソッドを記述しておきます。その際にZULページには、コンポーザーが結びつくコンポーネントを特定できるように属性を設定しておきます。

<window id="mywin" apply="MyComposer">

    <textbox/>

    <button id="hi"/>

    <button id="bye"/>

</window>

 制限は特にありませんが、通常はID属性を、コンポーザーがコンポーネントを特定するのに使用します。さらなる情報は、Wire Event Listenersを参照して下さい。

◆Deferrable Event Listeners(執行猶予イベント・リスナー)

 通常は、クライアンからイベントが発動された時は、イベントはサーバーに送られます。サーバーには多くのイベントリスナーががあり、ユーザーとやり取りしているかもしれません。言い変えると、すぐにイベントを送信する必要がない場合は、一度データ送信料を最小にしてから、クライアントからサーバーにイベントを送信すれば、サーバーのパフォーマンスを改善する事ができます。このようなために、Deferrable Event Listeners(執行猶予イベント・リスナー)を使用する事ができます。

 執行猶予イベント・リスナーを創るためには、"Deferrable(with EventListener)"を実装します。そして、isDeferrable メソッドで"true"を返します。次のように記述します。

public class DeferrableListener implements EventListener, Deferrable {

    private boolean _modified;

    public void onEvent(Event event) {

        _modified = true;

    }

    public boolean isDeferrable() {

        return true;

    }

}

 リスト選択項目をユーザーが選択するなど、クライアントでイベントが発動されると、イベント・リスナーが登録されていないか、もしくは執行猶予イベント・リスナーしか登録されていない場合は、ZKはイベントを送信しません。

 一方で、少なくとも1つの執行猶予イベント・リスナーではないイベント・リスナーが登録されたら、イベントは直ちに送信されて、すべてのイベントリクエストがサーバーに送信されます。予め決められた順番にイベントは送信されて、イベントが実行されない(ロストする)事はありません。

◆Page-level Event Listener(ページレベルのイベント・リスナー)

 開発者はイベント・リスナーをPagePage.addEventListener(String, EventListener)を使用する事で動的に追加する事ができます。

 ページに登録されるすべてのイベント・リスナーは(べージレベルのイベント・リスナー)は、Deferrableクラスを実装していなくても執行猶予のイベント・リスナーと仮定されます。ページレベルのイベント・リスナーが使用される典型的な例としては、次のような修正フラグを更新するの場合です。

page.addEventListener("onChange", new EventListener() {

    public void onEvent(Event event) {

        modified = true;

    }

});

Precedence of Listeners(リスナーの実行順序)

 リスナーの実行順序は次の通りです。

  1. Expressを実装しているイベント・リスナーで、Component.addEventListener(String, EventListener)で登録されている。
  2. ZUMLの定義によってイベントがハンドリングされている。
  3. イベント・リスナーがComponent.addEventListener(String, EventListener)で登録されており(※Expressは実装していない)、GenericForwardComposerに結びついているコンポーザーのメソッドを含む場合。
  4. イベント・ハンドラ―がクラスメソッドとして定義されている場合。
  5. ページレベルのイベント・リスナー。

◆Abort the Invocation Sequence(イベントの実行を中止する)

 Event.stopPropagation() を使用することで、イベントの実行を中止する事ができます。このメソッドが一度、あるクラスから実行されると、その後のイベント・ハンドラ―とリスナーは無視されます。

 イベントは通常、コンポーネントにより発動されます。しかしながら、アプリケーションがイベントを発動する事もできます。イベントの発動には3つの方法があります。

Post an Event

 "Post"は、イベントを発動するのにもっとも使用されます。Postする事により、イベント・キューの最後に追加されます。イベントは、イベント・キューの中で貯められて、ひとつひとつ先入れ先出し法により実行されます。各々のデスクトップ(desktop)には、ひとつのイベント・キューがあり、すべてのイベントを順番に処理しています。

Events.postEvent("onClick", button, null); //simulate a click

 イベント・キューの最後にイベントをPostすることに加えて、 Events.postEvent(int, String, Component, Object)を使用する事で、順番を指定する事ができます。初期値は0です。優先順位が高くなるほどイベントが早く処理されます。

 イベント・キューの順番を変更する事で、イベントの実行結果としても戻り値の取り扱には注意して下さい。

※イベント・キューは、アプリケーションの開発者からは見えません。


Send an Event

 イベント・キューに登録して実行を持つよりも早く、直接的にすぐにイベントを実行したい場合には、 Events.sendEvent(String, Component, Object)を使用する事ができます。

Events.sendEvent("onMyEvent", component, mydata);

  Events.sendEvent(String, Component, Object)は、このイベントに登録されているすべてのイベント・ハンドラ―とイベント・リスナーの処理が終わるまで戻ってきません。それは1つのメソッドを実行しているようなイメージです。イベント・ハンドラ―とイベント・リスナーはイベント・スレッドを開始する事無く、直接実行される事にも注意して下さい。※イベント・スレッドはEvent Threadsセクションを参照して下さい。


Echo an Event

  Echoは、イベントの処理を次の非同期リクエストを受け取るまで遅らせる方法です。もっと正確にいうと、Echoイベントは、イベント・キューには登録されません。それは直ちに非同期のリクエストとして、クライアントよりSend Backされます。そらに、非同期のリクエストを受け取った後で、イベントはイベント・キューに登録され、処理されます。

 言い変えると、Echoイベントとは現在のExecutionでは処理されず、次の非同期リクエストでクライアントよりEcho Backされて処理されます。次に、Events.echoEvent(String, Component, Object)の例を記載します。

Events.echoEvent("onMyEvent", component, mydata);

 Echoイベントは、長期のOperetionの実装によく使われます。HTTPのリクエストとレスポンスのプロトコルは、ユーザーはリクエストを送信してレスポンスが帰ってくるまで何も受け取りません。そのため、Echoイベントを使用する事で、開発者はメッセージをやり取りしてユーザーに何が起こっているのか通知する事ができます。これ以上の情報は、 the Long Operations: Use Echo Events を参照して下さい。

Event Forwarding(イベントの転送)

概要

 簡単なプログラミングのためには、複雑なイベントフローは導入すべきではありません。イベントがターゲットのコンポーネントに届けられたと時、ターゲットのコンポーネントに登録されているイベント・リスナーだけが呼び出されます。必要であれば、他のコンポーネントへイベントを転送する事もできます。


Event Forwarding in Java(Javaでのイベントの転送)

 イベントを直接転送します。ただもう一度PostもしくはSendするだけです。composerを使用するのは良い方法です。composer(コンポーザー)はイベントをハンドリングする拠点になります。例えば、下記のコードで、メニュー項目とボタンを押すことでイベントがハンドリングされて、openDialogメソッドを実行する事ができます。

public class FooComposer extends SelectorComposer {

   @Listen("onClick = menuitem#item1; onClick = button#btn")

   private void openDialog() {

     //whatever you want

   }

}

Event Forwarding in ZUML

 ZUMLのthe forward attributeを使うと、イベントを転送する事ができます。例えば…

<window id="mywin">

    <button label="Save" forward="onSave"/>

    <button label="Cancel" forward="onCancel"/>

</window>

 Saveボタンを押した時に、windowはonSaveイベントを受け取ります。この方法は、イベントとコンポーネントの間に、抽象的なレイヤーを導入する事ができます。例えば、windowは、どのコンポーネントからイベントが発動したかに関係なく、onSaveイベントだけをハンドリングする必要がある場合などです。したがって、他のUIから発動されたonSaveイベントもイベント・リスナーを修正する事無く導入する事ができます。

<menuitem label="Save" forward="onSave"/>

 もちろん、composerとZUMLの転送も一緒に使用する事も可能で、よりメンテナンスがしやすいコードにする事ができます。

public class BetterComposer

extends org.zkoss.zk.ui.select.SelectorComposer {

    @Listen(onSave = #mywin)

    public void doSave(ForwardEvent event) { //signature if you care about event

        ...

    }

    @Listen(onCancel = #mywin)

    public void doCancel() { //signature if you don't care the event

        ...

 上記の例は、ForwardEventのインスタンスをラッピングしてイベントの転送をしている事に注意して下さい。オリジナルのイベントを扱うために、ForwardEvent.getOrigin()メソッドが用意されています。

Using a component Path(コンポーネント・パスを使用する)

 ZUMLページの中に、特別なイベントを特別なターゲットコンポーネントに転送するために、component path(コンポーネント・パス)を使用する事ができます。これは、異なるIdSpaceにイベントを転送する特別な手段です。

<?page id="mainPage" ?>

<window id="mainWindow" apply="BetterComposer">

...

    <include src="incDetails.zul" />

...

</window>

 Pathを指定してページをインクルード(include)し、WindowコンポーネントであるmainWindowにイベントを転送します。

<button forward="//mainPage/mainWindow.onSave" /> <!-- default forward event is onClick -->

 下記に括弧でかこったforward属性で、アプリケーションの特別なデータを特定できます。

<button forward="onCancel(abort)"/><!-- "abort" is passed -->

<button forward="onPrint(${inf})"/><!-- the object returned by ${inf} is passed -->

 ForwardEvent.getDate()メソッドでアプリケーションの特別なデータを取得する事ができます。

 ZKのMVCコントロールで、ZUML(.zul)のForward属性を使用する時、getOrigin()メソッドで、オリジナル・イベントを取得しなければなりません。そうすれば、getData()メソッドでデータにアクセスする事ができます。

Example:ZUL

<tabbox id="ctrl" apply="composer1">

  <tabs>

     <tab id="tb1" label="News" forward="ctrl.onSelectTab(0)"></tab>

     <tab id="tb2" label="News Images" forward="ctrl.onSelectTab(1)"></tab>

  </tabs>

</tabbox>

Example Composer(composer1)

@Listen("onSelectTab = #ctrl")

public void doChangeTab(ForwardEvent e) { 

    MouseEvent me = (MouseEvent) e.getOrigin();

    System.out.println(me.getData());

}

 一度にいくつかのイベントを転送したい場合、forward属性でカンマ(,)で区切ることにより行う事ができます。例えば…。

<textbox forward="onChanging=onUpdating, onChange=some.onUpdate"/>

 加えて、ターゲットのコンポーネントとイベントのデータは、EL expressionsでも定義する事ができます。

Event Queues(イベント・キュー)

 イベント・キューは、イベントの発動とイベント・リスナーを利用した、アプリケーションの情報伝達とメッセージングのソリューションです。

イベント・キューは翻訳作業中です。

概要

Identification of an Event Queue

◆Locate an Event Queue

◆The Scope of an Event Queue

Publish and Subscribe

◆Publish an Event

◆Subscribe to an Event Queue

◆Example: Chat

◆Example: interactive between multiple ZUL pages

Asynchronous Event Listener

◆More About Scopes

Client-side Event Listening

概要

 ZKは、アプリケーションにサーバー側とクライアント側の両方でイベントをハンドルする事を許可しています。サーバー側でイベントをハンドリングする事は、ここまでのセクションで記述してきたように、イベント・リスナーはバックエンドのサービスに直接アクセスできます。

 クライアント側でイベントをハンドリングする事は、リスポンスの改善につながります。例えば、コンボボックスにフォーカスが当たった時に、ドロップダウンリストをオープンにしたい場合、クライアント側のリスナーで処理するようにした方が良いでしょう。

 よいルールとして提唱しているのが、まずはじめは簡単なのでサーバー側のリスナーでイベントをハンドリングし、レスポンスの改善が必要なクリティカルな部分については、クライアント側のリスナーでハンドリングするようにします。

 イベントのハンドリングについてのさらなる情報は、ZK Client-side Reference: Event Listening.を参照して下さい。