Challenge Engineer Life !

エンジニア人生を楽しみたい!仕事や趣味で学んだ技術的なことを書いていくブログです。

PrimeFacesを使ったJSFリッチユーザインタフェース入門 〜ちょっと便利なコンポ達〜

はじめに

この記事は「JavaEE Advent Calendar 2012」の12日目の記事となります。
昨日は@matsumanaさんによる「NetBeansでJavaEE6開発する時にHotDeploy可能なプロジェクトタイプ」でした。
明日は@noriandさんとなります。

簡単に自己紹介

入社8年目のエンジニアです。入社以来、1つの基幹業務システム開発プロジェクトに従事し、開発は主に.NET C#2.0&WindowsFormでした。
今年から、いよいよ別の新しいプロジェクトに参画!となったのですが、Formから一転Web開発、しかもC# ASP.NETではなくJavaEEを始めることになりました。
8月頃から触り初め、勉強メモがてらに日記を付け始めたのですが、このようなイベントへお声かけを頂いて嬉しい反面とても緊張してます(^^;
Java初心者なので、他の方々の記事に比べると色々至らぬ点もあると思いますが宜しくお願い致しますm(_ _)m

PrimeFacesとは

前置きが長くなりましたが、私はJSFベースのリッチUIコンポーネントである「PrimeFaces」の紹介をベースに、様々あるコンポーネントの中から「ちょっと便利なコンポ達」を紹介させて頂きたいと思います。

PrimeFacesはPrime Teknolojiというトルコのソフトウェア会社(特にアジャイル開発やJavaEE向けのコンサルを行っているようです)により提供されています。Apache License V2ライセンスのOSSとなります。

JavaEEは金魚本(Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava)で勉強を初め、JSFはそこで初めて知りました。
その後、実際に画面サンプルなどを作ってみたのですが、実際に業務システムで使うには何らかの画面部品が欲しいなと思い、調べてみたところ様々なものがあることを知りました。
当時調べたメモを引きずりだすと以下のようなライブラリがありました。

他にもまだ色々あると思うのですが、各々のデモやShowcaseをみたり、実際に落として触ったりした中で、私はPrimeFacesに惹かれました。
とにかくコンポーネントの種類が豊富なことが一番魅力的で、実際海外での利用も多いようで情報も多くあったため、PrimeFacesを選びました。

セットアップ

NetBeans&GlassFishを使ってPrimeFacesをセットアップする方法は先日まとめましたので、そちらを見て頂ければと思います。

本題:ちょっと便利なコンポ達

近年、デザインの良いホームページやスマホ・タブレット等の普及により、ユーザさんのUIに対する目がとても肥えている感じがします。
なので「まあ、基幹業務系システムなので見た目より中身が肝心でして…」なんて回避も難しく、見た目の印象が悪いだけで
「ダサい→使えなさそう」と負のイメージが最初から抱かれ、そうなると「システムバグがでる→ほらみたことか→やっぱ使えない→なんで業務システム刷新したんだ」
なんて流れにつながりかねません(-_-;くわばらくわばら

いずれにせよリッチな画面を手軽に作れれば嬉しく、幸いPrimeFacesにはそういったコンポーネントがたくさんあるように思います。
今日はその中でも「これはちょっと便利だ」「かゆい所に手が届くな」というコンポーネントにフォーカスを当てて紹介させて頂きます。

Password

パスワードマスクは普通にあるものですが、PrimeFacesのコンポでは属性の設定を加えるだけで強度を出すこともできます。
feedback属性をtrueに指定すればOKです。メッセージを日本語にするには以下のように記述します。

<p:password id="pswd" maxlength="10" feedback="true"
    promptLabel="入力して下さい" weakLabel="パスワード強度:弱い" 
    goodLabel="パスワード強度:普通" strongLabel="パスワード強度:強い" />

feedback="false"の場合

feedback="true"で未入力の場合

feedback="true"でパスワード強度weakの場合

feedback="true"でパスワード強度goodの場合

feedback="true"でパスワード強度strongの場合

強度判定の詳細に関しては情報を(探したのですが)拾えていません。感触的には長さ、組合せ、記号有無などをみて判定してそうです。

AjaxStatus

Ajaxによる通信時、処理完了までの間に表示されるクルクルを簡単に設置できます。

<!-- AjaxStatus -->
<p:ajaxStatus id="stsAjax">
    <!-- 開始した時の挙動 -->
    <f:facet name="start">
        <p:graphicImage id="imgLoader" value="/resources/images/ajax-loader.gif" />
    </f:facet>
    <!-- 完了した時の挙動 とりあえず何も表示しないラベル -->
    <f:facet name="complete">
        <h:outputLabel value="" />
    </f:facet>
</p:ajaxStatus>

クルクルのgif画像は
http://www.ajaxload.info/
等から簡単に作れるので、その画像を指定すればチャチャっと設置できます。
ただしAjax以外(コンポーネントの属性でajax="false"とした場合等)では効かないので注意して下さい。

Watermark

検索のテキストボックス背景にうっすら文字…的なものを簡単に実現できます。

<p:watermark id="wtmSearch" for="frm:txt" 
    value="検索するキーワードを入力して下さい" />

BlockUI

ボタン連打などのガード対応をプログラムで対応せずともViewで防げます。

<p:blockUI id="blcLogin" block="pnl" widgetVar="bui"/>
<p:commandButton id="btnLogin" value="ログイン" ajax="false" action="#{loginBean.login()}" onclick="bui.show()" />


ボタンを押下した後、次ページに遷移するまでの間、上図のようにグレー色になって、クリックとかできない状態となります。
ページ遷移以外でも、保存ボタンや削除ボタンでも有効です。

ConfirmDialog

確認ダイアログです。

JSFの複合コンポーネントも普通に*1適用できるので、例えば確認ダイアログで
・「OK」の1ボタン
・「はい」「いいえ」の2ボタン
・「はい」「いいえ」「キャンセル」の3ボタン
等を1つのコンポーネントにすることもできます。

こんな感じに定義しています。

<?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:composite="http://java.sun.com/jsf/composite"
      xmlns:p="http://primefaces.org/ui"
      xmlns:c="http://java.sun.com/jsp/jstl/core">

    <composite:interface>
        <composite:attribute name="btnFirstActionListener" targets="btnFirst" default="return true;" shortDescription="1つ目のボタン押下時の処理" />
        <composite:attribute name="btnSecondActionListener" targets="btnSecond" default="return true;" shortDescription="2つ目ボタン押下時の処理" />
        <composite:attribute name="btnThirdActionListener" targets="btnThird" default="return true;" shortDescription="3つ目ボタン押下時の処理" />
        <composite:attribute name="buttonCount" default="2" shortDescription="表示するボタン数(1:OKボタン|2:Yes,Noボタン|3:Yes,No,Cancelボタン)" />
        <composite:attribute name="header" shortDescription="ヘッダ文言" />
        <composite:attribute name="message" shortDescription="メッセージ" />
        <composite:attribute name="severity" default="alart" targets="dlg" shortDescription="メッセージアイコン(info|alart)" />
        <composite:attribute name="btnFirstUpdate" targets="btnFirst" shortDescription="1つ目のボタン押下時の更新対象コンポーネントID" />
        <composite:attribute name="btnSecondUpdate" targets="btnSecond" shortDescription="2つ目のボタン押下時の更新対象コンポーネントID" />
        <composite:attribute name="btnThirdUpdate" targets="btnThird" shortDescription="3つ目のボタン押下時の更新対象コンポーネントID" />
        <composite:attribute name="btnFirstValue" shortDescription="1つ目のボタン表示名" />
        <composite:attribute name="btnSecondValue" shortDescription="2つ目のボタン表示名" />
        <composite:attribute name="btnThirdValue" shortDescription="3つ目のボタン表示名" />
        <composite:attribute name="widgetVar" shortDescription="ウィジェットID" />
    </composite:interface>
    
    <composite:implementation>
        <p:confirmDialog id="dlg" message="#{cc.attrs.message}"
                         header="#{cc.attrs.header}" severity="#{cc.attrs.severity}" widgetVar="#{cc.attrs.widgetVar}">
            
            <c:choose>
                <c:when test="#{cc.attrs.buttonCount==1}">
                    <p:commandButton id="btnFirst" value="#{cc.attrs.btnFirstValue}" 
                             oncomplete="#{cc.attrs.widgetVar}.hide()" 
                             actionListener="#{cc.attrs.btnFirstActionListener}" update="#{cc.attrs.btnFirstUpdate}" />
                </c:when>
                <c:when test="#{cc.attrs.buttonCount==3}">
                    <p:commandButton id="btnFirst" value="#{cc.attrs.btnFirstValue}" 
                             oncomplete="#{cc.attrs.widgetVar}.hide()" 
                             actionListener="#{cc.attrs.btnFirstActionListener}" update="#{cc.attrs.btnFirstUpdate}" />
                    <p:commandButton id="btnSecond" value="#{cc.attrs.btnSecondValue}"
                             oncomplete="#{cc.attrs.widgetVar}.hide()"
                             actionListener="#{cc.attrs.btnSecondActionListener}" update="#{cc.attrs.btnSecondUpdate}"/>
                    <p:commandButton id="btnThird" value="#{cc.attrs.btnThirdValue}"
                                     oncomplete="#{cc.attrs.widgetVar}.hide()" update="#{cc.attrs.btnThirdUpdate}"/>
                </c:when>
                <c:otherwise>
                    <p:commandButton id="btnFirst" value="#{cc.attrs.btnFirstValue}" 
                             oncomplete="#{cc.attrs.widgetVar}.hide()" 
                             actionListener="#{cc.attrs.btnFirstActionListener}" update="#{cc.attrs.btnFirstUpdate}" />
                    <p:commandButton id="btnSecond" value="#{cc.attrs.btnSecondValue}"
                                     oncomplete="#{cc.attrs.widgetVar}.hide()" update="#{cc.attrs.btnSecondUpdate}"/>
                </c:otherwise>
            </c:choose>
            
        </p:confirmDialog>
    </composite:implementation>
</html>

利用する側

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:k="http://java.sun.com/jsf/composite/component"
      xmlns:p="http://primefaces.org/ui"
      xmlns:h="http://java.sun.com/jsf/html">

    <!-- 1ボタン info -->
    <k:commandButtonAjax id="btnOkInfoDialog" value="1ボタンダイアログ(Info)" onclick="okInfoDlg.show();" />
    <k:confirmDialog id="dlgOkInfo" buttonCount="1"
        message="message" header="header" severity="info" widgetVar="okInfoDlg" btnFirstValue="OK" />                
    
    <!-- 1ボタン alert -->
    <k:commandButtonAjax id="btnOkAlertDialog" value="1ボタンダイアログ(Info)" onclick="okAlertDlg.show();" />
    <k:confirmDialog id="dlgOkAlert" buttonCount="1"
        message="message" header="header" severity="alert" widgetVar="okAlertDlg" btnFirstValue="OK" />
    
    <!-- 2ボタン alert -->
    <k:commandButtonAjax id="btnOkDialog" value="2ボタンダイアログ" onclick="yesNoDlg.show();" />
    <k:confirmDialog id="dlgYesNo" buttonCount="2"
        message="message" header="header" severity="alert" widgetVar="yesNoDlg" btnFirstValue="Yes" btnSecondValue="No" />
    
    <!-- 3ボタン alert -->
    <k:commandButtonAjax id="btnOkDialog" value="3ボタンダイアログ" onclick="cancelDlg.show();" />
    <k:confirmDialog id="dlgCancel" buttonCount="3"
        message="message" header="header" severity="alert" widgetVar="cancelDlg"
        btnFirstValue="Yes" btnSecondValue="No" btnThirdValue="Cancel" />


Focus

地味ですが、フォーカスの位置を決められます。

<p:focus context="pnl" />

パネルの中の最初のテキストボックスにフォーカスが当たります。

IdleMonitor

一定時間(timeout属性で指定した時間)放置プレイするとイベント起こします。自動ログオフの警告などで使えそうです。

<!-- アイドルモニタ -->
<p:idleMonitor timeout="20000" onidle="idleDlg.show()" />
<k:confirmDialog widgetVar="idleDlg" message="残りxx分でログアウトします。" 
                                  btnFirstValue="はい" btnSecondValue="いいえ" btnFirstActionListener="this.hide()"/>

Messages

メッセージは種別ごとに色分けされて表示されます。

xhtmlはこんな感じで、ボタンイベントでメッセージを突っ込んでみます。

<h:body>
    <h:form id="frm">
        <p:messages id="mes"/>
        <p:commandButton update="frm:mes" value="ボタン" action="#{messageBean.message()}" />
    </h:form>
</h:body>

バッキングビーンはこんな感じです。

package jp.co.hoge.messages;

import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

@Named(value = "messageBean")
@RequestScoped
public class MessageBean {

    public MessageBean() {
    }
    
    public void message(){
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,"Infoです","Infoです"));
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN,"Warnです","Warnです"));
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,"Errorです","Errorです"));
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL,"Fatalです","Fatalです"));
    }
}


