JSF 2.0で作るWebアプリケーション 後編

http://d.hatena.ne.jp/shin/20091030/p1
http://d.hatena.ne.jp/shin/20091031/p1
の続き。

今回は編集系を実装します。

その前に

まずはサービスの追加。単体のID検索を作るの忘れていたので。以下のメソッドをEmployeeServiceに追加します。

    public Employee findById(Long editId) {
        EntityManager em = emf.createEntityManager();
        try{
            return em.find(Employee.class, editId);
        }finally{
            em.close();
        }
    }

IDで検索するだけです。今回は編集画面に移動するときにIDをパラメータとして渡していますから。たんなるPOSTならそのままエンティティ渡せますし、PRGパターンにしてもフラッシュ記憶にエンティティをわたせば実現できるので、実装方法によってはなくてもいいですけどね。

編集画面の管理対象Bean

Editというクラスをつくります。これがedit.xhtmlに対応するものです。

package managed;

import entity.Employee;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import service.EmployeeService;

@ManagedBean
@ViewScoped
public class Edit implements Serializable{

    @ManagedProperty("#{employeeService}")
    private EmployeeService service;

    private Employee employee;

    private Long editId ;

    

    public Employee getEmployee() {
        return employee;
    }

    
    public String save(){
        System.out.println(employee.get入社日());
        if(editId == null){
            service.insert(employee);
        }else{
            service.update(employee);
        }

        return "index?faces-redirect=true";
    }

    public String getTitle (){
        return editId<0?"新規作成":"編集";
    }

    public Long getEditId() {
        return editId;
    }

    public void setEditId(Long editId) {
        this.editId = editId;


        if(editId >= 0){
            employee = service.findById(editId);
        }else{
            employee = new Employee();
        }
    }

    public void setService(EmployeeService service) {
        this.service = service;
    }


}

saveメソッドが保存ボタンを押したときのイベントです。保存後リダイレクトでindex.xhtmlを表示します。「?faces-redirect=true」がリダイレクトです。PRGパターンが容易にできます。

setEditIdメソッドがGETパラメータを受け取るメソッドです。ここの値によって新規モードなのか編集モードなのか判別もしています。

getTitle では上記メソッドで設定されたeditIdの値を見て新規や編集といった表示する文言を変更しています。

わざわざeditIdというプロパティを用意しているのは柔軟さを持たせるためです。ここではやりませんがEmployeeのIDを変更することも可能になります。表示されるコードを主キーにもつ場合なども場所によってはありますのでこうしておくほうがいいのです。


Viewスコープなので、GETで受け取ったeditIdをずっと保持しています。入力ミスがあってもう一度入力画面が表示されたとしても失われることはないというわけです。requestスコープにする場合hiddenタグ等で埋め込んでいく必要があります。postback時にはURLパラメータは消えるからです。

編集画面のテンプレート

ではテンプレートに埋め込んでいきましょう。edit.xhtmlを以下のように修正します。

<?xml version='1.0' encoding='UTF-8' ?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title><h:outputText value="#{edit.title}"/></title>
    </head>
    
    <body>
        <f:metadata>
            <f:viewParam name="editId" value="#{edit.editId}"/>
        </f:metadata>

        <h1><h:outputText value="#{edit.title}"/></h1>
        <h:messages/>
        <form jsfc="h:form">
            <table border="1">
                <tr>
                    <td>ID</td>
                    <td><span jsfc="h:outputText" value="#{edit.employee.id}"/></td>
                </tr>
                <tr>
                    <td>従業員名</td>
                    <td><input jsfc="h:inputText" type="text" value="#{edit.employee.従業員名}"/></td>
                </tr>
                <tr>
                    <td>入社日</td>
                    <td>
                        <input jsfc="h:inputText" type="text" value="#{edit.employee.入社日}">
                            <f:convertDateTime  timeZone="JST" pattern="yyyy/MM/dd"/>
                        </input>yyyy/mm/ddで入力してください
                    </td>
                </tr>
                <tr>
                    <td>給料</td>
                    <td><input jsfc="h:inputText" type="text" value="#{edit.employee.給料}"/></td>
                </tr>
            </table>
            <input jsfc="h:commandButton" type="submit" action="#{edit.save}" value="保存"/>
        </form>

        <input jsfc="h:button" type="button" outcome="index" value="一覧へ戻る"/>
    </body>
    
