Page その1

画面に対して、Pageを継承したクラスを用意する。

こんなクラス図。(http://click.sourceforge.net/images/click-class-diagram.png
Contextオブジェクトには、コンテキスト情報が含まれている。requestやらsessionやらを使用したい場合などに使用する。
メソッドは後述。
controlsとmodelがキモ?
modelはVelocityTemplateで表示するためのオブジェクト。これをMapで保持。Controlも対象
Page自体は毎回のリクエスト毎に初期化される。(新しいインスタンス作成)

本当?少なくともGetの場合はそうっぽい。POSTはサンプルの動作見てる限り、違いそう

Page セキュリティ

onSecurityCheckメソッドが用意され、securityの拡張ポイントがある。
実際のコードは用意してあるものを利用すればよい。

Application Authentication

ログインページで、認証を行い、成功した場合はsessionを作成するアプリケーションがあるとする。ログインページ以外では、sessionの有無をチェックする。

public class Secure extends Page {
    _/**
     * @see Page#onSecurityCheck()
     */
    public boolean onSecurityCheck() {
    
        if (getContext().hasSession()) {
            return true;
            
        } else (
            setRedirect("login.htm");
            return false;
        }
    }
} 

Container Authentication

コンテナの認証を利用するのであれば、以下のようにすればよい

public class Secure extends Page {
    _/**
     * @see Page#onSecurityCheck()
     */
    public boolean onSecurityCheck() {
    
        if (getContext().getRequest().getRemoteUser() != null) {
            return true;
            
        } else (
            setRedirect("login.htm");
            return false;
        }
    }
}

Container Access Control

ロールによるアクセス制限を利用するのであれば、以下のようにすればよい

public class AdminPage extends Page {
    _/**
     * @see Page#onSecurityCheck()
     */
    public boolean onSecurityCheck() {
    
        if (getContext().getRequest().isUserInRole("admin")) {
            return true;
        } else (
            setRedirect("login.htm");
            return false;
        }
    }
}

LogOut

ログイン成功したらsessionを作成するようなアプリケーションのモデルにした場合、ログアウトは以下のようにすればよい

public class Logout extends Page {
    _/**
     * @see Page#onInit()
     */
    public boolean onInit() {
        getContext().getSession().invalidate();
    }
} 

Page の method

  • onInit()
  • onSecurityCheck()
  • onGet()
  • onPost()
  • onRender()
  • onDestroy()

以下のようなシーケンス図(http://click.sourceforge.net/images/get-sequence-diagram.png

  • onInit()

FrameWorkがいろいろした後に初期化処理として利用

  • onSecurityCheck()

認証などを実装。もちろんもっと別なことでもかまわない。
ここでfalseが返ると、onDestroy()が実行される。onGetなどは実行されない

  • onGet()

GETに対する処理

  • onPost()

POSTに対する処理

  • onRender()

ページ表示のための処理

  • onDestroy()

終了処理、リソースの開放など、今までのプロセスで例外が発生しても実行されることが保証される。

Page 画面遷移

forwardの場合

_/**
 * @see Page#onPost()
 */
public void onPost() {
   // Process form post
   ..
   
   setForward("index.htm");
} 

このようにすると、遷移先の新しいPageインスタンスを使用する。遷移先のPageのコントロールは実行されない。(?)

値の引継ぎ

  • 方法1
getContext().setRequestAttribute("customer", customer);

のように、setしてあげる。

  • 方法2

直接、modelに登録

addModel("customer", customer);
setPath("view-customer.htm");
  • 方法3

PageForwarding

EditPage editPage = (EditPage) getContext().createPage("/edit-customer.htm");
editPage.setCustomer(customer);
setForward(editPage);

のように、遷移先画面のPageをcreatePageメソッドで作成して、直接setしてしまう。
画面名をハードコーディングが気になるので、以下の方法推奨

EditPage editPage = (EditPage) getContext().createPage(EditPage.class);
editPage.setCustomer(customer);
setForward(editPage);

redirectの場合

setRedirect("/logout.htm");
コンテキストが "mycorp" で、"/customer/details.htm"を指定した場合、リダイレクト先は "/mycorp/customer/details.htm"となる。
正確なパスを取得したい場合

   String path = getContext().getPagePath(Logout.class);
   setRedirect(path);

forwardの場合と同様に、
クラス指定も可能

setRedirect(Logout.class);

値を引き継ぎたい場合

Long transId = OrderDAO.purchase(order);
setRedirect("trans-complete.htm?transId=" + transId);

とした場合、HttpServletResponse.encodeRedirectURL(url) を利用したencodeが行われる。

Page PageTemplating

StrutsのtilesのようにTemplateを使用することが出来る。

  • テンプレートPageの作成

まずは、templateとなるクラスを、Pageを継承して作成

public class BorderedPage extends Page {
    /**
     * @see Page#getTemplate()
     */
    public String getTemplate() {
        return "border.htm";  
    }
}

getTemplateメソッドをオーバーライドして、テンプレートとなるページを返す。

  • テンプレート画面の作成

border.htm

<html>
   <head>
     <title>$title</title>
        </head>
    
     <h2 class="title">$title</h2>
      #parse($path)
    </html>

$titleはmodelなどから自動的に埋め込まれる。
実際の画面の埋め込みは#parse($path)
決まり文句と思っていてもいいが、$pathはclick.xmlで指定してある、pageタグの属性。
(このあたりのpathやらcontextやら、formatやらは自動的にmodel(Map)に設定)

<page path="home.htm" classname="Home"/>
  • テンプレートを利用するPageの作成

テンプレートPageを継承して作成する、上のMappingのとおり、Homeクラス

public class Home extends BorderedPage {
     public Home() {
         addModel("title", "Home");
     }
}

テンプレートで$titleがあるので、パラメータを渡しておく

  • テンプレートに埋め込む画面の作成

home.htm

<b>Welcome</b> to Home page your starting point for the application.

とりあえず固定の文字列を埋め込むようにしておくと

最終的には

<html>
   <head>
     <title>Home</title>
        </head>
    
     <h2 class="title">Home</h2>
 
     <b>Welcome</b> to Home page your application starting point.
 
    </html>

Page エラーハンドリング

例外のハンドリングについては、
デフォルトで用意されている。
実際には定義されてはいないが、以下の指定がされているのと同等である。

<page path="click/error.htm" classname="net.sf.click.util.ErrorPage"/>

独自実装がしたい場合には、clickサブディレクトリの下に、error.htmを作成し、

<page path="click/error.htm" classname="com.mycorp.page.ErrorPage"/>

のように、独自の例外Pageを作成する。
デフォルトのerror.htmはTraceが見れたりいろいろ便利
エラーレポートでは、
例外のクラス、メッセージ、発生箇所の前後のソース、StackTrace、
Page情報、Request情報が見れます。

PageNotFound

<page path="click/not-found.htm" classname="net.sf.click.Page"/>

と定義されているのと同等。同じようにclickサブディレクトリに作成する必要

Page Message Properties

Pageクラスは、messagesというプロパティを持っていて、これまた、画面に表示することが出来る。
以下は、titleというプロパティを表示する例
$messages.title
このmessagesは以下の場所からLoadされる。

  • PageScopeMessages

この場合、Pageクラスがcom.mycorp.page.CustomerListとすると、
リソースは
/com/mycorp/page/CustomerList.properties
に存在する必要がある。

  • Global Page Scope Messages

リソースは以下に存在する。
/click-page.properties
PageScopeMessagesは、Global Page Scope Messagesより優先される。
また、後述の、Control messagesよりも優先される。

Control

画面側で発生したイベントをサーバ側で取得する仕組みです。
以下のようなシーケンス図(http://click.sourceforge.net/images/control-post-sequence-diagram.png

Controlは以下のインターフェースを実装する(http://click.sourceforge.net/images/control-class-diagram.png

method description
getContext() / setContext() provides access to the request Context.
getHtmlImports() defines the controls HTML header imports.
getId() defines the controls HTML id attribute.
setListener() set the control action callback listener.
getMessages() defines the controls localized messages map.
getName() / setName() defines the controls name in the Page model or Form fields.
getParent() / setParent() defines the controls parent.
onDeploy() deploy resources on startup.
onProcess() process request event handler.

Control 例

以下のような実装をしたとします。

public HomePage() {
 ActionButton actionButton = new ActionButton("button","buttonLabel");
  actionButton.setListener(this, "onButtonClick");
  addControl(actionButton); 
}
public boolean onButtonClick() {
 System.out.println("ButtonPushed");
 return true;
}

ActionButtonはClickで用意してあるControllでsubmitボタンを作成します。
ここでは、ラベルにbuttonLabelと表示されるボタン(のコントロール)をbuttonという名前で登録します。また、actionButtonイベントが発生した際には、onButtonClickメソッドを呼ぶように登録してあります。
これら、イベント時のメソッド(CallBackメソッド)は引数なしで、booleanもしくはBooleanを返す必要があります。返り値がfalseの場合、onPostやらなにやらは呼ばれません。(doProcess()では、ここで登録されたLisnerのメソッドを実行し、結果を返しています。もちろん他のこともしてますが・・・)
#LisnerはListではありませんので、上書きされていきます。
画面には

$button

とすることでボタンを表示できます。

public Form form = new Form();
 
public HomePage() {
   FieldSet paymentFieldSet = new FieldSet("paymentDetails");
   form.add(paymentFieldSet);
    paymentFieldSet.add(new TextField("cardName", true));
    paymentFieldSet.add(new CreditCardField("cardNumber", true));
    IntegerField expiryField = new IntegerField("expiry", true);
    expiryField.setSize(4);
    expiryField.setMaxLength(4);
    paymentFieldSet.add(expiryField);
    form.add(new Submit("ok", "    OK    ", this, "onOkClick"));
    form.add(new Submit("  Cancel  ", this, "onCancelClick"));
}

とすると、formを作成することが出来ます。
画面では、$formとすることで表示がされます。
Formのコンストラクタで名前を指定すると、その名前でformが登録されますが、上のようにデフォルトでも作成できます。

でも・・・

というか・・publicが気持ち悪い。。。
ので、private Form form = new Form(“name”);
として、最後に、addControll(form);
としましょう。これはPageのフィールドをpublicにするのが気持ち悪い場合も同様です。となると・・・値の引継ぎなど面倒くさいことになる?

ちなみに

CreaditCardFieldはjsが同梱されていて、visaとか表示してくれます。

[Click] Control 文字化け

このあたりで、入力チェックに引っかかった場合などのメッセージが文字化けしていると思います。もしくはページに日本語を使用していた場合に大惨事になってるかもしれません。
click.xml
にHeaderの指定している人は

<header name="Content-Type" value="text/html; charset=shift_jis"/>

と追加しましょう。もちろんページごとに指定してもいいと思います。
formで化けるのは手っ取り早くFilterで対応しましょう。

追加

実はもっと簡単に修正できました
click-app 参照

でもまぁ一方法として残しておきます

Control Message Properties

Controllにも個別のプロパティとGlobalなプロパティがあります。

  • Controll Scope Messages

com.mycorp.control.CustomTextFieldが存在した場合、リソースは以下にある必要がある。
/com/mycorp/control/CustomTextField.properties

  • Global Control Scope Messages

共通のコントロールメッセージ
/click-control.properties

Control 参考すべき箇所

FromのLayoutやら、VOへのcopyやら便利メソッドが多いので、Formクラスのドキュメントは一読
例)
form.setErrorsPosition(Form.POSITION_TOP);
でエラー表示がFormの上になる。
nputVO inputVO = new InputVO();
form.copyTo(inputVO);
でVOに値がcopyされる。
などなど

Config Servlet Configuration

まずは、ClickServletの設定を必要とします。
web.xml

<web-app>
  <servlet>
    <servlet-name>click-servlet</servlet-name>
    <servlet-class>net.sf.click.ClickServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>click-servlet</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>
</web-app>

ClickServletは、WEB-INF/click.xmlを参照します。
マッピングでは、*.htmlを対象とするようにしています。load-on-startupは0を推奨となっています。

Config Application Configuration

簡単なclick.xmlは以下のようになります。

<click-app charset="UTF-8" locale="de"> 
  <pages>
    <page path="index.htm" classname="com.mycorp.page.Home"/>
    <page path="login.htm" classname="com.mycorp.page.Login"/>
    <page path="logout.htm" classname="com.mycorp.page.Logout"/>    
  </pages>
  <headers>
    <header name="Pragma" value="no-cache"/>
    <header name="Cache-Control" 
            value="no-store, no-cache, must-revalidate, post-check=0, pre-check=0"/>
  </headers>
  <format classname="com.mycorp.util.Format"/>
  <mode value="debug"/>
</click-app> 

DTD

DTDは以下のようになっています。
click.dtd

<!-- The Click Application (click.xml) Document Type Definition. -->
<!ELEMENT click-app (pages?, headers?, format?, mode?, controls?)>
  <!ATTLIST click-app charset CDATA #IMPLIED>
  <!ATTLIST click-app locale CDATA #IMPLIED>
  <-- Application pages. -->
  <!ELEMENT pages (page*, excludes*)>
    <!ATTLIST pages package CDATA #IMPLIED>
    <!ATTLIST pages automapping (true|false) "false">
    
    <!-- Page definition. -->
    <!ELEMENT page (header*)>
      <!ATTLIST page path CDATA #REQUIRED>
      <!ATTLIST page classname CDATA #REQUIRED>
    <!-- Excludes definition. -->
    <!ELEMENT excludes (#PCDATA)>
      <!ATTLIST excludes pattern CDATA #REQUIRED>
  <!-- Application default page headers. -->
  <!ELEMENT headers (header*)>
  
    <-- Header definition. -->
    <!ELEMENT header (#PCDATA)>
      <!ATTLIST header name CDATA #REQUIRED>
      <!ATTLIST header value CDATA #REQUIRED>
      <!ATTLIST header type (String|Integer|Date) "String">
  <!-- Page template formatter class. An new format object added to
       the Velocity context under the key: "format". -->
  <!ELEMENT format (#PCDATA)>
    <!ATTLIST format classname CDATA #FIXED "net.sf.click.util.Format">
 
  <!-- Application mode, which configures logging and caching. -->
  <!ELEMENT mode (#PCDATA)>
     <!ATTLIST mode value (production|profile|development|debug|trace) "development">
     <!ATTLIST mode logto (console|servlet) "console">
  <!-- Application deployable controls. -->
  <!ELEMENT controls (control*)>
  
    <-- Deployable control class. -->
    <!ELEMENT control (#PCDATA)>
      <!ATTLIST control classname CDATA #REQUIRED>

Config click-app

click-appには、charsetと、localeがあります。
charsetで指定した文字エンコーディングは、

に使用されます。
なので、以前の記述にある、headersでの、指定は特に必要ないようです。
Filterも必要ありません、気をつけなくてはいけないのは、htmファイルのテキストファイルエンコードとあわせる事です。
あとはcacheとかworkディレクトリのせいで文字化けに悩まされないようにちゃんとwork消して、キャッシュ消して実験することです。(私事)

pages

ページは、
pathと、classnameを1:1でマッピングしますが、
ある特定のパッケージが、すべてPageクラスで、なおかつ以下の命名規約に従っている場合、automappingが可能です。

  <pages>
    <page path="index.htm" classname="com.mycorp.page.Home"/>
    <page path="login.htm" classname="com.mycorp.page.Login"/>
  </pages>

のように、com.mycorp.pageパッケージがPageファイルのパッケージであれば、

<pages package="com.mycorp.page" automapping="true"/>

とすることによりAutoMappingがなされます。
また、手動でマッピングする際にも、packageはルートパッケージとして使用できます。

  <pages package="com.mycorp.page">
    <page path="index.htm" classname=" Home"/>
    <page path="login.htm" classname=" Login"/>
  </pages>

automapping

<click-app>
  <pages package="com.mycorp.page">
    <page path="index.htm"                    classname="Home"/>
    <page path="search.htm"                   classname="Search"/>
    <page path="contacts/contacts.htm"        classname="contacts.Contacts"/>
    <page path="security/login.htm"           classname="security.Login"/>
    <page path="security/logout.htm"          classname="security.Logout"/>    
    <page path="security/change-password.htm" classname="security.ChangePassword"/>    
  </pages>
</click-app> 

上記のようなMappingの場合、
以下のようなマッピングで代替が可能です。

<click-app>
  <pages package="com.mycorp.page" automapping="true">
    <page path="index.htm" classname="Home"/>
  </pages>
</click-app> 

対応付けは代替以下のようなルールで行われます。

change-password.htm  =>  ChangePassword
change_password.htm  =>  ChangePassword
changePassword.htm   =>  ChangePassword
ChangePassword.htm   =>  ChangePassword

また、見つからなかった場合、自動的に、Pageをつけたクラスも検索します。

customer.htm         =>  CustomerPage
change-password.htm  =>  ChangePasswordPage

excludes

automappingから除外する指定は以下のようにします。

  <pages package="com.mycorp.page" automapping="true">
    <excludes pattern="/dhtml_/*, /tiny_mce_/*, banner.htm, about.htm"/>
  </pages>

header

ページごとに、headarの指定が可能です。

<page path="login.htm" classname="com.mycorp.page.Login"/>
  <header name="Pragma" value="no-cache"/>
  <header name="Cache-Control" 
          value="no-store, no-cache, must-revalidate, post-check=0, pre-check=0"/>
  <header name="Expires" value="1" type="Date"/>
</page> 

また、後述のとおり、共通でheaderを指定することも出来ます。

headers

以下のように共通したheadarのパラメータを指定することが可能です。

<click-app> 
  <pages>
     ..
  </pages>
  <headers>
    <header name="Pragma" value="no-cache"/>
    <header name="Cache-Control" 
            value="no-store, no-cache, must-revalidate, post-check=0, pre-check=0"/>
    <header name="Expires" value="1" type="Date"/>
  </headers>
</click-app> 

format

オリジナルのFormatクラスを指定できます。
フレームワークは、net.sf.click.util.Formatをデフォルトで指定してあります。
このFormatクラスは、e-mailの形式や、日付、DecimalFormatなどの表示で有用なフォーマット機能を提供します。
詳細はAPI(http://click.sourceforge.net/docs/click-api/net/sf/click/util/Format.html)を参考に。
画面では、以下のような文法で使用できます。
$format.date($deliveryDate, "dd MMM yyyy")

deliveryDateという名前でオブジェクトが取得できることが条件です。
deliveryDateという名前で取得できるオブジェクトを、dd MMM yyyyというフォーマットで変換して表示します。
オリジナルのFormatを登録する場合は、click.xmlに以下のように指定します。

<format classname="com.mycorp.util.CustomFormat"/>

Formatのサブクラスは、Localeを引数に受けるコンストラクタを用意する必要があります。

mode

modeでは、logと、キャッシュに関する設定が可能です。

<mode value=" development" logto=”console”/>

がデフォルトと同等です。

Application mode Page auto loading Velocity caching Message caching Load templates on startup Click logging level Velocity logging level
production No Yes Yes Yes WARN ERROR
profile No Yes Yes Yes INFO ERROR
development Yes No No No INFO ERROR
debug Yes No No No DEBUG ERROR
trace Yes No No No TRACE WARN

対応はそれぞれ上記のようになります。
logtoでservletを指定すると、サーバログとして出力されます。
(Tomcat4.1だと、logs/localhost_log.日付.txtになります。)
指定がconsoleの場合、System.outです。
(Tomcat4.1だと、コンソールです。)

controls

controlを登録することが出来ます、ここで登録したcontrolについては、アプリケーションのスタートアップ時に、それぞれのcontrolのonDeployメソッドが実行されます。

<control classname="com.mycorp.control.CustomField"/>

いまいち用途は不明です。
フレームワーク側だと、Tableがcssの配置を行うために、controlを指定してあるようです。extrasの方だと結構指定してあるようです。

Velocity Properties

velocityに関する設定です。
デフォルトでは以下のような指定になっています。

resource.loader=webapp
webapp.resource.loader.class=org.apache.velocity.tools.view.servlet.WebappLoader
webapp.resource.loader.cache=[true|false] 
#depending on application mode
webapp.resource.loader.modificationCheckInterval=0 
#depending on application mode
velocimacro.library.autoreload=[true|false]
#depending on application mode
velocimacro.library=click/VM_global_library.vm 

コメントにあるように、modeに依存する設定もあります。
独自の設定を追加したり、上書きをしたい場合には、WEB-INF/velocity.propertiesに設定をすれば可能となります。

AutoDeploy

  • click/error.htm - the Page Error Handling template
  • click/control.css - the Controls cascading stylesheet
  • click/control.js - the Controls _JavaScript library
  • click/not-found.htm - the Page Not Found template
  • click/VM_Global_library.vm - an empty Velocity macro library

上記のファイルは自動的にロードされ、配置されます。上書きしたい場合には、該当ファイルを用意すれば、独自設定とすることが出来ます。

Clickその1 まとめ

というわけで散々書き貯めたこのテキスト
途中からはただの中途半端な翻訳になってしまったわけですが・・・
本来はもう少しためてから・・・と思ってたんだけどね
気づいちゃったわけですよ。

何に?って
http://amateras.sourceforge.jp/cgi-bin/fswiki/wiki.cgi/click?page=FrontPage

に・・・
orz

というかこの人はいつもながらすごいなぁ・・・って思う。

TypeSafeActionListener

http://amateras.sourceforge.jp/cgi-bin/fswiki/wiki.cgi/click?page=TypeSafeActionListener

なるほど・・・すごいねぇ〜Swingとかと同じに使えそうですね〜
Stringでコールバックメソッド定義するの嫌だったので目にうろこです!

10分で作れるClickアプリ

どこかで見たようなタイトルですが・・・(流行?)
すごいです。やってみました。ちょっと感動。
http://d.hatena.ne.jp/winebarrel/20060904

が下準備として設定済みとします。
以下特に配置とかは説明していないけど読み取ってください。

ソース元と同じDBが定義されています

C:\tool\bookmark>java -cp ..\rubbish-db-1.6.1\rubbish-db-1.6.1.jar;mysql-connector-java-5.0.3-bin.jar rubbish.db.tool.ScaffoldGen
Enter

結果
required option undefined -- d, u, p, l
Options:
  -h show help
* -d scaffold destdir
  -r jdbc driver
  -c database catalog
  -s database schema
* -l database url
* -u database userid
* -p database password
  -t include tables (ex: one_table, two_table, ...)

という具合に引数が足りないといわれます。
って・・・別にここは再現させなくてもいいんだろうけど。。。

やってることは、rubbishのjarと、mysqljdbcドライバにクラスパスを通して、
rubbish.db.tool.ScaffoldGenを実行

C:\tool\bookmark>java -cp ..\rubbish-db-1.6.1\rubbish-db-1.6.1.jar;mysql-c
onnector-java-5.0.3-bin.jar rubbish.db.tool.ScaffoldGen -d bookmark -l jdbc:mysq
l://localhost/bookmark?characterEncoding=UTF-8 -u bookmark -p bookmark

ディレクトリと、DBURLとDBユーザ、DBパスワードを指定します。

C:\tool\bookmark(カレント)
の下に、bookmarkというディレクトリを作成して、そこに自動生成します。

C:\tool\bookmark\bookmark\webapp\WEB-INF\lib
に、
servlet-api.jar、jdbcドライバ、click.jar、rubbish-db.jarをコピーします

C:\tool\bookmark>copy C:\Tomcat5.0\common\lib\servlet-api.jar bookmark\webapp\WEB-INF\lib
        1 個のファイルをコピーしました。
C:\tool\bookmark>copy ..\rubbish-db-1.6.1\rubbish-db-1.6.1.jar bookmark\webapp\WEB-INF\lib
        1 個のファイルをコピーしました。
C:\tool\bookmark>copy click-1.0.jar bookmark\webapp\WEB-INF\lib
        1 個のファイルをコピーしました。
C:\tool\bookmark>copy mysql-connector-java-5.0.3-bin.jar bookmark\webapp\WEB-INF\lib

生成されたbookmarkに移動して、ant実行

C:\tool\bookmark\bookmark>ant

とすると、webappの下にコンパイルして配置されるので、
あとはwebappを名前変更して、Tomcatに認識させればOK!

すっげぇ〜〜って感じですね。
外部キーを認識して、結合テーブルを自動生成・・・
ってさすがにそれはやってくれないよね(^^;