ZKをはじめましょう

このドキュメントは、初めてZKを使用するユーザーのための入門チュートリアルです。 本資料はデータベースを使う簡単なウェブアプリケーションを作成し、ステップバイステップで説明します。Javaの基本知識さえあれば、ZKでAjaxウェブアプリケーションを開発することが可能です。

なお、ユーザーの皆様はJDK (1.4+)とサーブレットコンテナ(ex.Tomcat)が備わっている環境で作業することを想定しています。

初めてのZKアプリケーション (To-do List)

やるべきことを忘れないように、これらのことをリストアップし、アプリケーションに保存することにしましょう。 これが今回開発するデータベースを使う簡単なアプリケーションです。 インストールの手順を省くため、ここではJavaデータベース(HSQL DB)を使います。

実行してみましょう   Live Demo

  1. ZKのホームページからtodo.zipファイルをダウンロードし、解凍します。このファイルではWARファイルとフォルダが含まれています。
  2. アプリケーションをTomcatにデプロイするにはtodo.warを$TOMCAT_HOME/webapps/ディレクトリーにコピーします。Tomcatは自動的にファイルを解凍し、デプロイしてくれます。
  3. hsqldb フォルダ(データベースファイル)をホームディレクトリー(ex. C:\ )にコピーします。
  4. Tomcatを開始します。
  5. ブラウザを開き、http://localhost:8080/todo/todo.zul (ポート番号はTomcatの設定によって違います)を参照しますと、以下のような結果が見られます。

シナリオ

  • Addボタンをクリックすると、入力されたイベントに関する情報はデータベースに保存されます。
  • テーブルにあるイベントを選択した場合、選ばれた情報は下の入力フィールドに表示されます。情報は変更可能で、Update ボタンをクリックすると、イベントはアップデートされます。
  • テーブルにあるイベントが選択された場合、Deleteボタンで削除できます。

ワーキングディレクトリーを設定しましょう

まずは必要なZKライブラリーを適当なディレクトリーに置いたり、設定ファイルを編集したりする環境設定をします。

ワーキングディレクトリーの構造

Tomcatのウェブアプリケーションディレクトリー(ex. $TOMCAT_HOME/webapps )の下にワーキングディレクトリーを作成しましょう。 ワーキングディレクトリーの構造は以下の通りです:

  
+ProjectName
  +META-INF
  +WEB-INF
    +classes
    +lib
    web.xml
    zk.xml
  +src
  web pages  
  • WEB-INF/web.xml:サーブレットの定義や、ZKアプリを実行するのに必要なリスナーです。
  • WEB-INF/zk.xml:ZKのコンフィグディスクリプタです。(オプション)
  • WEB-INF/classes:全てのJavaクラスファイルです。
  • WEB-INF/lib:ZKライブラリーです。

    必要なjarファイルは:

  • ZK
    • bsh.jar: BeanShell Javaインタープリター
    • commons-el.jar: Apache用EL(Expression Language)インタープリター
    • zcommon.jar: ZKの共有ライブラリー
    • zhtml.jar: XHTML関係コンポーネンツ
    • zk.jar: ZKコアコード
    • zkplus.jar:統合コード: Acegi Security・Spring・Hibernate・data binding
    • zul.jar: XUL関係コンポーネンツ
    • zweb.jar: Web関係ユーティリティーコード
  • HSQLDB
    • hsqldb.jar: Hsqlデータベース
  • src/ ソースファイルです。

web.xml を設定しましょう

