JSF 2.0 の Ajax 対応はとてもかんたん

2011年1月18日 at 6:25 午後 4件のコメント

JSF 2.0 の新機能の一つに Ajax 対応があります。今日は JSF 2.0 に追加された Ajax と 画面遷移が無いページ(同一描画内)で利用可能な @ViewScope について紹介します。

JSF 2.0 で追加された Ajax の機能を使うと、JavaScript の知識が殆どなくても Ajax のページを作成する事ができます。またその際、自身で XMLHttpRequest, XMLHttpResponse の通信処理も記載する必要がないため開発効率が向上します。

もっと誇張していうなら、通常の JSF ページに対してほんの数行追加するだけで、簡単に Ajax 対応させる事ができます。下記に示す例ではほんの1〜2行追加するだけで Ajax 対応ページを実現しています。

JSF 2.0 のページを Ajax 対応させるための開発の手順としては、通常の JSF のページを記載して頂き、最後に Ajax 対応用のタグを追加するという手順で行って頂く事ができます、そのため、Ajax 対応を意識をせずに記載できるようになっています。

それでは、JSF 2.0 における Ajax 対応について詳細に説明していきます。JSF では 「実行」と「描画」に分けて処理を行います。

実行フェーズでは下記を行います
● コンポーネントデータのコンバートやバリデート
● 入力データをモデル(サーバ側の CDI/Managed Bean)に送信
● アクション、アクションリスナーの実行

描画フェーズでは下記を行います。
● 処理結果の描画

必要な描画部分だけを更新できるため、サーバ、クライアント(ブラウザ)共に負荷(データ転送容量や全データの描画処理等)を軽減する事ができるようになります。

サンプルアプリケーションの作成
それでは、サンプルのアプリケーションを作成してみます。ユーザ名、パスワードを入力し、ボタンを押下するアプリケーションを作成します。

このアプリケーションの描画部分である Facelets は下記のように記載します。

ログイン Facelets のソースコード (Ajax 未対応):

                <h:form>
                <h:inputText id="name" value="#{user.name}" validator="#{user.validateName}">
                </h:inputText> #{' '}
                <h:message for="name" id="nameError" style="color: red"/>
                <br/>

                <h:inputSecret id="password" value="#{user.password}">
                </h:inputSecret> 
                <br/>                
                <h:commandButton value="Submit" action="#{user.login}">
                </h:commandButton>
                </h:form>