ErrorとFatalは色が同じでマークが違うだけですね。

ThemeSwitcher

いくつかデザインの異なるテーマが定義されていて、画面デザインをユーザさんが自由に選択!なんかもできそうです。
上記で作った確認ダイアログをいくつかのテーマで表示すると以下のようになります。

結構見た目で印象変わりますね(^^)テキストボックスの角の丸み具合やグレーアウトしてる背景などもテーマによって異なります。
PrimeFacesのShowcaseサイトの右上にも付いているので、各コンポーネントの細かいデザイン具合はそこでご確認ください。
また、自分でもテーマは作成できる(まだ作ったことないのですが)ようですので、興味がある方はPrimeFacesのサイトから
ダウンロードできるドキュメントを参照して下さい。

BreadCrumb

最後はパンくずリストです。
ここまで、ほとんどがビューのxhtmlのみで、Javaプログラムが非常に少ない!ので、最後だけプログラムでコンポーネントを操作した例を掲載します。

xhtmlはこんな感じです。

<h:body>
    <f:view>
        <h:form id="breadFrm">
            <p:breadCrumb id="testBread" binding="#{componentBindingBean.bread}" />
            <p:inputText id="testInputText" binding="#{componentBindingBean.inputText}" />
            <p:breadCrumb /> <!--本来不要なはずですが、これないと1個目のパンくずが崩れて表示されてしまって… -->
        </h:form>
    </f:view>
