Eclipse+JSF+JPAで作るアプリ(11)―ManagedBeanと画面
前回は、xhtmlで書かれたテンプレートとそのパーツ、テンプレートクライアントについて記述しました。
今回は、マネージドビーンについて記述します。
- JSFでの画面要素は、Managed Beanと呼ばれるPOJOにマップされます。
- また、画面上のアクション(コマンドボタンの実行やリンク)も、Managed BeanのStringを返すメソッドにマップされます。
- このメソッドの戻り値である文字列を、画面遷移先として定義された文字列として返すことによりたページに遷移します。
Managed Beanは以前、バッキングビーン(Backing Bean)とも呼ばれています。
Managed Beanのクラス名ですが、Xxx, XxxManage, XxxBean, XxxManagedBean, XxxPage, XxxViewといくつも流派があるようです。
本ブログでは、PrimefacesのShowcaseに従ってXxxViewで統一します。
パッケージ構成
sample.yourlibraryに、viewパッケージを追加します。
ログイン画面用に、LoginView、ユーザ編集画面用に、EditUserView、ログインユーザをセッションスコープで管理するために、SessionInfoを作成します。
ViewUtilは、メッセージ(FacesMessage)と、上記SessionInfoを取得するためのAPIを提供しています。
LoginViewと、login.xhtml
ログイン画面は、アカウント、パスワードの2つのフィールドと、「ログイン」ボタンのみです。
これらは、LoginViewクラスの2つのフィールドaccount, passwordと、1つのメソッドloginにマッピングされます。
LoginView.java
package sample.yourlibrary.view; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import lombok.Getter; import lombok.Setter; import sample.yourlibrary.entity.User; import sample.yourlibrary.logic.UserManager; @ManagedBean(name="loginView") @ViewScoped public class LoginView { @Getter @Setter private String account; @Getter @Setter private String password; public String login() { User loginUser = UserManager.login(account, password); if( loginUser == null ) { ViewUtil.AddErrorMessage("ログイン失敗", "アカウントまたはパスワードが一致しません。"); return "failure"; } SessionInfo sessionInfo = ViewUtil.getSessionInfo(); sessionInfo.setLoginUser(loginUser); return "success"; } }
(コードを短くするため、Lombok.jarを導入しています。@Getter, @Setterでアクセッサメソッドが自動で生成されます。
@DataだとtoStringの循環参照などやり過ぎになるため、現在のところ、Getter、Setterのみ使用しています。
Lombokについては、
LombokとLombok-pg: Javaコードを減量する魔法のスパイス - I am programmer and proud
【Java】Lombokで冗長コードを削減しよう | キャスレーコンサルティング 技術ブログ
などを参照ください)
前回は、標準のhttp://java.sun.com/jsf/htmlのタグ(h:commandButtonなど)を利用していましたが今回からはPrimefacesのタグに一新します。
ほとんどのJSFタグはオーバーライドされていますが、所々違うため注意が必要です。
- <h:outputText> → <p:outputLabel> 通常テキスト(ラベル)
- <h:inputText> → <p:inputText> エディットボックス
- <h:inputSecret> → <p:password> パスワード
- <h:commandButton> → <p:commandButton> エディットボックス
- <h:messages> → <p:messages> エディットボックス
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" > <h:form id="loginForm"> <p:growl showDetail="true" autoUpdate="true"/> <p:graphicImage url="./resource/image/top.jpg"/> <p:panelGrid columns="2"> <p:outputLabel value="アカウント:" /> <p:inputText value="#{loginView.account}" /> <p:outputLabel value="パスワード:" /> <p:password value="#{loginView.password}" /> </p:panelGrid> <p:commandButton value="ログイン" action="#{loginView.login}" /> <p:messages showDetail="true" style="color:red" /> </h:form> </html>
"action"アトリビュートについて
Stringを返すManagedBeanのメソッドと対応させます。
戻す文字列は、LoginView.loginを参照すると分かるようにfailureとsuccessを返しています。
これは、faces-config.xmlのnavigation-ruleタグで以下のように定義しています。
<navigation-rule> <from-view-id>/index.xhtml</from-view-id> <navigation-case> <from-outcome>failure</from-outcome> <to-view-id>/index.xhtml?faces-redirect=true</to-view-id> </navigation-case> <navigation-case> <from-outcome>success</from-outcome> <to-view-id>/editUser.xhtml?faces-redirect=true</to-view-id> </navigation-case> </navigation-rule>
この戻り値の文字列は、直接ページ名を指定することもできます。
growlについて
<p:growl showDetail="true" autoUpdate="true"/>
とありますが、これは、FacesMessageをポップアップで表示するものです。
次の画像は右上のように表示されます。
自動でフェードアウトしますが、sticky="true"にすると、×ボタンを押すまで残ります。
EditUserViewと、userlist.xhtml
ユーザ編集画面は、ユーザ登録のためのフィールドと追加ボタンのフォーム、ユーザー一覧のフォームです。
この2つのフォームは、Strutsと異なり、1つのView、EditUserViewにマップされます。
- @ManagedBean(name="editUserView")は、ApplicationServerのコンテナにこの名前で管理対象ビーンとして生成させる指定をしています。
- @ViewScope 同一ページ内での遷移でフォームの情報をキープするスコープを指定しています。
- @PostConstructアノテーションのメソッドは、ビューが初期化された際に呼び出されるメソッドです。@PreDestoryというアノテーションもあります。そちらは破棄する直前に呼ばれるメソッドです。void型である必要があります。
package sample.yourlibrary.view; import java.util.List; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import lombok.Getter; import lombok.Setter; import sample.yourlibrary.entity.User; import sample.yourlibrary.logic.UserManager; @ManagedBean(name="editUserView") @ViewScoped public class EditUserView { @Getter @Setter private String account; @Getter @Setter private String name; @Getter @Setter private String password; @Getter @Setter private String email; @Getter @Setter private boolean isAdmin; @Getter @Setter private List<User> users; @PostConstruct public void init() { users = UserManager.findAll(); } public String addUser() { if( account == null || name == null ) return null; User user = UserManager.createUser(account, name); user.setPassword(password); user.setEmail(email); user.setAdmin(isAdmin); user = UserManager.updateUser(user); users = UserManager.findAll(); return "success"; } }
userlist.xhtmlです。
登録フォームは、login.xhtmlとほとんど同じですが、一覧フォームはPrimefacesのp:datatableタグを大活用しています。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" > <h2>ユーザー編集</h2> <h:form id="addUserForm"> <p:growl showDetail="true" autoUpdate="true"/> <p:panelGrid columns="2" > <p:outputLabel value="アカウント:" /> <p:inputText id="account" value="#{editUserView.account}" required="true" requiredMessage="アカウント名は必須です。"/> <p:outputLabel value="名前:" /> <p:inputText id="name" value="#{editUserView.name}" required="true" requiredMessage="名前は必須です。"/> <p:outputLabel value="パスワード:" /> <p:password value="#{editUserView.password}" /> <p:outputLabel value="E-mail:" /> <p:inputText id="email" value="#{editUserView.email}" /> </p:panelGrid> <p:messages /> <p:commandButton value="ユーザの追加" action="#{editUserView.addUser}" update=":userListForm" /> </h:form> <br /> <br /> <h2>ユーザー一覧</h2> <h:form id="userListForm"> <p:dataTable id="userList" var="user" value="#{editUserView.users}" paginator="true" paginatorPosition="top" rows="10" rowsPerPageTemplate="5,10,15,30,50" sortMode="multiple" > <p:column sortBy="#{user.account}" filterBy="#{user.account}" filterMatchMode="contains"> <f:facet name="header"> <p:outputLabel value="アカウント" /> </f:facet> <p:outputLabel value="#{user.account}" /> </p:column> <p:column sortBy="#{user.name}" filterBy="#{user.name}" filterMatchMode="contains"> <f:facet name="header"> <p:outputLabel value="名前" /> </f:facet> <p:outputLabel value="#{user.name}" /> </p:column> <p:column sortBy="#{user.email}" filterBy="#{user.email}" filterMatchMode="contains"> <f:facet name="header"> <p:outputLabel value="e-mail" /> </f:facet> <p:outputLabel value="#{user.email}" /> </p:column> </p:dataTable> </h:form> </html>
p:datatableタグでできること
画像を見ていただいてから、p:datatableを説明します。前回のh:datatableよりはるかにリッチになっていることがわかるはずです。
<p:dataTable id="userList" var="user" value="#{editUserView.users}" paginator="true" paginatorPosition="top" rows="10" rowsPerPageTemplate="5,10,15,30,50" sortMode="multiple" > <p:column sortBy="#{user.account}" filterBy="#{user.account}" filterMatchMode="contains">
これらの機能は、xhtml上でタグのアトリビュートを変更するだけで設定可能になっています。
- ページナビゲータがついています。paginator=trueです。paginatorPosition=topで上側だけにナビゲータを付けています。rows=10で、一度に表示する行を10にしています。rowsPerPageTemplateでコンボボックスに表示する行数を5,10,15,30,50としています。
- ソートができます。p:columnタグのsortByでソートに使う属性を指定しています。p:datatableタグのsortMode="multiple"で複数カラムのソートを行っています。
- フィルタができます。p:columnタグのfilterByでフィルタに使う属性を、filterMatchModeでフィルタの条件を指定しています。
p:datatableはできることが沢山あるため、別途掘り下げていく予定です。
Primefaces showcaseにも、以下のように大量の例があります。
SessionInfo
ログインユーザの情報を保持するためのセッションスコープのビーンです。
ログアウトを行っています。ログアウト方法ですが、セッションを破棄し、リクエストからlogoutを呼び出しています。
たかがレルムされどレルム GlassFish で始める詳細 JDBC レルム | 寺田 佳央 - Yoshio Teradaの中ほどのスライド85pを参考にしています。
package sample.yourlibrary.view; import java.io.Serializable; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import lombok.Getter; import lombok.Setter; import sample.yourlibrary.entity.User; @ManagedBean(name="sessionInfo") @SessionScoped public class SessionInfo implements Serializable { private static final long serialVersionUID = 9186759612086888662L; @Getter @Setter private User loginUser; public String logout() { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); externalContext.invalidateSession(); HttpServletRequest request = (HttpServletRequest)externalContext.getRequest(); try { request.logout(); } catch (ServletException e) { e.printStackTrace(); } return "/index.xhtml?faces-redirect=true"; } }
ViewUtil
FacesMessageと上記SessionInfoを取得するためのAPI、ログアウトのAPIです。
ページ間をまたがる情報はgetSessionInfoで渡す予定です。(カートに追加されている映画などの情報。)
この作法がJSFの設計にマッチしているか不明です。
Flash+@PostConstruct,@PreDestoryを使った方法でも可能と思われます。(いざStrutsからJSF 2へ! 移行における最大のポイントは?──最新Java EE開発“虎の穴” 第2回 岩崎浩文氏 - page2 - builder by ZDNet Japanで紹介されています)
@ManagedPropertyを各Viewに設定するよりは、ユーティリティで取得するほうが便利だからそうしています。
package sample.yourlibrary.view; import javax.el.ELContext; import javax.faces.application.FacesMessage; import javax.faces.application.FacesMessage.Severity; import javax.faces.context.FacesContext; public class ViewUtil { public static void AddMessage(String summary, String detail) { AddMessageInner(FacesMessage.SEVERITY_INFO, summary, detail); } public static void AddWarningMessage(String summary, String detail) { AddMessageInner(FacesMessage.SEVERITY_WARN, summary, detail); } public static void AddErrorMessage(String summary, String detail) { AddMessageInner(FacesMessage.SEVERITY_ERROR, summary, detail); } public static void AddFatalMessage(String summary, String detail) { AddMessageInner(FacesMessage.SEVERITY_FATAL, summary, detail); } private static void AddMessageInner(Severity severity, String summary, String detail) { FacesMessage message = new FacesMessage(severity, summary, detail); FacesContext.getCurrentInstance().addMessage(null, message); } public static SessionInfo getSessionInfo() { ELContext elContext = FacesContext.getCurrentInstance().getELContext(); SessionInfo sessionInfo = (SessionInfo) FacesContext.getCurrentInstance() .getApplication().getELResolver() .getValue(elContext, null, "sessionInfo"); return sessionInfo; } }
以上が、画面の詳細です。
次回は、Primefacesを使ったテーマの変更から入ります。
このブログは機能より、見た目や実装したいギミックを重視しています。