前回まで、あるWebページのボタンを押したら別のWebページに移り変わるような「画面遷移」の方法について解説しました。また、画面遷移のときに、パラメータの情報を受け渡す"Managed Bean"についても解説しました。faces-config.xmlとJSPの記述との関係、そしてValue Binding式とManaged Beanとの関係を理解するのが基本です。
今回は、画面が遷移するときに、データベースを検索したりするなどの何らかの処理を行う方法について紹介しましょう。
まず、図8.1[名前と現在の時刻を表示する]のようなサンプルについて考えてみます。
まず、画面1で名前を入力します。画面1のボタンを押すと、画面2に遷移します。画面2では、画面1で入力された名前と、現在の時刻を表示しています。
このサンプルでは、ボタンが押されたときに、「現在の時刻を取得する」という「ビジネスロジック」が実行されています。JSFでは、こうしたビジネスロジックのことを「Action Method」と言います。Action Methodは、Managed Beanに記述します。
では、このサンプルのManaged Beanを見てみましょう。
import java.util.Date; import java.text.DateFormat; public class ParameterBean { private String word = ""; public void setWord(String word) { this.word = word; } public String getWord() { return word; } public String currentTime() { Date d = new Date(); DateFormat df = DateFormat.getDateTimeInstance(); StringBuffer sb = new StringBuffer(); sb.append("こんにちは、"); sb.append(word); sb.append("さん。\n"); sb.append("いまは"); sb.append(df.format(d)); sb.append("です。"); word = new String(sb); return "success"; } }
このManaged Beanでは、currentTimeというメソッドがAction Methodになります。
このcurrentTimeメソッドでは、現在の日時を取得して、入力フィールドのパラメータと組み合わせています。
そして、返値として"success"という文字列を返しているのがポイントです。
では、画面1に対応するJSPを見てみましょう。
<%@ page contentType="text/html; charset=Shift_JIS" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <link href="style.css" type="text/css" rel="stylesheet" /> <title>JSF: ビジネスロジックのテスト</title> </head> <body> <h1>JSF: ビジネスロジックのテスト</h1> <f:view> <h:form id="searchForm"> <h:inputText id="word" value="#{ParameterBean.word}" /> <h:commandButton id="button1" action="#{ParameterBean.currentTime}" value="Go!" /> </h:form> </f:view> </body> </html>
h:commandButton要素のaction属性に注目してください。
<h:commandButton id="button1" action="#{ParameterBean.currentTime}" value="Go!" />
action属性の値は、#{ParameterBean.currentTime}という表記になっています。
これを、前回紹介した画面遷移のサンプルにある、h:commandButton要素のaction属性と比較してみましょう。
(前回の action 属性) <h:commandButton id="button1" action="success" value="Go!" /> (今回の action 属性) <h:commandButton id="button1" action="#{ParameterBean.currentTime}" value="Go!" />
action属性に注目します。前回では、"success"となっているのに対し、今回は、前回で出てきたValue Binding式のようなスタイルになっていますね。
では、#{ParameterBean.currentTime}という表記について解説しましょう。
ここでは、画面1のボタンが押されると、ParameterBeanのcurrentTimeメソッドが実行されるのです。こうしたしくみのことを"Method Binding"と言います。そして、#{……}という書式を"Method Binding式"と言います。
Method Binding式は、次のようなスタイルになります。
#{ Beanの名前 . Action Method名 }
"Beanの名前"は、先のmanaged-bean-name要素の内容部分となります。
"Action Method名"は、ボタンが押されたときに実行されるAction Method名になります。
Action Methodには、次の3つのルールがあります。
currentTimeメソッドが、この3つのルールに適合していることを確認してください。
いま説明したように、Action Methodの返値がoutcomeとなります。このoutcomeが、h:commandButton要素のaction属性の値となるのです。この値と、faces-config.xmlの設定情報により遷移先が決まります。
次のようなh:commandButtonタグの記述が
<h:commandButton id="button1" action="#{ParameterBean.currentTime}" value="Go!" />
currentTimeというAction Methodの実行によって、次のように変化するというイメージです。
<h:commandButton id="button1" action="success" value="Go!" />
Action Methodの処理内容によってoutcomeの値を変え、ページの遷移先を切り替えることもできます。どのようにすればよいかは、みなさんでお考えください。
ひとまず、Action MethodとMethod Bindingについてまとめておきましょう。
では、ここでサンプルプログラムを作ってみましょう。題材は「図書検索プログラム」です。
図8.2[検索語入力画面]で検索語を入力すると、その検索語を含む本のデータのリストを出力します。図書データはデータベースに格納されています。今回のサンプルでは、データベースにHSQLDBを用いています。HSQLDBの使い方に関する解説は、筆者のWebページをご覧ください。
入力フィールドに検索語を入れ、ボタンを押すと、Action Methodが呼ばれます。Action Methodでは、JDBCによって検索語に基づく検索を行います。そして図8.3[検索結果出力画面]に遷移して、検索結果を出力するのです。
<%@ page contentType="text/html; charset=Shift_JIS" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <link href="style.css" type="text/css" rel="stylesheet" /> <title>図書検索</title> </head> <body> <h1>図書検索</h1> <hr /> <f:view> <h:form id="searchForm"> <h:inputText id="searchWord" value="#{BookSearcher.word}" /> <h:commandButton id="submit" action="#{BookSearcher.searchBooks}" value="Go!" /> </h:form> </f:view> </body> </html>
h:input要素でValue Binding式、h:commandButton要素でMethod Binding式が使われています。
次に、Managed Beanです。
import java.util.List; import java.util.ArrayList; import java.sql.*; import javax.sql.*; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; public class BookSearcher { private String word = ""; private String id = ""; private List list = null; private BookData book = null; public void setWord(String word) { this.word = word; } public String getWord() { return word; } public List getBookList() { return list; } public BookData getBookData() { return book; } public String searchBooks() { searchBooks(word); return "success"; } private void searchBooks(String word) { list = new ArrayList(); try { Class.forName("org.hsqldb.jdbcDriver"); String url = "jdbc:hsqldb:hsql://localhost"; Connection con = DriverManager.getConnection(url, "sa", ""); String selectStatement = "select * " + "from books where ndc like ? " + "or tyosya_hyouji like ? " + "or id like ? " + "or title like ? " + "or author like ? " + "or publisher like ? "; PreparedStatement prepStmt = con.prepareStatement(selectStatement); prepStmt.setString(1, appendPercent(word)); prepStmt.setString(2, appendPercent(word)); prepStmt.setString(3, appendPercent(word)); prepStmt.setString(4, appendPercent(word)); prepStmt.setString(5, appendPercent(word)); prepStmt.setString(6, appendPercent(word)); ResultSet rs = prepStmt.executeQuery(); while (rs.next()) { BookData book = new BookData(); book.setNdc(rs.getString("ndc")); book.setTyosya_hyouji(rs.getString("tyosya_hyouji")); book.setId(rs.getString("id")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); list.add(book); } prepStmt.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } private String appendPercent(String from) { StringBuffer to = new StringBuffer(); to.append("%"); to.append(from); to.append("%"); return new String(to); } }
setWord, getWordというメソッドの存在から、wordプロパティがあることがわかります。また、bookList, bookDataプロパティもあります。
そして、searchBooksというAction Methodがあります。Action Methodのルールに則っていることがわかります。このメソッドでは、(別の)searchBooksメソッドで、データベースの検索処理を行っています。
public String searchBooks() { searchBooks(word); return "success"; } private void searchBooks(String word) { // データベースの検索処理 }
次に、ModelとなるBookDataというJavaBeansです。
import java.io.Serializable; public class BookData implements Serializable { private String ndc = ""; private String tyosya_hyouji = ""; private String id = ""; private String title = ""; private String author = ""; private String publisher = ""; public String getNdc() { return ndc; } public void setNdc(String ndc) { this.ndc = ndc; } public String getTyosya_hyouji() { return tyosya_hyouji; } public void setTyosya_hyouji(String tyosya_hyouji) { this.tyosya_hyouji = tyosya_hyouji; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getPublisher() { return publisher; } public void setPublisher(String publisher) { this.publisher = publisher; } }
ndc, tyosya_hyouji, titleなどのプロパティがあることがわかります。
データベースの検索結果には、複数の図書データが含まれます。1冊の図書データは、1つのBookDataに格納されます。そしてjava.util.Listを使って、複数のBookDataをまとめておきます。
次のような処理になります。
List list = new ArrayList(); BookData book = new BookData(); book.setId(rs.getString("id")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); list.add(book);
検索結果を出力するJSPです。
<%@ page contentType="text/html; charset=Shift_JIS" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <title>検索結果</title> </head> <body> <h1>検索結果</h1> <f:view> <h:form id="listForm"> <h:dataTable id="table" border="1" value="#{BookSearcher.bookList}" var="book"> <h:column> <f:facet name="header"> <h:outputText value="タイトル"/> </f:facet> <h:outputText id="bookTitle" value="#{book.title}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="著者"/> </f:facet> <h:outputText id="bookAuthor" value="#{book.author}"/> </h:column> </h:dataTable> </h:form> </f:view> </body> </html>
主に、dataTableというタグを使って検索結果を表示しています。
h:dataTableタグは、コレクション(List,配列など)のデータを表にして表示するUIコンポーネントです。
ほかのUIコンポーネントと同じく、id属性があります。border属性では、表の罫線の太さを指定しています。
value属性で、表示するコレクション型のプロパティを指定します。このとき、Value Binding式を使います。var属性は、コレクション中の1つの要素を表す変数名です。
この例では、value属性はList型であるbookListプロパティを指定しています。このbookListには、複数のBookDataが格納されています。従って、var属性で指定されたbookという変数名は、BookData Beanを指していることになります。
<h:dataTable id="table" border="1" value="#{BookSearcher.bookList}" var="book"> </h:dataTable>
h:dataTableタグでは、複数のh:columnタグが含まれています。このタグは、図8.4[h:column タグ]のように、h:dataTable要素の1列分のデータを表すUIコンポーネントです。
<h:dataTable id="table" border="1" value="#{BookSearcher.bookList}" var="book"> <h:column> <f:facet name="header"> <h:outputText value="タイトル"/> </f:facet> <h:outputText id="bookTitle" value="#{book.title}"/> </h:column> ......
h:column要素には、f:facet要素とh:outputText要素が含まれています。
このうち、f:facet要素は、表の1列のヘッダやフッタを表すUIコンポーネントです。
name属性に"header"を指定するとヘッダになります。"footer"だとフッタです。
<f:facet name="header"> <h:outputText value="タイトル"/> </f:facet>
そして、h:outputTextタグです。この列では、bookという変数(BookData Beanのこと)のtitleプロパティの値を出力しています。
<h:outputText id="bookTitle" value="#{book.title}"/>
では、h:dataTableタグの働きについて整理してみましょう。
h:dataTableには、表示すべきデータをまとめたコレクションが渡されます。
表示される表の1行分が、h:dataTableのvar属性の値に対応しています。var属性の値はコレクション中の1つのJavaBeansに対応して、表の1行分となります。。この例では、Listに含まれている1つのBookDataが、表の1行分となるのです。
BookData Beanのどのプロパティを出力するかは、column要素によって決まります。1つのcolumn要素で、1つのプロパティを出力するのです。
ここでは、MyFacesの独自タグを使って、図8.5[dataTableの分割表示]のように検索結果を分割して表示させてみましょう。
x:dataTableとx:dataScroller要素を使います。
list.jspは次のようになります。
<%@ page contentType="text/html; charset=Shift_JIS" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x" %> <html> <head> <title>検索結果</title> </head> <body> <h1>検索結果</h1> <f:view> <h:form id="listForm"> <h:inputText id="searchWord" value="#{BookSearcher.word}" /> <h:commandButton id="submit" action="#{BookSearcher.searchBooks}" value="再検索" /> <hr /> <x:dataTable id="bookTable" border="1" rows="10" value="#{BookSearcher.bookList}" var="book" > <h:column> <f:facet name="header"> <h:outputText value="タイトル"/> </f:facet> <h:outputText id="bookTitle" value="#{book.title}" /> </h:column> <h:column> <f:facet name="header"> <h:outputText value="著者"/> </f:facet> <h:outputText id="bookAuthor" value="#{book.author}" /> </h:column> </x:dataTable> <x:dataScroller id="scroll1" for="bookTable" fastStep="10" paginator="true" paginatorMaxPages="9"> <f:facet name="first"> <h:graphicImage url="images/arrow-first.gif" /> </f:facet> <f:facet name="last"> <h:graphicImage url="images/arrow-last.gif" /> </f:facet> <f:facet name="previous"> <h:graphicImage url="images/arrow-previous.gif" /> </f:facet> <f:facet name="next"> <h:graphicImage url="images/arrow-next.gif" /> </f:facet> <f:facet name="fastforward"> <h:graphicImage url="images/arrow-ff.gif" /> </f:facet> <f:facet name="fastrewind"> <h:graphicImage url="images/arrow-fr.gif" /> </f:facet> </x:dataScroller> <x:dataScroller id="scroll2" for="bookTable" pageCountVar="pageCount" pageIndexVar="pageIndex"> <h:outputFormat value="Page{0}/{1}"> <f:param value="#{pageIndex}" /> <f:param value="#{pageCount}" /> </h:outputFormat> </x:dataScroller> </h:form> </f:view> </body> </html>
このJSPでは、h:dataTableのかわりに、x:dataTableを用いています。x:dataTableは、h:dataTableとほとんど同じ機能を持っています。h:dataTableの機能に加えて、dataTableの分割表示(x:dataScroller)や、ソートできるヘッダ(x:commandSortHeader)が使えます。
<x:dataTable id="bookTable" row="10"……> ………… ………… </x:dataTable>
x:dataTableのrows属性では、dataTableで一度に表示する行数を指定します。
図8.5[dataTableの分割表示]では、2つのx:dataScroller要素が使われています。
ひとつめは、数字とアイコンが並んでいる行です。ふたつめは、“Page 1/3”となっている行です。
では、それぞれのdataScrollerについて解説しましょう。
<x:dataScroller id="scroll1" for="bookTable" fastStep="10" paginator="true" paginatorMaxPages="9"> <f:facet name="first"> <h:graphicImage url="images/arrow-first.gif" /> </f:facet> <f:facet name="last"> <h:graphicImage url="images/arrow-last.gif" /> </f:facet> <f:facet name="previous"> <h:graphicImage url="images/arrow-previous.gif" /> </f:facet> <f:facet name="next"> <h:graphicImage url="images/arrow-next.gif" /> </f:facet> <f:facet name="fastforward"> <h:graphicImage url="images/arrow-ff.gif" /> </f:facet> <f:facet name="fastrewind"> <h:graphicImage url="images/arrow-fr.gif" /> </f:facet> </x:dataScroller>
ひとつめのdataScrollerでは、アイコンと数字の行を表示しています。
x:dataScroller要素のfor属性の値が、操作対象となるdataTableのidとなります。
x:dataScrollerの子要素には、6つのf:facet要素があります。この6つのf:facetタグが、6つのアイコンとして表示されるのです。
f:facet要素の子要素では、アイコンの画像を表示させています。
では、ふたつめのdataScrollerです。
<x:dataScroller id="scroll2" for="bookTable" pageCountVar="pageCount" pageIndexVar="pageIndex"> <h:outputFormat value="Page{0}/{1}"> <f:param value="#{pageIndex}" /> <f:param value="#{pageCount}" /> </h:outputFormat> </x:dataScroller>
このdataScrollerでは、"Page 1/3"のように、総ページ数と現在のページ数が表示されます。
x:dataScroller要素にあるpageCountVar属性は、総ページ数を表す変数名となります。また、pageIndexVar属性は、現在のページを表す変数名となります。
x:dataScroller要素の子要素に、h:outputFormat要素があります。この要素は、パラメータを用いたメッセージ出力によく用いられます。
value属性は、次のようになっています。
value="Page{0}/{1}"
{0}の部分が、ひとつめのf:param要素のvalue属性の値に置き換えられます。同様に、{1}の部分が、ふたつめのf:param要素のvalue属性の値に置き換えられます。
こうして、最終的には"pageIndexの値/ pageCountの値"を展開した文字列が出力されるのです。