</html>

「f:viewParam」タグでGETパラメータを受け取っています。

実行

新規作成ボタンを押すと以下のようになります。
http://shin.cside.com/diary/2009/1101-01.png

編集ボタンを押すと以下のようになります。
http://shin.cside.com/diary/2009/1101-02.png

保存ボタンを押すとデータがそれぞれ新規作成、もしくは更新されて一覧表示に戻ります。

正常入力以外

入力ミスがあると保存させないようにしたいということはあると思います。日付型(ここでは入社日)や数値型(個々では給料)は文字列との変換が発生しますので入力ミスが自動で検知されます。

たとえば、給料に有効な数字以外を入れた場合以下のようになります。メッセージはリソースでかえることも出来ますが、コードでかえること(JSF 1.2から)ももちろん出来ます。

http://shin.cside.com/diary/2009/1101-03.png

また、名前を必須入力にしたいということも考えられます。

まず名前に必須入力をつけます。これはJSFは簡単で、「required="true"」属性をつけるだけです。また、必須入力時のエラーメッセージもリソースをかえることで設定も出来ますが、ここではrequiredMessageを設定します。社員名のところは以下のようになります。

<tr>
    <td>従業員名</td>
    <td><input jsfc="h:inputText" type="text" value="#{edit.employee.従業員名}"
        required="true" requiredMessage="従業員名は必須入力ですよ"/></td>
</tr>

名前を入れないで保存ボタンを押すと以下のようになります。簡単ですね。リソースを使わずデータバインディング(たとえば#{messageHolder.required}とか)すれば自由に管理ができるということです。データベース等に入れておいて即時に変更が反映されるようにという場合も確かにありますから。
http://shin.cside.com/diary/2009/1101-04.png


給料の数値のバリデーションをしてみましょう。マイナスを入力したらダメという風に。Editクラスに以下のメソッドを追加します。

    public void validate給料 (FacesContext context,
        UIComponent component, Object value){

        Integer 給料 = Integer.valueOf( value.toString() );

        if(給料 < 0){
            throw new ValidatorException(new FacesMessage("マイナスはダメよ"));
        }
    }


続いてedit.xhtmlテンプレートの給料のところを変更します。

<td>
    <input jsfc="h:inputText" type="text" value="#{edit.employee.給料}"
           converterMessage="給料は数値で入力してください"
           validator="#{edit.validate給料}">
        <f:convertNumber integerOnly="true" />
    </input>
</td>

コンバートに失敗した場合のメッセージを入れています。また、先ほどのバリデータを設定し、整数部分のみ取得するようにしています。

バリデータの確認のためにマイナスを入れてみると以下のようになります。
http://shin.cside.com/diary/2009/1101-05.png



削除は今回実装しませんでしたが、ここまで読んでくれた人はすぐにできるはずです。JSF2のWebアプリ開発がどれだけ楽になったかがわかると思います。テンプレートはそのまま表示をして確認できるのもなかなか強いです。やっぱりui:repeatとjsfc属性の存在が大きいですね。また、Ajax対応も容易です。

ただ、やっていて思いましたがTomcat 6だとELメソッド使えないのでTomcat 7が出ないとちょっときついですね。最初の一覧画面から編集ボタンで遷移するとき今回はGET使いましたが、PRGパターンやる場合何番目のボタンかなどの取得が新機能を使って簡単に記述できないので(まぁ難しくはないですが。f:setPropertyActionListenerタグをボタンの下に1行書く必要があります。これは特定のアクション時のみ有効になるパラメータです)。あと、再デプロイはGlassfish V3のほうがTomcat6より早いかもしれないです。

というわけでJava EE 5の環境(つまりTomcat6程度)しかないんじゃあという人もJSF2に触れてみてはいかがでしょうか。