CDI の UserBean.java は下記のように書きます。下記のコードは Submit ボタンが押下された際 (UserBean#logic が実行される)、画面遷移は行わず入力されたデータのバリデーションのみ行います。@Named(“user”) の代わりに @ManagedBean(name=”user”) を指定すると、本クラスは CDI ではなく JSF の Managed Bean として扱われます。
(※ データの入力値チェックは通常 JavaScript で記載する事が多いと思いますが、今回は説明をかんたんにするためサーバ側でチェックを行っています。)

UserBean.java のソースコード:

package jp.co.oracle.cdi;

import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import javax.inject.Named;

@Named("user") //@ManagedBean(name="user")
@SessionScoped
public class UserBean implements Serializable{
    private String name="";
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public void validateName(FacesContext fc, UIComponent uic, Object value){
        String inputData = (String)value;
        if(inputData.length() > 25){
            throw new ValidatorException(new FacesMessage("25 文字以上は入力できません。"));
        } else if(inputData.contains("_")){
            throw new ValidatorException(new FacesMessage("アンダースコアは入力できません。"));
        } else if (! inputData.matches("^[\\u0020-\\u007E]+$")){
            throw new ValidatorException(new FacesMessage("入力文字は ASCII 文字でなければなりません。"));           
        } else{
            ;
        }
    }
    
    public void login(){
        ;
    }
    
}

コードを記述した後、アプリケーションを実行してください。「Submit」ボタンを押下すると Validation が実行されます。この際、画面の一部ではなく全画面が再読み込みされている事を確認してください。

次に、上記のコードに対して下記のコードを追加してください。

3 行目:<f:ajax execute=”name” render=”nameError” event=”blur” />
12 行目:<f:ajax/>

追記したログイン Facelets のソースコード(Ajax 対応):

                <h:form>
                <h:inputText id="name" value="#{user.name}" validator="#{user.validateName}">
                    <f:ajax execute="name" render="nameError" event="blur" />
                </h:inputText> #{' '}
                <h:message for="name" id="nameError" style="color: red"/>
                <br/>

                <h:inputSecret id="password" value="#{user.password}">
                </h:inputSecret> 
                <br/>                
                <h:commandButton value="Submit" action="#{user.login}">
                    <f:ajax/>
                </h:commandButton>
                </h:form>

コードを追記後、アプリケーションを再実行してください。実行すると、画面をリフレッシュせず Validation を実行する事が確認できます。

上記で追加した <f:ajax execute=”name” render=”nameError” event=”blur” /> は execute, render, event の 3 つの属性を持っています。

execute=”name” で指定した “name” は Ajax のリクエストを送信した際、サーバ側で実行される JSF コンポーネント名を指定します(複数のコンポーネントを実行する場合、スペースで区切ります。また、直接コンポーネント名を指定する他、@this, @form, @all, @none 等の予約語を指定する事もできます。)。上記の例では <h:inputText id=”name” のコンポーネントが実行されます。

次に、render=”nameError” ですが、Ajax の実行結果を描画する JSF コンポーネントを指定します(複数のコンポーネントを実行する場合、スペースで区切ります)。上記の例では <h:message for=”name” id=”nameError” style=”color: red”/> の部分が再描画されます。

次に event=”blur” ですが、どのタイミングでこの Ajax が実行されるかを指定します。blur は onblur の事を指し、コンポーネントのフォーカスが失われた際に実行する事を意味しています。

つまり、上記のコードを追加すると、テキストフィールドに対するフォーカスを失った際、id=”name” で指定したコンポーネントを実行し、id=”nameError” で指定した箇所にエラーコードを出力するという動きになります。

最後に、上記の例では<h:commandButton/> のタグ内に <f:ajax> タグを記載しています。これは input 内にフォーカスがあたっている際に、ボタンが押下された場合、Ajax イベントと ボタン押下のイベントが競合して同時実行されてしまう可能性があるため、このような意図しない振る舞いを防ぐために記載しています。なぜなら、JSF では Ajax のリクエストをキューイングしますので、最初の Ajax のリクエストが終了した後に、次の commandButton 内に記載した Ajax リクエストが実行されるようになります。

<f:ajax> タグはその他、disabled, event, execute, immediate, onerror, onevent, listener, render の属性を指定する事ができます。
例えば、下記のようにしてエラーハンドリングを行う事ができます。

<f:ajax onerror=”handleAjaxError”/>

如何でしょうか? たった1、2行の追加で、
かんたんに Ajax 対応ができる事ができました。

それでは、
上記よりちょっとだけ複雑な Ajax のページを作成してみます。

このページはテキストフィールド内に入力したデータをテーブル内に追加していく(カートのような)ページです。この例では直接製品名を入力していますが、製品の写真を表示したりボタン等を設けてよりリッチなページも作成する事ができるかと思います。
(※ 今回は説明かんたんにするためテキスト入力にさせて頂きます。)

まず、このページの描画用 Facelets を下記のように記載します。

簡易カート Facelets のソースコード (Ajax 未対応)

                <h:form prependId="false">
                    <h:inputText id="productname" value="#{cart.product.productname}">
                    </h:inputText> #{' '}
                    <h:commandButton action="#{cart.addItem}" value="カートに追加">
                    </h:commandButton>
                    <br/>
                    <br/>

                    <h:dataTable border="1" id="tables" value="#{cart.selectedItems}" var="item">
                        <f:facet name="header">  
                            <h:outputText value="追加された製品一覧" />  
                        </f:facet>
                        <h:column>
                            <h:outputText value="#{item.productname}" />  
                        </h:column>
                    </h:dataTable>
                </h:form>

次に、製品名を持つ 製品 Bean を作成します。

製品 Bean のソースコード:

package jp.co.oracle.cdi;
import java.io.Serializable;

public class Product implements Serializable{

    private String productname;

    public String getProductname() {
        return productname;
    }

    public void setProductname(String productname) {
        this.productname = productname;
    }    
}

最後に、入力された製品名を格納する Cart Bean を作成します。

カート Bean のソースコード:

package jp.co.oracle.cdi;
import java.io.Serializable;
import java.util.ArrayList;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.inject.Named;
import javax.validation.constraints.NotNull;

@Named("cart")
@SessionScoped
public class Cart implements Serializable{
    @NotNull
    private Product product = new Product();

    private ArrayList<Product> selectedItems = new ArrayList();

    public ArrayList<Product> getSelectedItems() {
        return selectedItems;
    }

    public void setSelectedItems(ArrayList<Product> selectedItems) {
        this.selectedItems = selectedItems;
    }
    
    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public void addItem(){
        String inputProductName = product.getProductname();

        Product item = new Product();
        item.setProductname(inputProductName);
        selectedItems.add(item);
    }
}

補足:

このページの振る舞いについて若干補足します。
1. <h:inputText id=”productname” value=”#{cart.product.productname}”>
の描画領域にデータを入力します。

2. 入力された値は、下記の呼び出しによって Product のインスタンスが生成されます。
Cart#setProduct()#setProductname(productname)

3. ボタンが押下された際、Cart#addItem が実行されます。
<h:commandButton action=”#{cart.addItem}” value=”カートに追加”>

4. Cart#addItem は、内部で持つ ArrayList に対して入力された値を追加します。
この際画面遷移は行わず同一ページを表示します。

5. Cart 中の ArrayList に含まれる一覧をリスト表示します。
<h:dataTable border=”1″ id=”tables” value=”#{cart.selectedItems}” var=”item”>

6. 配列の各要素は、var=”item” となるため、#{item.productname}で製品名を表示します。

ソースコードを記述後、アプリケーションを実行してください。すると画面がリフレッシュされ、入力した文字列が画面下部に表示されます。

次に Ajax 対応させます。Ajax 対応させるために記載するコードは下記の1行だけです。

5 行目:<f:ajax execute=”productname” render=”tables”/>

上記 Ajax 対応のコードはテキストフィールドの値で Ajax の Request を送信し、dataTable の描画部分を更新しています。

追記した簡易カート Facelets のソースコード(Ajax 対応):

                <h:form prependId="false">
                    <h:inputText id="productname" value="#{cart.product.productname}">
                    </h:inputText> #{' '}
                    <h:commandButton action="#{cart.addItem}" value="カートに追加">
                        <f:ajax execute="productname" render="tables"/> 
                    </h:commandButton>
                    <br/>
                    <br/>

                    <h:dataTable border="1" id="tables" value="#{cart.selectedItems}" var="item">
                        <f:facet name="header">  
                            <h:outputText value="追加された製品一覧" />  
                        </f:facet>
                        <h:column>
                            <h:outputText value="#{item.productname}" />  
                        </h:column>
                    </h:dataTable>
                </h:form>

追記したコードでアプリケーションを再度実行してください。画面がリフレッシュされず、ボタン押下時に画面下部に文字列が追加されている事を確認できるかと思います。

ViewScoped について
最後に、JSF 2.0 で追加された @ViewScoped を紹介します。上記 CDI の Cart Bean は @SessionScoped を宣言しています。つまり上記 Cart Bean はセッションの有効期限内はずっと保持されます。
JSF 2.0 では画面遷移の無い同一画面のみ有効なスコープ ViewScoped が新たに追加されました。ViewScoped は CDI(Weld) では実装されておらず、JSF の Managed Bean で実装されています。
そこで、ViewScope を扱うためには、CDI から Managed Bean に変更する必要があります(もしくは独自拡張を実施)。

Cart Bean を CDI から Managed Bean に変更するため下記のように書き直してください。

変更箇所(11〜14 行目):
// @Named(“cart”)
// @SessionScoped
@ManagedBean(name=”cart”)
@ViewScoped

Managed Bean に修正したコード:

package jp.co.oracle.cdi;

import java.io.Serializable;
import java.util.ArrayList;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.inject.Named;
import javax.validation.constraints.NotNull;

// @Named("cart")
// @SessionScoped
@ManagedBean(name="cart")
@ViewScoped
public class Cart implements Serializable{
    @NotNull
    private Product product = new Product();

    private ArrayList<Product> selectedItems = new ArrayList();

    public ArrayList<Product> getSelectedItems() {
        return selectedItems;
    }

    public void setSelectedItems(ArrayList<Product> selectedItems) {
        this.selectedItems = selectedItems;
    }
    
    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public void addItem(){
        String name = product.getProductname();
        System.out.println("Product Name: " + name);
        Product product = new Product();
        product.setProductname(name);
        selectedItems.add(product);
    }
}

変更後、再度アプリケーションを実行してください。
SessionScoped の場合は、画面をリロード(再読み込み)しても、過去に入力したデータが表示されていました。
一方 ViewScoped に変更すると画面をリロードすると(ビューが変わるため)、データが破棄されている事が確認できます。
この ViewScope は同一画面内で処理する Ajax 等に便利なので是非ご使用ください。

最後に、上記では <f:ajax/> タグを使って Ajax を実現しましたが、細かなカスタマイズを行いたい場合は、別途カスタマイズできるように JavaScript ライブラリを提供しています。
そういったニーズがある場合、JavaScript ライブラリを利用するために下記のコードを追加して実装してください。
<h:outputScript library=”javax.faces” name=”jsf.js”/>
また、jsf.ajax には addOnError(), addOnEvent(), isAutoExec(), request(), response() 等の関数が用意されていますので、これらを使って処理を柔軟に記載する事ができるようになります。

是非、JSF 2.0 の Ajax 対応を試してみてください。

Entry filed under: 未分類.

WebLogic 10.3.4 が正式リリース JSF 2.0 で Facelets タグをコメントする方法

4件のコメント

  • […] JSF 2.0 の Ajax 対応はとてもかんたん […]

  • 2. tk  |  2011年2月1日 4:13 午後

    ありがとうございます。いつも参考にさせていただいております。
    ソースをコピーして勉強させて頂いているのですが、
    なぜかうまく動かせず。。。

    import javax.enterprise.context.SessionScoped;

    import javax.faces.bean.SessionScoped;

    わたくしの環境が悪いのでしょうか、上記変更で動かすことができました。

    • 3. Yoshio Terada  |  2011年2月1日 4:30 午後

      javax.enterprise.context.SessionScoped は CDI を使っているのですが、
      プロジェクト中に beans.xml のファイルは(中身は空でも可)存在して
      ますでしょうか?beans.xml の空ファイルを作成するとどのようになるか
      ご確認頂けないでしょうか。

      ちなみに、
      javax.faces.bean.SessionScoped は JSF の Managed Bean のクラスに
      なります。

      • 4. tk  |  2011年2月1日 5:09 午後

        申し訳ありません。WEB-INFフォルダにbeans.xmlを置くと動きました。

        CDI と Managed Bean すら理解できておりませんでした。

        良い勉強になりました。
        ご丁寧にお教えいただき、ありがとうございました。

        今後ともJSFの事、記事にしていただければ嬉しいです。
        ありがとうございました。


Java Champion & Evangelist

Translate

ご注意

このエントリは個人の見解であり、所属する会社の公式見解ではありません

カレンダー

2011年1月
 12
3456789
10111213141516
17181920212223
24252627282930
31  

カテゴリー

clustermap

ブログ統計情報

  • 1,288,449 hits

Feeds

アーカイブ