WicketのサンプルをClickで動かしてみる−その2

肥満診断

フォームに身長と体重を入力し、その結果を表示するというアプリケーション。以下の示すのが、表示結果。

例によって、EclipseでDynamic Web Projectを作成する。
click.xml, web.xml, velocity.propertiesをコピペし、border-template.htm, redirect.htmlをコピペし、redirect.html内の最初に表示するページをbmiform.htmとした。
bmiform.htmの内容は以下の通り:

<h1>BMIによる肥満度診断</h1>
$inputForm
<hr/>
あなたのBMIは
<span class="bmi">$bmi</span>
です。<br/>
あなたは「
<span class="diagnosis">$diagnosis</span>
」です。<br/>
ちなみにあなたの標準体重は
<span class="idealWeight">$idealWeight</span>
kgです。<br/>

身長と体重を入力するフォームが$inputFormの部分になる。このフォームの作成は、対応するJavaクラスで定義する。Clickの規約により、対応するJavaのクラス名は、BmiformPageとなる。もし、クラス名としてBmiFormPageというクラス名にしたいなら、HTMLのファイル名は、bmi_form.htmとすればよい。
BmiformPage.javaのフィールド宣言とコンストラクタを以下に示す。

package com.fujitsu.fst.hal;

import net.sf.click.control.FieldSet;
import net.sf.click.control.Form;
import net.sf.click.control.Submit;
import net.sf.click.control.TextField;
import net.sf.click.util.ClickLogger;

public class BmiformPage extends BorderPage {
    public String title = "BMIによる肥満度診断";
    public Form inputForm = new Form();
    public double bmi;
    public String diagnosis;
    public double idealWeight;
    
    /* cmHeightField, kgWeightFieldは、DoubleFieldにしたいところ
     * だが、DoubleFieldは、required statusが利かないようだ。
     * フィールドをクリアしてsubmitボタンを押してもエラーにならない。
     * なので、TextFieldにしておく。
     */
    private TextField cmHeightField;
    private TextField kgWeightField;
    private Submit submitButton;
    private double cmHeight = 170.0;
    private double kgWeight = 50.0;
    
    private ClickLogger logger = ClickLogger.getInstance();
    
    public BmiformPage() {
        logger.debug("Bmiform#<init>(): this = " + this);
        FieldSet inputFieldSet = new FieldSet("bmi", "");
        inputForm.add(inputFieldSet);
        cmHeightField = new TextField("cmHeight", "身長 (cm)", true);
        inputFieldSet.add(cmHeightField);
        kgWeightField = new TextField("kgWeight", "体重 (kg)", true);
        inputFieldSet.add(kgWeightField);
        
        submitButton = new Submit("submit", "submit", this, "onStartDiag");
        inputForm.add(submitButton);
        
        Bmi bmi = new Bmi(cmHeight, kgWeight);
        this.bmi = bmi.getBmi();
        this.diagnosis = bmi.getDiagnosis();
        this.idealWeight = bmi.getIdealWeight();
    }

publicなフィールドは、その値が、対応するHTMLファイル内の変数を置き換える。
フォームは、身長と体重を入力するTextFieldとSubmitボタンからなる。レイアウトを単純にするため、ふたつのTextFieldは、FieldSetオブジェクトに貼り付けている。それぞれのTextFieldは、フィールドの名前を表示するラベルと値を入力するフィールドからなるが、こうすることで、2つあるフィールドのラベルとフィールドがきちんと並ぶ。
身長と体重の入力フィールドは実数値のみを扱うフィールドなので、コメントにあるように、これらをDoubleFieldにすることも可能だが、値が入力されなかったときのエラーチェックがなされないようなので、TextFieldを使用した。
コンストラクタは、デフォールトの身長と体重に対するBMIと肥満度診断結果、身長に対する理想的な体重を計算している。これらの計算結果は、ページを表示直後に表示されることになる。
Submitボタンに対応するオブジェクトを生成するコンストラクタの4番目の引き数は、このボタンが押されたときに呼び出されるコールバックメソッド名となる。このコールバックメソッドは、最初以下のように作った。

    public boolean onStartDiag() {
        try {
            this.cmHeight = Double.parseDouble(cmHeightField.getValue());
            this.kgWeight = Double.parseDouble(kgWeightField.getValue());
        } catch (NumberFormatException e) {
            return false;
        }
        Bmi bmi = new Bmi(cmHeight, kgWeight);
        this.bmi = bmi.getBmi();
        this.diagnosis = bmi.getDiagnosis();
        this.idealWeight = bmi.getIdealWeight();
        return true;
    }

つまり、身長と体重の入力フィールドに設定した値をgetValue()メソッドで取り出して、それをもとにBMIを計算する。
ところが、これだとNumberFormatExceptionを捕捉して、BMIの計算まで処理が進まない。あれこれやってみてわかったことは、このメソッドから見えるthisポインタとコンストラクタで使っているthisポインタが違うこと。Submitボタンを押したあとに画面遷移がおきて、数値を入力したフォームとは異なるフォームに関連付いたPageになっていることだった。
結局、このPageに貼り付けられたフォームから各部品を取り出し、そこから入力した身長と体重の値を取り出すようにするようにした。そのコードを以下に示す。

    public boolean onStartDiag() {
        logger.debug("Bmiform#onStartDiag(): this = " + this);
        FieldSet inputFieldSet = (FieldSet)inputForm.getField("bmi");
        this.cmHeightField = (TextField)inputFieldSet.getField("cmHeight");
        this.kgWeightField = (TextField)inputFieldSet.getField("kgWeight");
        logger.debug("cmHeightField = " + cmHeightField);
        logger.debug("kgWeightField = " + kgWeightField);
        if (cmHeightField == null || kgWeightField == null) {
            return false;
        }
        try {
            this.cmHeight = Double.parseDouble(cmHeightField.getValue());
            this.kgWeight = Double.parseDouble(kgWeightField.getValue());
        } catch (NumberFormatException e) {
            return false;
        }
        Bmi bmi = new Bmi(cmHeight, kgWeight);
        this.bmi = bmi.getBmi();
        this.diagnosis = bmi.getDiagnosis();
        this.idealWeight = bmi.getIdealWeight();
        return true;
    }

その他のメソッドを以下にあげる。

    public void onInit() {
        cmHeightField.setValue(Double.toString(cmHeight));
        kgWeightField.setValue(Double.toString(kgWeight));
    }
    
    public String getContentType() {
        return "text/html; charset=Windows-31J";
    }

onInit()は、Pageのコンストラクタが呼ばれたあと(画面が出来上がったあと)呼び出されるメソッドで、ここで初期画面のデフォールト値を設定するために用意している。ただし、Submitボタンを押したあとでもこのメソッドは呼ばれ、入力された値を設定しなおすことになる。
最後のgetContentType()は、Shift_JISで日本語を表示するためのおまじない。