Challenge Engineer Life !

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

Faces Flowでリダイレクトできない…

2015/2/14 追記
本件、odaさんが解決してくださいました(^^)!!!ありがとうございます!

Faces Flow でリダイレクトしてみる - お だ のスペース

Faces Flowでは、画面遷移の際にaction="hoge.xhtml"と指定するのではなく、action="hogeId"のようにView Node ID(でいいのかな?)を利用するみたいです。

簡単なFaces Flowサンプルはこちら↓


kikutaro/SimpleFlow · GitHub

で、通常JSFでは、画面遷移した際にURLを遷移先ページへ変えるには、リダイレクトする必要があります。定義はaction="hoge.xhtml?faces-redirect=true"という形。

じゃあFaces Flowの際にはaction="hogeId?faces-redirect=true"みたいに書くの?と試したけど、予想通り駄目でした(^^;

どうやらFlowを定義するところで

flowBuilder.navigationCase().fromOutcome(firstFlowId)
               .toViewId("/secondFlow.xhtml").redirect();

のようにredirect()というのがあるようです。

が、色々やっても動かない…。

FlowBuilderの設定では

  • definingDocumentId
  • viewNodeId
  • viewId
  • flowDocumentId
  • vdlDocumentId

などの言葉が入り乱れていて、なんとも…。この辺、一度整理したい。

なお、同じようにFaces Flowでredirectを試みている方々をみつけたのですが…動いたぜ!っていう回答がない…。


jsf 2.2 - How to enter a JSF 2.2 flow with faces-redirect - Stack Overflow


Returning from a JSF flow with faces-redirect | Oracle Community


JSF 2.2 Navigating to a flow using a navigation... | Oracle Community

まぁ、今回の場合はリダイレクトしなくてもいいので何とかなるのだけど、何が悪いのか全然わかりませんでした(^^;上のStackOverflowに良い回答が来るの待ち…。

Faces Flowで即時にフローを開始したい

今かかわっている案件、画面数は少ないのですが、業務的には画面間を行き来することが多いような画面となっています(なら画面分けるのやめてSPAに、というのは置いておいて…)

開発はJava EE 7でJSFを使うのですが、ViewScopeだとちょっと微妙で、候補としてはSessionScope、ConversationScope、そしてJSF 2.2で入ったFlowScopeのいずれかかなと。

Conversationはあまり良い思い出がないので、FlowScopeことFaces Flowを初めて実戦投入してみようかな…と思っています。

Faces Flowについては2年前のJava EE Advent Calendar 2013で書いてました。

で、今回のシステムは、他のベンダさんが提供してるWebシステムから呼びされるもので、呼び出された瞬間にフローを開始したいです(下図)。

f:id:kikutaro777:20150209225201j:plain

スタブのページ用意して、URLリンクで普通にフローを呼び出してみたのですが、フローの開始にはなりませんでした。

色々なサンプルをみたのですが、ほとんどがindex.xhtmlのようなページがあって、そこからoutcome属性でフローに入って…という流れのものばかり…。

悩みで近いのは次の2つでした。


jsf 2.2 - How to enter a JSF 2.2 flow with faces-redirect - Stack Overflow


jsf 2 - Starting faces flow - Stack Overflow

2つ目のコードをみると、ConfigurableNavigationHandlerのperformNavigation()にてoutcomeと同じハンドリングができるようなので、1枚ダミーのページをはさんでリダイレクトする形に……(下図)。

f:id:kikutaro777:20150209231417j:plain

これでフロー開始ができました。が、なんかダサい…。良いやり方ないのかな。

PrimeFacesのTabViewでTabを非表示にする

PrimeFacesのTabViewコンポーネントで、条件によってTabを非表示にしたい状況がありました。

ここではわかりやすくするため、下図の画面を例にします。

タブとして乃木坂46メンバが並んでいる中に堀北真希さんが入っていて、(メンバじゃないので)堀北さんタブを非表示にしたい。

f:id:kikutaro777:20150121203048j:plain

自分の記憶では、PrimeFaces 3.5のときは普通にrenderedで非表示にできたと思うのですが…。PrimeFaces 5.1を使うと
下図のようになります。タブの中身が非表示^^;

f:id:kikutaro777:20150121203053j:plain

他のタブはちゃんと中身も表示されます。

f:id:kikutaro777:20150121203056j:plain

ビューの定義

<p:tabView id="tabview" dynamic="true" cache="false"
         value="#{indexBean.playerList}" var="player">
    <p:tab title="#{player.name}" rendered="#{!player.invisible">
        <h:outputText value="#{player.id}" />
    </p:tab>
</p:tabView>

仕方がないので、こうしました。Tabコンポが持つtitleStyleで、条件満たした際にdisplay : none。

<p:tabView id="tabview" dynamic="true" cache="false"
         value="#{indexBean.playerList}" var="player">
    <p:tab title="#{player.name}" titleStyle="#{player.invisible ? 'display : none;' : ''}">
        <h:outputText value="#{player.id}" />
    </p:tab>
</p:tabView>

実行すると次のようになります。

f:id:kikutaro777:20150121203059j:plain

PrimeFacesのTabViewではactiveIndexを内部で管理していて、renderedでタブを消したときとかに、あまり綺麗に管理されていなかった記憶があります。その辺をトータルに対応するために、中身だけ非表示になかったの…かな。リリースの対応履歴を一応みましたが、それっぽいのは見つけられませんでした。

PrimeFacesのGrid CSSが動かなくてハマった

PrimeFacesのバージョン5.1で新しく入った「Grid CSS」では、JSFでも手軽にレスポンシブWebチックなレイアウトができる機能(コンポーネント?)です。

例えば2つのテーブルをpanelGridで横並びに置いてみると、次の表示になります。
f:id:kikutaro777:20141202220721j:plain
Faceletsは次のような定義です(管理Beanは省略)

<p:panel header="画面名">
    <h:panelGrid columns="2">
        <p:dataTable var="actless" value="#{mitsumoriConfigBean.actlessList}">
            <p:column width="30">
                <p:selectBooleanCheckbox />
            </p:column>
            <p:column headerText="女優名">
                <h:outputText value="#{actless}" />
            </p:column>
            </p:dataTable>
                <p:dataTable var="book" value="#{mitsumoriConfigBean.photoBookList}">
            <p:column headerText="写真集名">
                <h:outputText value="#{book}" />
            </p:column>
        </p:dataTable>
    </h:panelGrid>
</p:panel>

この表示でブラウザの横幅を縮めていくと
f:id:kikutaro777:20141202221058j:plain
こんな感じにテーブルの幅が小さくなって横並びのままです。

で、Grid CSSを使うと次のような初期表示から
f:id:kikutaro777:20141202221212j:plain
縮めていくと、あるタイミングで次のように1つ目のテーブルの下にまわりこみます。
f:id:kikutaro777:20141202221243j:plain
Faceletsは次のような感じで、最初のdivにある「ui-grid-responsive」がレスポンシブの指定となります。

<h:outputStylesheet name="grid/grid.css" library="primefaces" />
<p:panel header="画面名">
    <div class="ui-grid ui-grid-responsive">
        <div class="ui-grid-row">
            <div class="ui-grid-col-6">
                <p:dataTable var="actless" value="#{mitsumoriConfigBean.actlessList}">
                    <p:column width="30">
                        <p:selectBooleanCheckbox />
                    </p:column>
                    <p:column headerText="女優名">
                        <h:outputText value="#{actless}" />
                    </p:column>
                </p:dataTable>
            </div>
            <div class="ui-grid-col-6">
                <p:dataTable var="book" value="#{mitsumoriConfigBean.photoBookList}">
                    <p:column headerText="写真集名">
                        <h:outputText value="#{book}" />
                    </p:column>
                </p:dataTable>
            </div>
        </div>
    </div>
</p:panel>

ui-grid-col-6とかいうのは、横幅が12区画に分かれていて、左に6区画、右に6区画といった指定になっています。これは自由に変えられて、4区画と8区画とか、2区画を6個とか色々できます。

そのイメージでPrimeFacesのShowcaseをみるとイメージしやすいかなと。

http://www.primefaces.org/showcase/ui/panel/grid.xhtml

で、当初Showcaseのサンプルをみながら、そのまま書いてたのですが、全く動かず…。

別にエラーになるわけでもないので、全然わからなかったのですが…調べてみたら

安定のStackOverflow(^^;

ということで、上記サンプルのfacelets冒頭に入れたcssの宣言が必要でした。

<h:outputStylesheet name="grid/grid.css" library="primefaces" />

この辺はドキュメントかサンプルにも明示しておいてほしいなぁ…。

PrimeFaces Extensions 3.0.0の新しいコンポーネント

JSFのリッチコンポーネントライブラリ「PrimeFaces」の拡張ライブラリである「PrimeFaces Extension」がバージョンアップしてました。
3.0.0となって、次のコンポーネントが新たに追加されています。

  • Analog Clock
  • Timer
  • Knob
  • Document Viewer
  • GChart
  • Gravatar

それぞれ簡単に紹介します。

Analog Clock

名前の通り、アナログ時計ですね(^^;

Faceletsにて次のように定義するだけで表示されます。

<pe:analogClock />

が、で・でかい。デフォルトだとブラウザの最大幅で出るようです。

f:id:kikutaro777:20141107155008j:plain

width属性でピクセルが指定できました。

<pe:analogClock width="300" />

f:id:kikutaro777:20141107155023j:plain

Timer

カウントダウンするタイマーです。5秒を設定してみると

5…

f:id:kikutaro777:20141107163953j:plain

4…

f:id:kikutaro777:20141107163958j:plain

3…2…1…0

listener属性で、カウントがゼロとなったときにイベントを呼び出せるので、メッセージを出してみました。

f:id:kikutaro777:20141107164009j:plain

Faceletsはこんな感じ。

<h:form>
    <p:growl />
    <pe:timer timeout="5" listener="#{extNewCompBean.timerZero()}" update="@form"/>
</h:form>

CDI管理Beanにてメッセージ生成処理のメソッドを定義してます。

@Named(value = "extNewCompBean")
@RequestScoped
public class ExtNewCompBean {

    public void timerZero(){
        FacesContext.getCurrentInstance().addMessage("timer", new FacesMessage("タイマーゼロ―"));
    }
}

Knob

(ドア)ノブみたいなコンポーネントです。占有率みたいなグラフで使う感じ…でしょうか。

f:id:kikutaro777:20141108211048j:plain

マウスクリックやドラッグでグルッと塗られます。

f:id:kikutaro777:20141108211124j:plain

ちょうど堀北さんの年齢を示してますね。

Document Viewer

Mozilla Foundationが開発しているpdf.jsを利用したPDFビューアコンポーネントのようです。まるでAcrobat Reader(^^;;

f:id:kikutaro777:20141108211647j:plain

PrimeFacesにもMediaコンポーネントというのがあって、PDFビューアの機能もありますが、Mediaコンポでは操作メニューのようなものはないので、こちらのほうが多機能です。

GChart

Google Chart」をJSF向けにラップしたコンポーネントとのことです。

基本的にはサーバサイドでGChartModelを生成して、GChartのvalue属性とバインドして使う感じみたいです。

まだ実際には触ってません。

f:id:kikutaro777:20141108212006j:plain

f:id:kikutaro777:20141108212216j:plain

Gravatar

プロフィール画像など登録しておく「Gravatar」サービスのコンポーネント(^^;;

PrimeFacesなんでもありになってきたな…。

これも実際にはまだ試してないです。

f:id:kikutaro777:20141108212508j:plain

ハマったとこ

理由はわからないのですが、テーマ設定しないと、一部コンポーネントで次のエラーが動きませんでした(^^;

Expression cannot be null

f:id:kikutaro777:20141107154230j:plain

デフォルトテーマで動いてくれそうなのになぁ…。

PrimeFaces 5.1がリリースされてました!

PrimeFaces 5.1がリリースされていました。
てっきり有償サポート版と思っていたら、今回はコミュニティリリースとなっていて、自由に使える形でした(^^;

http://blog.primefaces.org/PrimeFaces 5.1 Released |

個人的にバーコードコンポが地味に感動したのでブログに書いてみます。

新しく追加となったコンポーネントは大きくは4つのようです。

Ribbonコンポーネント

Officeとかでよく見るリボンコントロールです。

タグを定義して、その中でごとにグルーピングできます。
次の定義をしてみました。

<p:ribbon>
    <p:tab title="ホーム">
        <p:ribbonGroup label="クリップボード">
            <h:panelGrid columns="2">
                <p:commandButton value="貼り付け" icon="ui-icon-document" />
                <h:panelGrid columns="1">
                    <p:commandButton icon="ui-icon-scissors" />
                    <p:commandButton icon="ui-icon-copy" />
                </h:panelGrid>
            </h:panelGrid>
        </p:ribbonGroup>
        <p:ribbonGroup label="フォント">
            <p:selectOneMenu>
                <f:selectItem itemLabel="MS 明朝" itemValue="menu1"  />
            </p:selectOneMenu>
        </p:ribbonGroup>
   </p:tab> 
   <p:tab title="挿入" />
</p:ribbon>

表示はこんな感じです。
f:id:kikutaro777:20141010233907j:plain

InputSwitchコンポーネント

スマホとかでよくみるオン・オフのスイッチです。

<p:inputSwitch />

と定義しただけですが表示できました。

f:id:kikutaro777:20141010230544j:plain

クリックするとスライドするアニメーションでOffに切り替わりました。

f:id:kikutaro777:20141010230615j:plain

もちろんラベル文言を指定する属性があるので

<p:inputSwitch onLabel="オン" offLabel="オフ" />

みたいに宣言すればラベルが変わります。

f:id:kikutaro777:20141010230801j:plain

Barcodeコンポーネント

バーコード画像を自動生成してくれるコンポーネントです。
QRコードも対応してて、結構面白い!

定義が簡単で、QRコードの場合はtypeにqrと指定して、valueで値を設定するだけです。

<p:barcode value="http://kikutaro777.hatenablog.com/" type="qr" />

上記定義で実行すると次のQRコードが表示されます。

f:id:kikutaro777:20141010231123j:plain

スマホで読み取ってみました(^^)

f:id:kikutaro777:20141010231827j:plain

ちゃんと読み取れてる!

QRコードはqrgenというライブラリ参照が必要で、Mavenであればpom.xmlへ次の依存性を定義すれば上記のように使えます。

<dependency>
    <groupId>net.glxn</groupId>
    <artifactId>qrgen</artifactId>
    <version>1.4</version>
</dependency>

普通のバーコードもできるようですが、そちらはbarcode4jを利用するようです。
barcode4jの2.1はMaven Centralにはないらしく、手動でのjar参照が必要となるらしいです。

普通のバーコードは今回試してません(^^;読み取り機ないし…

Grid CSSコンポーネント

Grid CSSは軽量なレスポンシブWebを実現するコンポとのことです。

PrimeFacesのShowcaseで動きを確認できます。
PrimeFaces ShowCase

その他

詳細は以下ページをご確認下さい。

PrimeFaces 5.1 Released |

いつも通りのユーザガイド(PDFドキュメント)も出ています。

http://www.primefaces.org/documentation

JSFで1レコード複数行のテーブル表示

PrimeFacesとか使うとそれっぽいコンポもあるのですが、シンプルにやるなら以下とかどうでしょうか(^^;

<h:dataTable id="todoTbl" var="todo" value="#{todoBean.todoList}"  border="1">
    <h:column>
        <h:outputText value="#{todo.id}" />
    </h:column>
    <h:column>
        <h:panelGrid columns="1">
            <h:outputText value="#{todo.todo}" />
            <h:outputText value="#{todo.duedate}" />
        </h:panelGrid>
    </h:column>
</h:dataTable>

Columnの中をpanelGridで分割してるだけです。

以下のデータベースのデータを

f:id:kikutaro777:20140719092309j:plain

表示すると

f:id:kikutaro777:20140719092332j:plain

JSFでもHttpSessionListener使えばいいのかな

JSFで、セッションタイムアウトをハンドリングしたい、と聞くと、「ViewExpiredException」拾って何かする、くらいしか知識がなかったのですが…(^^;(というか前のPrjはそうしてた)

後輩にHttpSessionListenerを教えてもらいました。

んー、いまだにちょっとServletとJSFの関係がイマイチちゃんと理解できていない気が。
Servletの上にJSFがいる、ぐらいな。

HttpSessionListener、内容的には以下サイトがわかりやすい。
http://www.techscore.com/tech/Java/JavaEE/Servlet/8/

これ使えばセキュリティ要件でログイン後にセッションID変える必要がある場合(本来は普通にするべきなのかも…だけど)とか、簡単にできそうな気も。

package jp.co.hoge.listener;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class SessionHandler implements HttpSessionListener{
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("Sessionつくるのをフック" + se.getSession().getId());
        se.getSession().setAttribute("hogehoge", "ほげほげ");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("Sessionしんだのをフック" + se.getSession().getId());
    }    
}

また管理ビーンで以下のように値を取ることも。

HttpSession se = (HttpSession)FacesContext.getCurrentInstance().getExternalContext().getSession(false);
System.out.println(se.getAttribute("hogehoge") + se.getId());

ほげほげ824e074d64acd84f86e648717f94

JSFの各PhaseListenerとどういう関係になるのか、とか調べてみたい。

はぁ。まだまだ知らないこと多い…。

いまいさんのJSFを自分なりに試してみた

いつもJenkinsオジサマの画像関連Twitterでお世話になっているIntelliJ IDEAマスターこと@masanobuimaiさんが何とJSF2.2を触られていて、色々もやもやされていました。

「あー自分も最初の頃そういう疑問を多々思っていた」

と思い出しつつ

「あれっ、今もモヤっとしてるじゃん! (゚д゚) ガタッ」

と机に足をぶつけながら読んでました。

人間慣れとは恐ろしいもので、いつの間にか細かいことを気にしなくなってて、初心忘れるべからずだなぁ…と改めて思いつつ、何となくつぶやいたら無言のプレッシャーが(^^;

ということで、GitHubにある今井さんのコード

https://github.com/masanobuimai/JSFSample/tree/01-managed-beans

を拝見しながら、自分なりに考えたもの・仕事で使ってるようなもので書いてみました。

https://github.com/kikutaro/JSFSample-01-managed-beans

2014/6/24 追記
@megascusさんが「あるべき姿」を書かれていました(>_<)



自分が書いた以下コードは、いまいさんのブログにあったようにCDIを使うと~という流れで書いたため、少し微妙な感じです。

私も色々意識せずシンプルに普通に書くとmegascusさんのようになるかなと思います!

画面挙動

僕は今井さんのようにリッチではなく、Java EEが使える有償のIntelliJは持っていないので(´・ω・`) NetBeansを利用して、まずはいまいさんの元コードを動かしてみました。

起動するとメニューが出て

f:id:kikutaro777:20140624021213j:plain

一番上のPlainを選択すると以下画面となります。数値は乱数のようです。

f:id:kikutaro777:20140624021245j:plain

計算ボタンを押下すると、足し算をして結果を下に表示します。

f:id:kikutaro777:20140624021312j:plain

計算時には、左辺が偶数であればその旨をメッセージとして表示し、右辺が奇数であればその旨をメッセージとして表示する仕様のようです。

f:id:kikutaro777:20140624021447j:plain

f:id:kikutaro777:20140624021453j:plain

f:id:kikutaro777:20140624021459j:plain

画像だけみると、メッセージの感じが伝わりにくいですが、1つ前に入ってた値に対するメッセージとなってます。

管理ビーン

f:id:kikutaro777:20140624022104j:plain

用語…確かにいつもフワフワしてます。英語では主に

  • Backing Bean
  • Managed Bean

ですが、日本語だと人によってマチマチで

  • バッキングビーン
  • バックビーン
  • 管理ビーン
  • 管理対象Bean

とか…英語・カタカナも混じったり(^^;さらに面倒なのはJSFとCDIの両者に存在するため

  • JSF管理対象Bean
  • CDI管理対象Bean

などの接頭辞が(^^;

自分のブログも統一できていないのですが、今日は以降、管理ビーン(CDIの)で合わせておきます。

Getter/Setter

f:id:kikutaro777:20140624022611j:plain

管理ビーンでGetter/Setterの嵐になるのが嫌、というのは誰もが皆同じですね(^^;

Lombokは無しで、とのことですが、私は初めてJava EE 6で開発したPrjでLombokを使わずにやっていましたが、IDEで自動生成できるとはいえGetter/Setterが何ともうっとうしくて、それ以降のプロジェクトではLombokを利用しています。

ということで私が書いた版ではLombok使いました。以下はCalcクラスの例です。

package com.jsfsample.calc;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
@AllArgsConstructor
public class Calc {
    private long left;
    private long right;
    private long answer;
}

CDI(Contexts and Dependency Injection)

f:id:kikutaro777:20140624023152j:plain

この辺は色々モヤモヤしますよね(^^;

個人的には今回のサンプルレベルであれば、あまりInjectを利用することもなく、必要に応じてnewしてもいいのかなぁと思います。

でもそれだと上記モヤモヤの回答にならないので、CDIを積極的に使う姿勢でやってみます。

ListのオブジェクトをInjectできる?

Listやプリミティブ型などは直接Injectできないため、いまいさんが書かれているようにProducerを用意する必要があり、定義してみました。

package com.jsfsample.calc;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.enterprise.inject.Produces;

public class CalcProducer {
    
    @Produces
    public List<Calc> getCalcList(){
        return new LinkedList<>();
    }
}

ちなみに元のコードをみるとコンストラクタでnewしてますが、多分自分もそうします(^^;Producerをあえて使うと上記になる感じです。

Injectできるのは管理ビーンだけ?

元文章を読んでいると、InjectできるクラスはCDIで@RequestScopedのようなアノテーションがつけられたCDI管理ビーンだけ…のような認識をされているのかなと(もし違ってたらすみません(>_<;;)

この辺、私も昔そう思っていたのですが、Inject対象はCDI管理ビーンでなくてもでき、普通のPOJOなんかもInjectできます。

ただ、そうするためにはbeans.xmlを利用して以下bean-discover-modeの定義が必要です。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="all">
</beans>

ただし、NetBeansでbeans.xmlを追加した場合、デフォルトはannotatedとなっており、コメントをみると強く推奨しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="annotated">
</beans>

この辺の理由は@n_agetsuさんが詳しく細かくまとめられているので、そちらをどうぞ(^^;

Java EE環境におけるCDIのデフォルト化

というか、CDIやるなら同じく@n_agetsuさんの以下が大変参考になります。

少し脱線しましたが、戻ります。

最初にLombokうんぬんの部分で定義した私のCalcはLombok関連のアノテーションのみで、単なるPOJOですが、上記beans.xmlでall指定していれば

@Inject
private Calc calc;

でInjectされます。

ただし今回の元コードでCalcをnewしてるのはresetメソッドで、newした際に乱数を当て込んでいます。

この処理は自分のCalcクラスだと、コンストラクタに実装してもいい気がするのですが、今回は先ほど定義したProducer用のクラスを使って以下のようにしました。

package com.jsfsample.calc;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.enterprise.inject.Produces;

public class CalcProducer {
    
    @Produces
    public List<Calc> getCalcList(){
        return new LinkedList<>();
    }
    
    @Produces
    public static Calc getCalc(){
        Random rand = new Random(System.currentTimeMillis());
        return new Calc(
                Math.abs(rand.nextInt() % 10) + 1,
                Math.abs(rand.nextInt() % 10) + 1,
                0
        );
    }
}

ここも大げさな書き方してますが、元々のコードのresetメソッドでやっているように普通にnewすると思います(^^;あえてProducer使ったら、という例かなと。

staticメソッドにしている理由は後ほど。

Producerでスコープを指定

f:id:kikutaro777:20140624030430j:plain

上記にあるRequestScopedへViewScopedのInjectは私も自信がないです。

が、一応動きました(^^;いいのかなぁ…。

ちなみにやった方法としては、先ほどProducerでListをInjectできるようにした部分でスコープを指定しただけです。

package com.jsfsample.calc;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.enterprise.inject.Produces;
import javax.faces.view.ViewScoped;

public class CalcProducer {
    
    @Produces @ViewScoped
    public List<Calc> getCalcList(){
        return new LinkedList<>();
    }
}

元ネタは以下サイトの「7. Producer method scopes」です。

Java EE CDI Producer methods tutorial

管理ビーンであるCalcViewクラスはRequestScopedになっています。

@Named
@RequestScoped
@Getter @Setter
public class CalcView extends AbstractBean{
    @Inject
    private Calc calc;
    @Inject
    private List<Calc> results;

    //略

この書き方はあまりやったことないので、実際にはCalcViewをViewScopedにしておくかなぁ。

抽象クラスに画面共通処理を定義

1画面1管理ビーンを基本にしていますが、実際のプロジェクトでは管理ビーンの雛型として抽象クラスを設けています。
さきのコードでネタばれしてますが、今回の定義だと以下のような抽象クラスです。

package com.jsfsample.calc.plain;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public abstract class AbstractBean {
    protected void message(String clientId, String message){
        FacesContext.getCurrentInstance().addMessage(
                clientId, 
                new FacesMessage(message)
        );
    }
}

で、管理ビーンのCalcViewクラスはAbstractBeanを継承しています。

package com.jsfsample.calc.plain;

import com.jsfsample.calc.Calc;
import com.jsfsample.calc.CalcProducer;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import lombok.Getter;
import lombok.Setter;

@Named
@RequestScoped
@Getter @Setter
public class CalcView extends AbstractBean{
    @Inject
    private Calc calc;
    @Inject
    private List<Calc> results;
    
    @PostConstruct
    public void init(){
        //今回は使ってないですが普通使います(^^;
    }
    
    public void calc(){
        long l = calc.getLeft();
        long r = calc.getRight();
        long a = l + r;
        calc.setAnswer(a);
        
        if (l % 2 == 0) message("left", "左辺 " + l + " は偶数です");
        if (r % 2 != 0) message("right", "右辺 " + r + " は奇数です");
        
        results.add(0, calc);
        calc = CalcProducer.getCalc();
    }
}

messageメソッドの部分で使っています。

メッセージ処理は元コードではFaceUtilsクラスを定義して、flashを利用されていますが、今回は上記のようにしました。
あまりよくないのはわかっているのですが、メッセージを動的に生成したり変えたりしたい、というのはよくあって、わりと上記のように管理ビーンから制御してしまうことが今のプロジェクトでは多いです。モデルがビューを気にするんじゃないよ、と言われるのはわかるのですが…。

……

管理ビーン用の抽象クラスのメリットは今回の例だと1画面しかないのでわかりにくいですが、普段のプロジェクトだと、画面IDや画面遷移処理を定義させています。

また、各画面で必ず実装してね、というものは抽象メソッドとしておいて、継承先の管理ビーンで書く形をとっています。

元コードにあるresetメソッドがCalcViewクラスにないですが、Calcインスタンスの生成をProducer使ったため、ここではそれを普通のメソッドとして呼び出してます(いいのか(^^;)
なのでstaticメソッドにしていました。ここは微妙ですね。

Facelet

最後にビューですが元コードでHTMLのtableタグで書かれていた部分をFaceletっぽく変えました。

<h:form prependId="false">
    <h:panelGrid columns="3">
        <h:inputText id="left" value="#{calcView.calc.left}" />
        <h:outputText value="+" />
        <h:inputText id="right" value="#{calcView.calc.right}" />
                
        <h:message for="left" />
        <h:outputText value="&nbsp;" />
        <h:message for="right" />
    </h:panelGrid>

    <!-- 略 -->
</h:form>

あとレンダリングされた際にformのIDがつかないようにprependId属性をfalseにしています。

formのid属性をしていない場合、適当なidがふられて、form内のコンポーネントのidがわかりにくくなります。

prependId属性を指定いない場合(あるいはtrueの場合)
f:id:kikutaro777:20140624040429j:plain

prependId属性をfalseにした場合
f:id:kikutaro777:20140624040435j:plain

j_idt5というのが付くか付かないかですね。

コード

ということで、トータルなコードはGitHubにあげました。
https://github.com/kikutaro/JSFSample-01-managed-beans

動作確認

戻るボタンを忘れてしまいましたが(^^;
動作としては同じになりました。

f:id:kikutaro777:20140624035742j:plain

f:id:kikutaro777:20140624035750j:plain

f:id:kikutaro777:20140624035755j:plain

f:id:kikutaro777:20140624035801j:plain

まとめ

f:id:kikutaro777:20140624034811j:plain

言葉が刺さりますね(^^;JSFに限った話ではなく、Java EE全般な気もします。

Java EEを利用してる方々はだいぶ増えてる感じがするので、うちではこうやってるよ、的な情報がもっと出てくるといいなぁとm(_ _)m

明日(というか今日)朝イチで顧客打合せなんだけど…(^^;ちょっと夜遊びをしすぎました。

PrimeFacesのDialog Framework

PrimeFaces 4.0から入った(厳密には3.5.7ですが、有償のelite planのみ)Dialog Framework、仕事では3.5時代にやっていたダイアログ制御を踏襲していて、使っていませんでした。

で、ちょっと触ってみたのですが、Dialog Frameworkというだけあって、色々なページから呼び出されても共通的に使えるダイアログとして良い感じになってるようです。

確認環境
NetBeans 8
JDK 8
GlassFish 4.0.1 b06
PrimeFaces 5.0

普通にdialog.xhtmlのようなダイアログページ(dialog.xhtml)を定義して

<h:body>
    <h:outputText value="ダイアログ画面" />
    <h:button value="ダイアログボタン" />
</h:body>

呼び出し側のページにボタンを置いて

<p:commandButton value="ダイアログ" actionListener="#{baseBean.viewDialog()}" />

管理対象BeanではRequestContextを使って

@Named
@ViewScoped
public class BaseBean implements Serializable{
    
    public void viewDialog(){
        RequestContext.getCurrentInstance().openDialog("dialog");
    }
}

のように書きます。

追記:書き忘れていたのですが、以下faces-config.xmlが必要でした。

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
              xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
    <application>
        <action-listener>
            org.primefaces.application.DialogActionListener
        </action-listener>
        <navigation-handler>
            org.primefaces.application.DialogNavigationHandler
        </navigation-handler>
        <view-handler>
            org.primefaces.application.DialogViewHandler
        </view-handler>
    </application>
    
</faces-config>

実行してボタンを押すと

f:id:kikutaro777:20140606071447j:plain

f:id:kikutaro777:20140606071452j:plain

と普通にダイアログとして呼び出されます。

ダイアログをドラッグ不可にしたり、サイズ変更不可にしたりするための設定は呼び出し元から

public void viewDialog(){
    Map<String, Object> options = new HashMap<>();
    options.put("draggable", false);
    options.put("resizable", false);
            
    RequestContext.getCurrentInstance().openDialog("dialog", options, null);
}

とMapを渡せばいいようです。

画像だとわかりにくいですが

f:id:kikutaro777:20140606071939j:plain

ドラッグ移動できません。また、ダイアログの右下を先ほどの図と見比べてもらえばわかるのですが、リサイズできないようになっています。

ふむ。

値を渡したり、戻したりもできるらしいので次にやってみたいと思います。

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