次に、ZKが必要なサーブレットとリスナーをweb.xmlにて設定しましょう。

 
<!-- ZK -->
  <listener>
    <description>Used to clean up when a session is destroyed</description>
    <display-name>ZK Session Cleaner</display-name>
    <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
  </listener>

  <servlet>
    <description>ZK loader for ZUML pages</description>
    <servlet-name>zkLoader</servlet-name>
    <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
    <init-param>
      <param-name>update-uri</param-name>
      <param-value>/zkau</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>zkLoader</servlet-name>
    <url-pattern>*.zul</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>zkLoader</servlet-name>
    <url-pattern>*.zhtml</url-pattern>
  </servlet-mapping>

  <servlet>
    <description>The asynchronous update engine for ZK</description>
    <servlet-name>auEngine</servlet-name> 
    <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>auEngine</servlet-name>
    <url-pattern>/zkau/*</url-pattern>
  </servlet-mapping>  


インテグレーション(Persistent)層

次の段落ではデータベースの概要・ドメインオブジェクト・DAOオブジェクトを紹介します。

データベースの概要

アプリケーションの仕様からみれば、次の属性、イベントid・イベント名・優先順位・日付が必要です。データベースの概要は以下の通りです。
FieldType
idvarchar(50)
namevarchar(50)
priorityint
datedate

ドメインオブジェクト

上記のテーブルに基づき、以下のドメインオブジェクトを作成します。
 
public class Event {
 private String id;
 private String name;
 private int priority;
 private Date date;
 
 public Event(){}
 public Event(String id,String name,int priority,Date date){
  this.id = id;
  this.name = name;
  this.priority = priority;
  this.date = date;
 }
 //getter and setter methods are ommited, please refer to the source code.
}  

DAOオブジェクト

このアプリケーションのDAOオブジェクトは以下のメソッドが必要です: findAll(),delete(),insert(),and update() .
 
public class EventDAO {
 private String url = "jdbc:hsqldb:file:/hsqldb/event";
 private String user = "sa";
 private String pwd = "";
 
 public EventDAO() {
  try {
   Class.forName("org.hsqldb.jdbcDriver");
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
 //The implementation is ommited, please refer to the source code.
 public List findAll(){} 
 public boolean delete(Event evt){} 
 public boolean insert(Event evt){} 
 public boolean update(Event evt){}
 }  


ユーザーインターフェースページ

初めてのZKコンポーネント

まずは拡張子がzulであるファイル(たとえばtodo.zul)を作成し、ウェブアプリケーションのホームディレクトリー(例: $TOMCAT_HOME/webapps/ProjectName/ )に置きます。 ZKコンポーネントを宣言する方法はHTMLと同様です。では、 window コンポーネントを宣言してみましょう。

 
<window title="To do list" width="640px" border="normal">
</window>
 

それからTomcatを開始し、ブラウザを開き、http://localhost:8080/todo/todo.zulを参照します(ポート番号は設定によって違います)。 結果は以下になります:タイトルが”To do List”になっているウィンドウです。

ZKの世界では全てのものはコンポーネンツの組み合わせにより作られています。お好きなように window のタイトル、幅、枠などを調整することができます。やり方はとても簡単で直観的なので、いくつか変更してみてください。

ZKコンポーネンツ間の階層関係

これからは、ZKコンポーネンツでページを豊富にしましょう。 テーブルを作成しますので、 listbox コンポーネントを宣言します。リストボックスは window タグの中でデータを表示させるためのコンポーネントです。

 
<window title="To do list" width="640px" border="normal">
 <listbox id="box" multiple="true" rows="4">
  </listbox>
</window>  

ここでは listboxwindow コンポーネントの子コンポーネントになっています。 つまり、ZKコンポーネンツ間では階層関係が存在します。 windowlistbox の子コンポーネントとして宣言するというように階層関係を間違えてしまうと、UIの例外が発生します。

入れ子コンポーネント

listbox の宣言の中で、id属性を使って”box”という識別子を指定します。これによって、”box”で listbox を参照することが可能になります。さらに、 listbox は入れ子構造のコンポーネントで、listhead (aka:テーブルの欄)とlistitem (aka:テーブルの列)の二つの子コンポーネントが使用できます。

 
<window title="To do list" width="640px" border="normal">
 <listbox id="box" multiple="true" rows="4">
  <listhead>
   </listhead>
   <listitem>
   </listitem>
 </listbox>
</window>  

テーブルには三つの欄が必要ですので、listheadの中に”Item”・”Priority”・”Date”の三つのlistheaderコンポーネンツを宣言しましょう。

 
<window title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
      <listheader label="Priority" width="50px" />
      <listheader label="Date" width="90px" />
  </listhead>
  <listitem>
  </listitem>
 </listbox>
</window>  

結果は以下になります。

欄が三つありますので、それぞれの列にも三つのフィールドが必要になります。listitemコンポーネントの中で三つのlistcellコンポーネンツを宣言します。

 
<window title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem>
   <listcell/>
      <listcell/>
      <listcell/>
  </listitem>
 </listbox>
</window>  
listbox コンポーネントの入れ子構造は以下の通りです。
 
+listbox 
  +listhead
    listheader
  +listitem
    listcell  

入力コンポーネンツ

listbox にイベントを表示させるには、イベント名(テキスト)・優先順位(数字)・日付(日付 )等イベントに関する情報を入力しなければなりません。そこでtextbox・intbox・dateboxを window コンポーネントの中で宣言します。

 
<window title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem>
   <listcell/>
   <listcell/>
   <listcell/>
  </listitem>
 </listbox>
  <listcell/>
  Item:<textbox id="name" cols="50" />
    Priority:<intbox id="priority" cols="1" />
    Date:<datebox id="date" cols="8"/>
    <button label="Add" width="36px" height="24px"/>
    <button label="Update" width="46px" height="24px"/>
    <button label="Delete" width="46px" height="24px"/>
</window>  

結果は:

レイアウトコンポーネント

入力するエリアを listbox と分けて表示するため、 groupbox コンポーネントを使って入力コンポーネンツをグループにします。そうすると、入力コンポーネンツは枠に囲まれます。

 
<window title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem>
   <listcell/>
   <listcell/>
   <listcell/>
  </listitem>
 </listbox>
    <groupbox>
              <caption label="Event" />
       Item: <textbox id="name" cols="50" />
       Priority: <intbox id="priority" cols="1" />
       Date: <datebox id="date" cols="8">
       <button label="Add" width="36px" height="24px"/>
       <button label="Update" width="46px" height="24px"/>
       <button label="Delete" width="46px" height="24px"/>
    </groupbox>
</window>  

上記のコードでは、 groupbox の他、 caption コンポーネントを宣言し、グループボックスの枠の上部に”Event”というラベルを表示させます。 caption コンポーネントはHTMLのレジェンド要素と類似しています。結果は以下になります。

ここまでで、ユーザーインターフェースページの作成は完了です。次はこのページをデータベースと連結させます。



UIとデータベース間の対話

シナリオではイベントを表示・新規作成・編集・削除することができます。次に、ページとデータベースとの対話を実現します。

JavaコードでZKコンポーネンツと対話

ZKのパワフルな特徴の一つは、Javaコードでページに定義されているZKコンポーネンツと対話できることです。ZKは三つの選択肢を提供しています:これらのJavaコードをページの中に埋め込むか、別のファイルに保存するか、または、Javaクラスにします。ここでは一番目の方法について詳しく説明します。

Javaコードをページに埋め込む

まずはイベントデータをデータベースから読み込みます。そして<zscript>タグを使ってJavaコードをページに埋め込みます。

zscript要素はスクリプトコードを定義する特別な要素です。zscriptはZULページがレンダリングされる度に処理され、一般では初期化、グローバル変数の宣言、グローバルメソッドの宣言で使います。ここではeventDAOのインスタンスを宣言し、結果を List に保存します。

zscript要素の中で定義されたグローバル変数はEL表記でアクセスできます。JSPと同様に、ZUMLページではELが使用できます。EL表記のシンタックスは${expr}です。

データベースアクセスを初期化する方法は以下の通りです。

 
<window title="To do list" width="640px" border="normal">
 <zscript>
    import events.Event;
    import events.EventDAO; 
    import java.util.ArrayList; 
    
    //fetch all events from database 
    EventDAO evtdao = new EventDAO(); 
    List allEvents = evtdao.findAll();  
  </zscript>      
      
 <listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem>
   <listcell/>
   <listcell/>
   <listcell/>
  </listitem>
 </listbox>
    <groupbox>
       <caption label="New" />
       Item: <textbox id="name" cols="50" />
       Priority: <intbox id="priority" cols="1" />
       Date: <datebox id="date" cols="8">
       <button label="Add" width="36px" height="24px"/>
       <button label="Update" width="46px" height="24px"/>
       <button label="Delete" width="46px" height="24px"/>
    </groupbox>
</window>  

forEachループを使う

次にlistitemコンポーネントでforEachループを使ってallEventsに保存されているデータを listbox コンポーネンツに入れます。 forEach属性は生成されるコンポーネンツの数を決定します。

集合を指定する場合、ZKローダーは同じ数のコンポーネンツを生成します。each変数は集合の中にあるオブジェクトに対応します。ここでは下のようにEL表記でオブジェクトとその属性を取得します。

 
<window title="To do list" width="640px" border="normal">
 <zscript>
  import events.Event;
  import events.EventDAO; 
  import java.util.ArrayList; 

  //fetch all events from database 
  EventDAO evtdao = new EventDAO(); 
  List allEvents = evtdao.findAll();
  </zscript>
<listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem forEach="${allEvents}" value="${each}">
   <listcell label="${each.name}" />
   <listcell label="${each.priority}" />
   <listcell label="${each.date}" />
  </listitem>
 </listbox>
    <groupbox>
       <caption label="New" />
       Item: <textbox id="name" cols="50" />
       Priority: <intbox id="priority" cols="1" />
       Date: <datebox id="date" cols="8">
       <button label="Add" width="36px" height="24px"/>
       <button label="Update" width="46px" height="24px"/>
       <button label="Delete" width="46px" height="24px"/>
    </groupbox>
</window>  

入力したデータをデータベースに保存

はじめに<zscript>の中でadd( )メソッドを宣言しましょう。
 
<zscript>
 ....
 void add(){}
<zscript>  
上記のadd( )メソッドの中に以下の三つのステップを完成させます。
  1. 入力したデータを取得
  2. データをデータベースに保存
  3. Ajaxでビューを更新

入力したデータを取得

ZKではJavaコードにあるUIコンポーネンツにアクセスできます。アクセスの方法は、id属性でコンポーネンツの識別子を指定します。 この例では、入力されたデータはtextbox(id ="name")、 intbox(id ="priority")、 datebox(id="date")、この三つの入力コンポーネンツに保存されていますので、それぞれのvalue属性にアクセスすればデータを取得できます。

 
 name.value;    //イベント名
 priority.value;   //優先順位
 date.value;     //日付  

データをデータベースに保存

データを取得したら、データベースに保存する手順はとても簡単です。

 
 Event newEvt = new Event(UUID.randomUUID().toString(),name.value,priority.value.intValue(),date.value);
 evtdao.insert(newEvt);
 
 //synchronized data object with database 
 allEvents = evtdao.findAll();  

Ajaxでビューを更新

ZKではJavaコードにより簡単にAjaxの形でビューを更新できます。ここではJavaコードで新しいリストアイテムを listbox に挿入するだけでOKです。ZKが自動的にページを更新してくれます。

 
//insert a new Event into the listbox 
Listitem li = new Listitem(); 
li.setValue(newEvt); 
li.appendChild(new Listcell(name.value)); 
li.appendChild(new Listcell(priority.value)); 
li.appendChild(new Listcell(new SimpleDateFormat("yyyy-MM-dd").format(date.value)));
box.appendChild(li);  
  • appendChild( )で子コンポーネントを挿入します。

完全なコードは以下の通りです。

 
<window title="To do list" width="640px" border="normal">
 <zscript>
  import events.Event;        
  import events.EventDAO;       
  import java.util.ArrayList;   
  import java.text.SimpleDateFormat; 
  import java.util.UUID;

  //fetch all events from database 
  EventDAO evtdao = new EventDAO(); 
  List allEvents = evtdao.findAll();
  
  void add(){ 
      //insert into database 
      Event newEvt = new Event(UUID.randomUUID().toString(),name.value,priority.value.intValue(),date.value);
      evtdao.insert(newEvt);
      
      //synchronized data object with database 
      allEvents = evtdao.findAll();
      
      //insert a new Event into the listbox 
      Listitem li = new Listitem(); 
      li.setValue(newEvt); 
      li.appendChild(new Listcell(name.value)); 
      li.appendChild(new Listcell(priority.value)); 
      li.appendChild(new Listcell(new SimpleDateFormat("yyyy-MM-dd").format(date.value)));
      box.appendChild(li);
    }  
  </zscript>

<listbox id="box" multiple="true"  rows="4">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem forEach="${allEvents}" value="${each}">
   <listcell label="${each.name}" />
   <listcell label="${each.priority}" />
   <listcell label="${each.date}" />
  </listitem>
 </listbox>
    <groupbox>
       <caption label="New" />
       Item: <textbox id="name" cols="50" />
       Priority: <intbox id="priority" cols="1" />
       Date: <datebox id="date" cols="8">
       <button label="Add" width="36px" height="24px"/>
       <button label="Update" width="46px" height="24px"/>
       <button label="Delete" width="46px" height="24px"/>
    </groupbox>
</window>  

ZKコンポーネンツにイベントリスナーを登録

ページが変化したのは、90%以上はユーザーの操作によるものです。 add( )メソッドも同じです。add( )はユーザーが”add”ボタンをクリックした場合のみ作動しなければなりません。ボタンがクリックされたかどうかを判断するには、 button コンポーネントの宣言の中ににonClickイベントリスナーを宣言し、add( )メソッドを指定します。

ユーザーがボタンをクリックすると、add( )メソッドは実行されます。とても直観的なやり方ではありませんか? ZKはいつも最もシンプルな方法を提供しています!

 
<button label="Add" width="36px" height="24px" onClick="add()" />  

add以外のコントロールコード

add以外のコントロールコードは以下になります。

  • delete() , イベントをデータベースから削除し、データモデルと listbox を更新します。
  • update() , データベースにあるイベントをアップデートし、データモデルと listbox を更新します。
  • move() , listbox にある情報をグループボックスにある入力コンポーネンツに移動します。
  • cleargp() ,グループボックスにある入力コンポーネンツのデータをクリアします。
 
 <zscript>
  import events.Event;        
  import events.EventDAO;       
  import java.util.ArrayList;   
  import java.text.SimpleDateFormat; 
  import java.util.UUID;        

  //fetch all events from database 
  EventDAO evtdao = new EventDAO(); 
  List allEvents = evtdao.findAll();

  void add(){ 
  //insert into database 
  Event newEvt = new Event(UUID.randomUUID().toString(),
    name.value,priority.value.intValue(),date.value);
  evtdao.insert(newEvt);

  //synchronized data with database 
  allEvents = evtdao.findAll();

  //insert a listEvent into the listbox 
  Listitem li = new Listitem(); 
  li.setValue(newEvt); 
  li.appendChild(new Listcell(name.value)); 
  li.appendChild(new Listcell(priority.value.toString())); 
  li.appendChild(new Listcell(new SimpleDateFormat("yyyy-MM-dd").format(date.value)));
  box.appendChild(li);
  }
  void update(){
      //update database
      Event editEvt = (Event)box.selectedItem.value;
      editEvt.setName(name.value);
      editEvt.setPriority(priority.value);
      editEvt.setDate(date.value);
        evtdao.update(editEvt);
      
      //update listbox
      List children = box.selectedItem.children;
      ((Listcell)children.get(0)).label = name.value;
      ((Listcell)children.get(1)).label = priority.value.toString();
      ((Listcell)children.get(2)).label = new SimpleDateFormat("yyyy-MM-dd").format(date.value);
  } 
  void delete(){
      evtdao.delete((Event)box.selectedItem.value);
      box.removeItemAt(box.getSelectedIndex());
      cleargb();
  }
  void move(){
       name.value = ((Event)box.selectedItem.value).getName();
       priority.value = ((Event)box.selectedItem.value).getPriority();
       date.value = ((Event)box.selectedItem.value).getDate();
  }  
  void cleargb(){
      name.value = null;
      priority.value = null;
      date.value = null;
  }
 </zscript>  

必要なイベントリスナーを追加

最後のステップはZKコンポーネンツに必要なイベントリスナーを追加します。
  • 削除ボタンに onClick イベントリスナーを追加
  • 編集ボタンに onClick イベントリスナーを追加
  • listboxonSelect イベントリスナーを追加
 
 <window title="To do list" width="640px" border="normal">
 <zscript>
   //the implementation part is ommited, please refer to the above listing.
   void add(){}
   void delete(){}
   void update(){}
   void move(){}
   void cleargb(){}
 </zscript>
 
 <listbox id="box" multiple="true" rows="4"
  onSelect="move()">
  <listhead>
   <listheader label="Item" />
   <listheader label="Priority" width="50px" />
   <listheader label="Opened" width="90px" />
  </listhead>
  <listitem forEach="${allEvents}" value="${each}">
   <listcell label="${each.name}" />
   <listcell label="${each.priority}" />
   <listcell label="${each.date}" />
  </listitem>
 </listbox>
 <groupbox>
  <caption label="Event" />
  Item: <textbox id="name" cols="25" />
  Priority: <intbox id="priority" cols="1" />
  Date: <datebox id="date" cols="8" />
  <button label="Add" width="36px" height="24px" onClick="add()" />
  <button label="Update" width="46px" height="24px" onClick="update()" />
  <button label="Delete" width="46px" height="24px" onClick="delete()" />
 </groupbox>
 <window>  


サマリー

このドキュメントではZKを用いて簡単なウェブアプリケーションを作成する基本知識を説明しました。

項目別の詳細資料は以下の通りです。

.

SourceForge.net