ZK Web フロー

From Documentation

Jump to: navigation, search
  • Author
    Henri Chen, Principal Engineer, Potix Corporation
  • Date
    June 16, 2009
  • Version
    ZK Web Flow 1.0 RC

Contents

[hide]

イントロダクション

これは ZK と Web フローシステムに関する記事の2弾目です。前回の記事で、私は ZK をスプリング Web フローと共に動作させる方法について述べましたが、それについて沢山のフィードバックを受けまして、そこでは XML 構成の複雑さについての不満が最も報告されていました。私はもっと、このような XML 構成問題に同意することは出来ません。これらの複雑さは実にスプリング Web フローそのものから来ているのです。というより、スプリング MVC から最も起因していると言いたいのです。
システムをシンプルにする方法を考えると、私たちはこのような XML マターにするよりも、ピュア Ajax ベースの web フローシステムを実装することが実際的に可能なことに考え付きました。あの記事で述べたように、スプリング MVC は Ajax がポピュラーになる以前のページ志向やフォーム-ベースのナビゲーションに頼っています。 ZK がイベント志向のナビゲーションが可能なのに、スプリング Web フローは ZK や ZK スプリング Webフローができることに制限をかけています。
そこで、私たちはスプリング Web フローフレームワークから離れ、その代わりに我々が持つ ZK のデザインの優れたコンセプトを採用して、 ZK Web フローシステムを創ることにします。そして、この記事は私たちが到達した地点を示します。

サンプル

これは前回と同じスプリング Web フロー2.0.3からの「ホテルサーチと予約」のサンプルですが、ページのビューとフロー定義ファイルを ZK Web フローシステムで書き直しました。次のデモを見てください。

デモ


XML 構成ファイル無し

このシステムをデザインするに当たって、私たちは次の3つのルールを定めました。

  • アプリケーション依存で必要なフロー定義ファイルを除いては、余分な XML 構成ファイルを無くする事。
  • フロー定義の XML スキーマは zul ページのそれに似たものにすること。これは、 ZK の zul ページに慣れているならば、 ZK Web フロー定義フィルのデザインに慣れるべしということです。
  • スプリング Web フロータグの名前付けを再使用して、スプリング Web フローになれた人たちが ZK Web フローシステムに早くなれることができるようにすること。

ZUL ページでシンプルに

それでフローについて何処から始めましょうか? 次は入り口のページ(index.zul)ですが、これはレイアウトページでもあります。これは Ajax ベースの Web フローシステムであることを思い起こしてください。従来のコンセプトの共通レイアウトページは使用しません。(複数のページから単一の共通レイアウトデザインに切り替えます。)その代わりに、シングル-レイアウト ページのコンセプトを使用します。(部分的なコンテンツから単一の主レイアウトページへの切替)

index.zul

<?page title="ZK Web Flow Sample Application" contentType="text/html;charset=UTF-8"?>
<?init class="org.zkoss.zwf.FlowHandler" arg0="/WEB-INF/flow/main/main.xml"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver" ?>
<zk xmlns="http://www.zkoss.org/2005/zul">
<zscript>
<![CDATA[
//@IMPORT
import org.zkoss.zwfdemo.samples.booking.*;
]]>
<![CDATA[
    //TODO, Take out following code when security is done
    User keith = new User("Keith", null, "keith");
    User currentUser = keith;
]]>
</zscript>
<window width="800px" height="100%" style="margin: 0 auto;">
<borderlayout>
<north size="60px">
    <borderlayout>
        <center border="none" style="background-color:#B1CBD5"><html><![CDATA[<h2 style="color:#0C7A9A;margin-left:10px">ZK Hotel Booking Sample Application</h2>]]></html></center>
        <east border="none" style="background-color:#B1CBD5"><toolbarbutton src="/img/zkpowered_s.png" href="http://www.zkoss.org"/></east>
    </borderlayout>
</north>
<south style="background-color:#B1CBD5">
<span style="font-size:small">Copyright © 2009 Potix Corporation. All rights reserved.</span>
</south>
<west>
    <toolbarbutton src="/img/hotel.jpg" href="http://www.zkoss.org" />
</west>
<center>
    <div id="working" self="@{view(content)}"/> <!-- working view -->
