Challenge Engineer Life !

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

Java EEを使って10分で作れる「いろふさんを探せ!」ゲーム #irof_history

この記事はいろふ Advent Calendar 2013の11日目の記事です。
昨日は@pocketberserkerさんの「いろふさんとの遭遇記」 でした。
明日は@akuraruさんです。
http://atnd.org/events/44814

「いろふさんを探せ!」ゲーム

こんなゲーム(?といえるのか)を10分くらいで作りました。細かい解説は後ほど。


そもそも@irofさんって何なん…

去年Javaを触り始めて、一番理解が難しかったのが「いろふさん」の存在でした。
でも今年の春にJJUG CCCで実物を見て、おぉ、実際に存在する方だったのか…と理解(^^;
そのときの参加ブログ↓
JJUG CCC 2013 Springに参加しました~!

いろふAdvent Calendarとは…

謎でした。書いてる今も謎です…w

数日前まで参加する気も全然なかったのですが、以下、鬼のような行為によって参加に至ります…。

@bitter_foxさんが「動くirofさんに会いたい!!」を書いていて、面白いなぁと何気なく、以下つぶやきました。

そこから





既に登録されてました…鬼に出会いました orz

いろふさんを探せ!解説編

気を取り直して、私のネタは、NetBeansJava EEが入っていればすぐ作れる簡単なゲーム紹介です。
ウォーリーを探せ!のいろふさん版的な。。。

本当はNetBeans7.4とGlassFish4.0のJava EE7でやる予定でしたが、かくかくしかじかで、NetBeans7.3.1、GlassFish3.1.2.2のJava EE6で作りました(^^;

先に流れから

まずは、Webアプリを起動すると以下画面が表示されます。
f:id:kikutaro777:20131211191131j:plain

デスクトップに、いろふさんがいます。
f:id:kikutaro777:20131211191205j:plain

ドラッグ&ドロップしましょう。(ちなみに召喚ボタン押すとファイル選択ダイアログ)
f:id:kikutaro777:20131211191227j:plain

召喚されました。
f:id:kikutaro777:20131211191453j:plain

ゲームの世界に連れ込みます(単なるアップロードボタン)
成功すると、消え去ります。
f:id:kikutaro777:20131211191517j:plain

ゲーム画面へボタンで次のページにいきます。
こんな画面です。
f:id:kikutaro777:20131211191549j:plain

召喚されたいろふさんを触ると、スライドしていろふさんが出てきます。
f:id:kikutaro777:20131211191654j:plain
f:id:kikutaro777:20131211191646j:plain

画像からマウスポイントを離すと崩れ去ります。
f:id:kikutaro777:20131211191718j:plain

これが本物のいろふさんです。

では次に、増殖ボタンを押します。すると、偽物のいろふさんをいくらでも発生することができます。
f:id:kikutaro777:20131211191746j:plain

本物のいろふさんも、偽物のいろふさんもドラッグで自由に移動できるので、ごちゃまぜにして準備完了です。

いろふさんを愛するプレイヤーに、どこに本当のいろふさんがいるか探してもらいましょう!

みつけると
f:id:kikutaro777:20131211191839j:plain

いいことあるかも!

コード

ドラッグ&ドロップとか、画像にマウスオーバーしてエフェクト(画像がスライドイン、コラスプしたり)、画像を移動したり…10分はうそでしょ!と思われるかもしれませんが、JSFのPrimeFacesというコンポーネントを使っていて、簡単に実現しています。

  • ドラッグ&ドロップのアップロードやアップ画像のサムネイル表示はFileUploadコンポーネント

http://www.primefaces.org/showcase/ui/tooltip.jsf

http://www.primefaces.org/showcase/ui/draggableBasic.jsf

を使ってます。

なので、ほとんどビュー定義のみで

最初のアップロードするページビューはこんな感じ。

<h:body>
    <h:form>
        <p:fileUpload mode="advanced" 
                  label="召喚する"
                  uploadLabel="ゲームの世界に連れ込む"
                  cancelLabel="現実世界に戻してあげる"
                  fileUploadListener="#{irofSanUploadBean.handleFileUpload}" />
        <p:commandButton value="ゲーム画面へ" ajax="false" action="irofSanCooking.xhtml" />
    </h:form>
</h:body>

次のゲームページはこんな感じ。

<h:body>
    <h:form id="frm">
        <p:commandButton value="増殖" actionListener="#{irofSanCookingBean.incrementIrofSan()}" update="@form"/>
        <p:graphicImage id="irofSan" url="/images/irof.jpeg" />
        <p:draggable for="frm:irofSan" />
        <p:tooltip for="frm:irofSan" showEffect="slide" hideEffect="explode">
            <p:graphicImage id="tooltipIrofSan1" url="/images/irof.jpeg" />
        </p:tooltip>
    </h:form>
</h:body>

コンポーネント置いてID指定するだけ…(^^;

増殖ボタンでコンポーネントが増える部分はプログラム側でコンポーネントを生成しています。

package jp.co.hoge.irofsanwebapp;

import java.io.Serializable;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.context.FacesContext;
import org.primefaces.component.dnd.Draggable;
import org.primefaces.component.graphicimage.GraphicImage;

@Named
@SessionScoped
public class IrofSanCookingBean implements Serializable{
    
    private int cnt;
    
    public void incrementIrofSan(){
        cnt++;

        //プログラムでコンポーネントを追加
        UIForm form = (UIForm) findComponent(":frm");
        GraphicImage img = new GraphicImage();
        img.setId("irofSan" + cnt);
        img.setUrl("/images/irof.jpeg");
        img.setStyle(randomPosition());
        form.getChildren().add(img);
        
        Draggable dra = new Draggable();
        dra.setFor(img.getId());
        form.getChildren().add(dra);
    }
    
    private String randomPosition(){
        StringBuilder posStr = new StringBuilder();
        posStr.append("position:absolute;left:");
        posStr.append(Math.round(Math.random() * 1280));
        posStr.append("px;top:");
        posStr.append(Math.round(Math.random() * 640));
        posStr.append("px;");
        return posStr.toString();
    }
    
    private UIComponent findComponent(String componentId){
        return FacesContext.getCurrentInstance().getViewRoot().findComponent(componentId);
    }
}

ちなみに昨日の時点では、アップロードした画像をImageCropperコンポーネントでバラバラにするという残酷なネタだったのですが、ImageCropperが動かなくて、デバッグしてもわからなかったので断念しました(^^;
http://www.primefaces.org/showcase/ui/imageCropper.jsf
うーん、なぜ動かない。。。

NetBeansでGlassFishのリモートデバッグ

この記事は「GlassFish Advent Calendar 2013」の2日目の記事となります。

昨日はGlassFish Users Group Japanの運営をされていますHASUNUMA Kenjiさん(@btnrouge)による「GlassFish Community便り」でした。

明日は再度HASUNUMA Kenjiさん(@btnrouge)です!

デプロイした環境で動かない…だと

先日、手元の開発環境(NetBeans+GlassFish)で上手く動くのに、デプロイしたサーバ環境(GlassFish)で上手く動かない、という事象がお隣のチームであったようです。

ログを解析すれば何らか情報が得られるでしょうが、なんと信じがたいことにアプリケーションによるログ埋め込みしてないとか何とか聞こえ………いや、聞こえないっ、僕には何も聞こえないっ(((゚Д゚)))ガタガタ

f:id:kikutaro777:20131202182457j:plain

横で聞いてるだけでショボ-ン(´・ω・`)

それはさておき、そういう状況下でも、VisualStudioって確かリモートデバッグして原因究明できたよなぁ…とふと思ってNetBeans+GlassFishってどうなんだろと調べてみたら…

普通にできるらしい(^^;

知らなかった…ってか、これ知ってたら何かと便利なのに気づいてなかった……

うわぁぁ(ノД`)

ということで、2010年に@backpaper0さんがEclipseベースに既に書かれて(さらにそこに@shinsan68kさんがNetBeansなら超簡単にできるよと突っ込みあり)いますが、NetBeansでの具体的な手順を書いてみようと思います!

確認環境

自分が試したのは以下環境でした。

■サーバ(リモートデバッグ対象)環境
OS :Windows Server 2008 R2 64bit
AP :GlassFish 3.1.2.2
Java :JDK7

■クライアント(開発)環境
OS :Windows 7 Professional 64bit
IDE :NetBeans 7.3
Java :JDK7

手順

まずはデバッグしたいサーバ環境のGlassFishを一旦止めて、以下のdebugオプション付きで起動します。

asadmin start-domain --debug

今回の環境ではdomainは1つだったのでドメイン名は省いてます。

でこんな感じのメッセージが表示されました。
f:id:kikutaro777:20131129191911j:plain

続いてサーバマシンのデバッグポート(上記メッセージに合ったので9009)をあけました。

サーバ側の設定はこれで完了。

で、手元の開発マシンのNetBeansを起動してデバッグメニューから「デバッガのアタッチ」を選びます。

f:id:kikutaro777:20131129192552j:plain

すると以下のウィンドウが表示されます。今回はそのままJPDAにして、サーバのIPと先ほどあけたポートを入れました。

f:id:kikutaro777:20131129192556j:plain

ちなみにJPDAって何…(^^;と調べたらJava Platform Debugger Architectureの略称とのこと。知らなかった(>_<)

Java SE7のドキュメントは英語ですが、SE6では日本語のドキュメントがありました。
http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/
http://docs.oracle.com/javase/jp/6/technotes/guides/jpda/

で、OKボタンを押すと、しばらくして以下の状態がNetBeans画面下の出力に表示されて

f:id:kikutaro777:20131129192604j:plain

その後、ユーザー・プログラム実行中が出てデバッグ実行しているような状態となります(下図のようにデバッグ終了ボタンが出たり)。

f:id:kikutaro777:20131202183622j:plain

次にデプロイしたWebのプロジェクトを開いて、止めたい部分のソースにブレイクポイントを置きます。

f:id:kikutaro777:20131129192608j:plain

で、ブラウザを起動して、デプロイされてるWebアプリケーションに普通にアクセスして、ブレイクの部分に該当するような操作をすると…

f:id:kikutaro777:20131129192612j:plain

おお!ブレイクに止まった!!!

もちろん普通にウォッチもできるし、ローカルでデバッグしてるのと同じ感じです。

結局最初の問題がなんだったか…私はわからないのですが、リモートデバッグ機能を確認できて個人的にはめでたしめでたしでした。

JSF2.2のFaces Flows(FlowScoped)

この記事は「JavaEE Advent Calendar 2013」の1日目の記事となります。
明日はManabu Matsuzaki(@matsumana)さんです。宜しくお願いします!

Java EE 7登場

今年は6月にJava EE 7がリリースされました。

Java EE 6からのアップデートは大小様々ありますが、今日はJava EE 7の中からJSF(JavaServer Faces)に着目して、JSF2.2で新しく追加されたFaces Flowsについて書いてみたいと思います。Faces Flowsは、Big Ticketと言われる目玉機能の1つですが、探してみたら意外と日本語情報がなかったので(^^;

JSFとは?

本題の前に、そもそもJSF(JavaServer Faces)って何?ということについて少しだけ。

JSFは現在のJava EEにおける画面構築の標準仕様で、コンポーネントベースにWeb画面を構築するものです。.NETのWebFormと感覚的には近いものです。

JSFは大きな風呂敷でWeb内部を隠したような抽象化がされており、逆にそれが敬遠されることも多いのが現状です。
オープンなWebシステム構築にはあまり向いていませんが、業務系システム等では有効に使えるケースもあるのではないかな?と私は実際に使って思っています。

また、海外では様々なJSF用のリッチコンポーネントライブラリが開発されていたり、情報も細かいもの含めて豊富にあるので、それなりに利用されているように思います。

JSF2.2のアップデート内容

ここを細かく書くと今日の主題に到達しない…のと、日本語情報も既に多くあるため、後日まとめようと思います。

概要をざっと知りたい方は@den2snさんが以前にOracleさんのセミナーでお話された以下が役に立つと思います。

Faces Flows

Faces Flowsとは何か?ですが、すごくざっくり書くと、あるページからあるページまでの一連の流れを1つのフローとして定義する仕組みです。

絵でイメージかくとこんな感じ。ちょっと雑すぎましたが…。

f:id:kikutaro777:20131130235823j:plain

ノードが1つのWebページで、ノードをつないでいるエッジが画面遷移です。
上段のフローと下段のフローは別物ですが、お互いのフローを結びつけることも可能です。

だいぶざっくりな説明ですが、JSR-344では「有向グラフ(directed graph)」という言葉を使ってノード(node)がビュー、エッジ(edge)を画面遷移と表現していました。詳しく知りたい方は以下を参照して頂くのが良いと思います。

※海外サイトみると「Faces Flow」とFlowが単数形のものが多いですが、JSRではFlowsと複数形になっているので、ここではすべて複数形にして書いています。

フローの定義方法

フローを定義する方法は大きく分けて2種類あります。

  • プログラムによる定義

FlowBuilderクラスを利用してJavaプログラムでフローを定義します。

  • XMLによる定義

XMLタグを利用してフローを定義します。

言葉だけだとわかりにくいので、今回Advent用に作ったサンプルコードから定義部分を表示します。

プログラムによる定義

FlowBuilderが持つメソッドでフロー定義をしていく形です。今回のサンプルでは

  • フローが呼び出された際に処理を呼ぶ(initializer())
  • ノードとページのマッピング(viewNode())
  • メソッドを呼び出すノード(methodCallNode())
  • 条件によるフローのスイッチング(switchNode(), switchCase())
  • 別に定義されたフローへの遷移(flowCallNode())

などのメソッドを使った例にしています。

XMLによる定義

先のクラスで定義したのと同じレベルが記述できると思いますが、今回のサンプルでは以下のように一部だけとなっています。すみません。

サンプルアプリケーション

作ったサンプルは以下のような動きをするものです。

f:id:kikutaro777:20131201000303j:plain

上図のノードはページを表していますが、裏でデータを保持するBacking Bean(管理対象Bean)は、会員登録フロー、特典選択フローともにそれぞれ1つだけあります。@FlowScopedアノテーションをつけることで、フローの間だけ生きているBacking Beanとなります。

また、特典選択フローはWebページとBacking Beanが1つのJarファイルになっており、会員登録フローのWebアプリがそのJarを参照する形で構成されています。

実際のソースコードは以下GitHubに置きました。

実際に画面を動かした結果は以下です。

フロー図のTopノードにあたるindex.xhtmlです。これが最初に表示されます。
会員登録するリンクを押すと、画面遷移し、会員登録フローが呼び出されます。
f:id:kikutaro777:20131201000712j:plain

会員登録フローの最初のページです。
f:id:kikutaro777:20131201000742j:plain

会員登録フローが呼び出されたので、initalizerメソッドが走ってコンソールに以下表示されています。

f:id:kikutaro777:20131201104103j:plain

名前と年齢を入れます。
f:id:kikutaro777:20131201102348j:plain

メッセージ入力ボタンを押します。
f:id:kikutaro777:20131201102406j:plain

メッセージ入れて登録確認へ
f:id:kikutaro777:20131201102441j:plain

で、今まで入力した情報が表示され「名前の修正」を押せば、名前入れたページへ、「終了」を押せばindexに遷移します。
f:id:kikutaro777:20131201102517j:plain

なんてことない普通の画面遷移なんですが…(^^;

ちなみに、会員登録のBacking Beanのフィールドは以下のような定義です。

年齢を保持する変数を定義していません。

代わりにFacesFlowsで追加されたEL式オブジェクトである#{flowScope}を使っています。

ビュー定義は以下だけなので、お手軽です。中身はjava.util.Mapのようで、キーが自分で定義した属性(ここではage)となっていて、値に入力された値が格納されていました。

<h:inputText value="#{flowScope.age}" />

ちなみにこの値をプログラム内で扱いたい場合には

Map flowObject = FacesContext.getCurrentInstance().getApplication().getFlowHandler().getCurrentFlowScope();
System.out.println(flowObject.get("age"));

という感じで取れます。下手に使うとキー定義の嵐になりそうですが(^^;
サンプルでは「テストボタン」を押したときに、この処理を読んでいるので試してみて下さい。

別フローを呼び出す

では、先ほどの状態から「名前の修正」を押して以下画面に戻ってみます。
f:id:kikutaro777:20131201104934j:plain

選択された特典コードというラベルの横は空白となっていることを確認して「特典を選ぶ」を押します。

ここで、Jarにパッケージ化された中に定義してある特典選択フローへ移ります。
特典選択フローにもinitializer定義(こちらはXMLなのでタグですが)しており、処理が呼び出されて以下表示されます。

f:id:kikutaro777:20131201104451j:plain

画面はこんな感じに。
f:id:kikutaro777:20131201105022j:plain

会員登録フローで入力していた情報をinbound-parameterで受け取って表示しています。
同一のフロー内ではBacking Beanを通じて簡単にデータ共有できますが、別定義のフローと値をやりとりする場合には、こうした定義が必要となるようです。

次に、特典は適当にBを選んで、戻るボタンを押します。
すると、選ばれた特典のコードが表示されています。
f:id:kikutaro777:20131201105148j:plain

ここも先ほどと同じですが、今度はoutbound-parameterで値を呼び出し側に渡しています。

メリットは?

このサンプル、しょぼいわりには意外と手間がかかったのですが、これって何が嬉しいんだろう…(^^?とちょっと思ったり思わなかったり。

メリットをあげてみると大体以下のような感じかと思います。
海外のサイトでも概ねこの辺りの記述なので。。。

  • RequestScopeやViewScopeより長く、SessionScopeより短いスコープ定義

SessionScopeだと長すぎるけど、ViewScopeだと1画面だけで短い…もやもや、って感じたことがあるので、FlowScopeは嬉しい気もします。

でもそれってConversationScopeも同じようなことができるよね?というのがありそうですが、ConversationScopeであれこれ画面遷移がある場合、ちゃんと設計して作らないと

「あれ、会話が終わらね…」
「うわ、この操作したときダメじゃん」

とか、なり…ませんかね(^^;?
自分は前ちょっと触ったときそんな感じでした。センスがないだけかもですが;

  • フローをパッケージング(jar化)することができるため、フローの再利用ができる

今回のサンプルもそうですが、これが大きいメリットなのだと思います。
JSFは「コンポーネント」という概念が強く、再利用するには便利です。
複数の画面部品をくっつけた「複合コンポーネント」も、その1つでした。

今回FacesFlowsが入ることで、ページと遷移のまとまった単位を再利用できるようになりました。うまく設計すれば、プロジェクトが違っても再利用したフローが作れるかもしれません。

実際はなかなか難しそうですが(^^;

  • ブラウザの複数ウィンドウ(タブ)でも各々でフローが管理される

FacesFlowsでは、ブラウザのタブごとにフローを制御することができます。
サンプルでやると、以下のような感じです。

1つのタブで入力して
f:id:kikutaro777:20131201111408j:plain

もう1つタブを起動して、入力します
f:id:kikutaro777:20131201111413j:plain

同一セッションですが、各々で制御されており、情報が混ざることはありません。
f:id:kikutaro777:20131201111522j:plain

f:id:kikutaro777:20131201111526j:plain

これもConvrsationScopeは同じなんですが(^^;うーん。

余談

Faces FlowsはJSF2.2が出たときに少し試そうとしたのですが、情報が少ないのと、実際に書いてみても全然動かなくて困ってました(^^;
今年JavaOne2013に参加したのですが、その際、Faces Flowsのデモをやっているセッションがあって、その方がGitHubにサンプルもあげてました。

そのセッションレポートは以下
Challenge JavaOne 2013 レポート - 10 Tips for Java EE 7 with PrimeFaces -
https://github.com/marfous/j1demo-pf

今回のサンプルもほとんど上記のをベースに、そこでは使われていなかったメソッドを色々試した、というものです。
やっぱり実際に動く参照があることのありがたさを身に染みました…(^^;

Java EE Advent Calendar 2013を作成しました!!

Java EEに関してまだまだ初心者レベルな自分がAdvent Calendar立てるのもかなり恐縮なのですが…(経緯は最後の余談に)是非ご参加下さい!

Java EE Advent Calendar 2013
http://www.adventar.org/calendars/152

Java EEに限らず、SpringやSeasarなどのフレームワークアプリケーションサーバGlassFishは別途Advent Calendarが立っているので、そちらが良いかと思います>http://www.adventar.org/calendars/149)等々、なんでも構いません。

Advent Calendarに関して

このブログを書き始めて1年ちょっと経ちました。

色々投稿してきましたが、一番思い出深いのは去年のJava EE Advent Calendar 2012に投稿したエントリだと思います(^^;

それまで「Advent Calendar」なる存在すら知らず、突然にOracleの寺田さんから「参加しませんか?」と声をかけて頂き、「えっ、えっ」ってなりました(^^;

Advent Calendarについては以下が詳しいです。

すごく簡単にいうと、1つの技術テーマに関して12月に色んな人で毎日ブログ書こう!みたいな(^^;

ちなみに参考までに、去年のJava EE Advent Calendarは以下です。

去年のJava EE Advent Calendar 2012
http://atnd.org/events/33783

余談

今年も@megascusさんが立てるかなー、と思いつつ、最近JJUGのイベント活動等でお忙しい感じもあってお疲れな感じも…。去年・今年と色々お世話になったので、今年のJava EE Advent Calendarを代わりに立てさせて頂くことになりました(^^;

宜しくお願いいたします!

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へ
にほんブログ村