</h:body>

プログラムで入力テキストに文字を入れたり、パンくずのリスト項目を追加してみます。

package jp.co.hoge.binding;

import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import org.primefaces.component.breadcrumb.BreadCrumb;
import org.primefaces.component.inputtext.InputText;
import org.primefaces.component.menuitem.MenuItem;
import org.primefaces.model.DefaultMenuModel;
import org.primefaces.model.MenuModel;

@Named(value="compBindingBean")
@RequestScoped
public class ComponentBindingBean implements Serializable{

    private InputText inputText;
    private BreadCrumb bread;

    public InputText getInputText() {
        return inputText;
    }

    public void setInputText(InputText inputText) {
        this.inputText = inputText;
    }

    public BreadCrumb getBread() {
        return bread;
    }

    public void setBread(BreadCrumb bread) {
        this.bread = bread;
    }
    
    public ComponentBindingBean() {
        inputText = new InputText();
        inputText.setValue("JavaEE Advent Calendar 12日目");
        
        bread = new BreadCrumb();
        MenuModel menuModel = new DefaultMenuModel();
        
        MenuItem home = new MenuItem();
        home.setId("breadItemHome");
        home.setValue("ほーむ");
        home.setUrl("home");
        menuModel.addMenuItem(home);
        
        MenuItem item1 = new MenuItem();
        item1.setId("breadItem1");
        item1.setValue("ぱんくず1");
        item1.setUrl("menu1");
        menuModel.addMenuItem(item1);
        
        MenuItem item2 = new MenuItem();
        item2.setId("breadItem2");
        item2.setValue("ぱんくず2");
        item2.setUrl("menu2");
        menuModel.addMenuItem(item2);
        
        bread.setModel(menuModel);
        bread.buildMenuFromModel();
    }
}

実行するとこんな感じです↓

さいごに

なお、上記含むPrimeFaces全てのコンポーネントはこちらで確認&触れます→ PrimeFacesのShowcase
是非一度触ってみて下さい!

明日は@noriandさんです。よろしくお願いします。

*1:ただしPrimeFacesコンポーネントを複合コンポーネントとした場合、コンポの組合せや挙動によっては上手くいかないケースがあるので、利用の際には細かい検証をお勧めします

にほんブログ村 IT技術ブログへ
にほんブログ村 にほんブログ村 IT技術ブログ Javaへ
にほんブログ村