</center>
</borderlayout>
</window>
</zk>

サンプルコードの index.zul で見るように、メインレイアウトページにシンプルに FlowHandler を定義して、フロー定義ファイル (main.xml) が何処に有るか知らせ、適切な working view に (@{view(content)} でアノテーションを付加します。これで完了です。
メインレイアウトページをブラウズすると、 FlowHandler が指定された位置からフロー定義を読み出して、 main.xml で定義されたフロー定義に従って.メインレイアウトページの updateworking view を開始します。

Image:zwfdemo_01.png

ZUL ページをシンプルに定義する

そこでどのようにフロー定義ファイルを定義しましょうか? 次に示すのはフロー定義ファイル main.xml のリストです。

main.xml

<?xml version="1.0" encoding="UTF-8"?>
<flow id="main" onEntry='flowScope.put("searchCriteria", new org.zkoss.zwfdemo.samples.booking.SearchCriteria())'>
    <view-state id="enterSearchCriteria" onEntry='stateScope.put("bookings", bookingService.findBookings(currentUser.name))' 
        uri="enterSearchCriteria.zul, bookingSection.zul">
        <transition id="search" to="reviewHotels" >
            <attribute name="onTransit">
                searchCriteria.searchString = searchString.value; 
                searchCriteria.pageSize = pageSize.getSelectedItem().getValue(); 
                searchCriteria.resetPage();
            </attribute>
        </transition>
        <transition id="cancelBooking" onTransit='bookingService.cancelBooking(componentScope.get("booking"));'/>
    </view-state>
 
    <view-state id="reviewHotels">
        <attribute name="onEntry">
            stateScope.put("hotels", bookingService.findHotels(searchCriteria)); 
            stateScope.put("hotelsCount", bookingService.findHotelsCount(searchCriteria));
        </attribute>
        <transition id="sort" onTransit='searchCriteria.sortBy = componentScope.get("sortBy"))' />
        <transition id="paging" onTransit='searchCriteria.page = paging.getActivePage()' /> 
        <transition id="select" to="reviewHotel" onTransit='flowScope.put("hotel", componentScope.get("hotel"))' />
        <transition id="changeSearch" to="changeSearchCriteria" />
    </view-state>
 
    <view-state id="reviewHotel">
        <transition id="book" to="bookHotel" />
        <transition id="cancel" to="enterSearchCriteria" />
    </view-state>
 
    <subflow-state id="bookHotel" subflow="../booking/booking.xml" onEntry='flowScope.put("hotelId", hotel.id)'>
        <transition id="bookingConfirmed" to="finish" />
        <transition id="bookingCancelled" to="enterSearchCriteria" />
    </subflow-state>
 
    <view-state id="changeSearchCriteria" uri="enterSearchCriteria.zul" popup="yes" >
        <transition id="search" to="reviewHotels" bookmark="no">
            <attribute name="onTransit">
                searchCriteria.searchString = searchString.value; 
                searchCriteria.pageSize = pageSize.getSelectedItem().getValue(); 
                searchCriteria.resetPage();
            </attribute>
        </transition>
    </view-state>
 
    <end-state id="finish" />
</flow>

Web フローは基本的に states で構成されています。各々の state の中で、フローエンジン (FlowHandler)に前もって定義した flow action(言い換えれば遷移の id )に応じて、何処へ遷移すべきかを定義できます。しかも、Web フローのライフサイクル内で、ある種の評価をすることが出来、フロー実行中に onXxx のようなイベントハンドラや、 ZK の zul ページで使用する <zscript/> タグを使用して実行内容を指定できます。ここでも私たちは ZK のスクリプトメカニズムを使用できますので、フロー定義ファイル内で好みのスクリプト言語例えば. Java(BeanShell), Groovy, JavaScript, Ruby, もしくは Python が ZK フレームワークで既にサポートされているので使用することが出来ます。
以下にフロー定義関連のタグと属性を手短に解説します。

Flow コンポーネント

これらはフロー定義で使用される要素です。

  • <flow>: flow または subflow を定義します。フロー定義ファイルのルートタグです。
  • <view-state>:view state を定義します。各々の view state は1つもしくは複数の zul コンテンツにマッピングされています。それらをオプションの uri 属性の中に定義できます。それが省略されたときエンジンは view state の id と同じ名前の zul ファイルにマッピングします。(たとえば、 <view-state id="reviewHotels"/> であれば、main.xml フロー定義ファイルと同じフォルダー内の reviewHotels.zul ファイルにマッピングされます。)
  • <subflow-state>: subflow に遷移することを定義します。subflow 定義を subflow 属性で定義できます。属性は絶対( / スラッシュで始まる)か、メインフローパスに対する相対パスで定義されます。state が subflow に遷移するとき、subflow はフローエンジンによってロードされ、実行されます。subflow 内で subflow を再帰的に定義できることに注意。
  • <end-state>: はフローの最終を定義します。最終 state に到達したとき、フローエンジンは現在の flow もしくは subflow から離れます。
  • <action-state>: は test 属性の結果によっての遷移実行を定義します。通常ある値もしくは計算結果の条件によって遷移を決めるときに使われます。
  • <transition>: は遷移アクションを定義します。これは常に states の下にあります。id はどのフローアクションがどのフローアクションを起動するかを知らせます。オプション属性の to="target-state" は指定された遷移が実行されたとき、どの target state へ行くかを定義します。もし、属性 to が省略されたとき、同じ state への遷移と同様です。ということは、state の onExit と state の onEntry がその順序で、依然として起動されるだろう事を意味します。 遷移アクションの起動の仕方については、後でサンプルの zul ページで説明します。

Flow イベント

これらは関連するフローイベントリスナーを書いて、フローをカスタマイズする為の何らかの事を出来るフローイベントです。

  • onEntry: onEntry フローイベントはフローに入ったときや、state に入ったときに xxx-state が起動されたときに flow に対して起動されます。フローデザイナーはこのようにしてフロー実行に介入してカスタマイズできます。たとえば、onEntry ハンドラ内で何らかのデータモデルの準備や、state 間で情報の受け渡しができます。
  • onExit: onExit フローイベントはフローから出るときや、state から出る際に xxx-state が起動されたときに flow に対して起動されます。フローデザイナーはこのようにしてフロー実行に介入してカスタマイズできます。たとえば、 onExit ハンドラ内で何らかのデータの保存や、他システムへの何らかの通知ができます。
  • onTransit: onTransit フローイベントは遷移が実行されたとき、 transition に対して起動されます。フローデザイナーはこのようにしてフロー実行に介入してカスタマイズできます。たとえば、 onTransit ハンドラ内で何らかの遷移に関する操作を実施できます。

暗黙のFlow オブジェクト

これらはフローをカスタマイズするに際してアクセス可能な暗黙のフローオブジェクトです。暗黙のフローオブジェクトは .zul ファイルやフロー定義ファイル内でアクセス可能です。そして、暗黙の zul オブジェクト(desktop, page, self, sessionScope, desktopScope, ...,etc..)もフロー定義ファイル内てアクセス可能です。それ故、これらの暗黙オブジェクトを view ページやフロー state 間の情報受け渡しの為に使用できます。

  • flow: 現在実行中のフロー
  • topFlow: フローエンジンが実行した入り口のフロー(subflow ではない)
  • parentFlow: 現在実行中のフローの親のフロー。topFlow が現在実行中のフローの場合、null の場合があります。
  • state: 現在実行中の state 。
  • flowScope: 現在実行中のフローに関連した属性マップ。フローに入ると生成され、フローから出ると消されます。
  • flashScope: 現在実行中のフローに関連した別の属性マップ。フローに入ると生成され、フローから出ると消されます。また、view state をレンダーした後、クリアされます。
  • topFlowScope: topFlow に関連した属性マップ。topFlow に入ると生成され、topFlow から出ると消されます。topFlow が現在実行中のフローならば、 topFlowScope は flowScope と同じです。
  • parentFlowScope: 現在実行中のフローの親のフローに関連した属性マップ。topFlow が現在実行中のフローの場合、null の場合があります。
  • stateScope:現在実行中の state に関連した属性マップ。

最初のView ステートのロード

最初にメインレイアウトページがロードされたとき、フローエンジンは最初の <view-state/> の内容を working view 領域にレンダーします。我々のサンプルでは、それは2つの UI.zul 定義ファイル enterSearchCriteria.zulbookingSection.zul を持つ view state の enterSearchCriteria view state です。(<view-state/> の uri をご覧ください。)フローエンジンは 指定された .zul ファイルを順番にレンダーします。

Image:zwfdemo_02.png


enterSearchCriteria.zul

<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
<zk>
    <div id="hotel search" onOK="" self="@{action(search,when=onOK)}">
        <html><![CDATA[<h2>Search Hotels</h2>]]></html>
        <grid>
            <columns>
                <column width="200px"/>
                <column/>
            </columns>
            <rows>
                <row>
                    Search String: 
                    <textbox id="searchString" value="${searchCriteria.searchString}" tooltiptext="Search hotels by name, address, city, or zip." />
                </row>
                <row>
                    Maximum Results:
                    <listbox id="pageSize" rows="1" mold="select">
                        <listitem forEach="${referenceData.pageSizeOptions}" value="${c:int(each)}" label="${each}" selected="${c:int(each) == searchCriteria.pageSize}"/>
                    </listbox>
                </row>
                <row spans="2" align="right">
                    <button id="findHotels" label="Find Hotels" onClick="" self="@{action(search)}"/>
                </row>
            </rows>
        </grid>
    </div>
</zk>

ワーキングViewコンテンツの更新

ユーザーがボタンを押すか、textbox のテキストを更新すると、フローエンジンは前もって定義されたflow action に従って state の遷移をするようにトリガーされます。私たちは ZK UI コンポーネントに @{action({action}[, when={ZK-event}]} のアノテーションを付加して、エンドユーザーから関連する ZK-イベントが起動されたとき、どのフローアクションを起動すべきかを指定します。when 引数はオプションですが、デフォルトで onClick を意味するので、ボタンやツールボタンその他のコンポーネントでは指定する必要はありません。 enterSearchCriteria.zul で定義しましたように、私たちは Find Hotels ボタンに self="@{action(search)}". でアノテーションを付加します。エンドユーザーが Find Hotels ボタンを押すと、フローエンジンのサーチフローアクションが起動されます。 main.xml ファイルのフロー定義により、 onTransit フローイベントハンドラがコールされ、state は次の state -- reviewHotels.に遷移します。

Image:zwfdemo_03.png

自動履歴マネジメント

これはピュア Ajax ベースの Web フローシステムなので、自動的に履歴ブックマーク機能を使用できます。ということは、エンドユーザーは以前のステータスを取り戻すのにブラウザの back もしくは forward ボタンを押すことができるということです。もちろん、ユーザーがこのような自動履歴登録を望まない場合があります。ユーザーはシステムの履歴登録の粒度をコントロールできます。
bookmark は、システムが何であれ、現在のフローコンテクスト(データ)のスナップショットです。そしてそれで、エンドユーザーはブラウザの back ボタン もしくは forwardボタンを押すことにより、フローコンテクストのスナップショットからビューを取り戻すことができます。それで、同じビュー結果を取り戻すことを保証するために、 .zul ページはフローコンテクストすなわち flowScope, stateScope等からデータを使用するようにデザインされなければなりません。

Viewの取り戻し

Image:zwfdemo_04.png

サマリー

ある意味で、フロー定義をデザインすることは何かしら MVC プログラミングを XML のコントローラ流 で書いているようなものです。それでは、 Web フローシステムを使用するのに煩わされるより、独自のフローコントローラを書いても良いのではいのでしょうか? 私は最もリーズナブルな答えは、良いフレームワークというのは開発者が最初から実装するための苦労とかアプリケーションをメンテナンスする多大な苦労を大幅に軽減出来るもので有るべきだということです。フレームワークはアプリケーションの最も共通で使用されるパーツをカバーするのに使用され、アプリケーションをカスタマイズすることができるように、「柔軟性を追加するのが充分に容易」であるべきだと思います。充分に容易にという用語は全てのフレームワークのキャッチフレーズです。ZK Web フローシステムを楽しんでいただけたでしょうか。より使い易くするためにフィードバックをお待ちしています。

Download




Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.