Eclipse+JSF+JPAで作るアプリ(19)―JSF Flashを使った画面遷移

今回は、画面遷移する際にパラメータを渡す方法です。

いざStrutsからJSF 2へ! 移行における最大のポイントは?──最新Java EE開発“虎の穴” 第2回 岩崎浩文氏 - page2 - builder by ZDNet Japanにあるように、Flashというオブジェクトを使います。
解説にある通り、Flashとは画面遷移の1度きりだけ有効になるマップです。

今回やりたいことは、

  • 映画の検索画面でチェックした映画をカートに入れて、
  • カートの表示画面に遷移した際にその画面にチェックされた映画を表示する。

というものです。Flashでは、カートに入ったデータを画面間で受け渡しします。
(次回のドラッグ&ドロップを実装するために、カート画面は無理やり作っています。)

Session以上のスコープを使ってBean間でのデータをやり取りする、FlowScopeをJavaまたはxmlで定義する、@ManagedPropertyを使う、など他の方法もありますが、スコープが短くてよい単純な画面遷移であればFlashで十分だと考えます。

それでは実際のコードを見ていきます。

Flashの利用方法

FlashはFacesContextの、ExternalContextから取得します。
Flashの使い方は、通常のマップと同じで、putで値をセットし、getで取得します。
ViewUtil.javaというクラスにstaticメソッドで2つ用意しました。2行が1行になるだけです。

	public static void putToFlash( String key, Object obj )
	{
		Flash flash = FacesContext.getCurrentInstance().getExternalContext().getFlash();
		flash.put( key, obj );
	}
	
	public static Object getFromFlash(String key)
	{
		Flash flash = FacesContext.getCurrentInstance().getExternalContext().getFlash();
		return flash.get( key );
	}

ViewUtil.javaにはその他、FacesMessageを発行する、リソースバンドルから文字列リソースを取得する、PrimefacesのテーマをServletContextのパラメータに設定するなど雑多な処理を実装しています。

作りたい画面

作りたい画面は、次のように

  • 検索フォームが上にあって、
  • 検索結果一覧が下にあり、
  • 検索結果で選択したものをカートに入れる

というよくある画面です。

できたものは、こちら。
f:id:tshix:20150805235104p:plain

searchmovie.xhtmlの抜粋

<h2>検索結果</h2>
<h:form id="movieListForm" onkeypress="if(event.keyCode == 13){return false;}">
	<p:commandLink id="addCartLink"
		value="選択した映画をカートに追加"
		action="#{searchMovieView.addCart}" update=":movieListForm:movieList :movieListForm:addCartLink :movieListForm:viewCartLink"
		disabled="#{searchMovieView.isSelected == false}">
	</p:commandLink>
	&#160;
	<p:commandLink id="viewCartLink"
		value="#{'カートを見る('.concat(searchMovieView.moviesInCart.size()).concat(')')}"
		action="#{searchMovieView.viewCart}"
		>
	</p:commandLink>
	<br/>
	<br/>

	<p:dataTable id="movieList" 

  • moviesInCartというList<Movie>でカートの中の映画を管理しています。
  • addCartというメソッドで、moviesInCartに選択した映画を追加、
  • viewCartというメソッドで、次の画面/viewCart.xhtmlに遷移します。

データを渡す側のManagedBean

ManagedBean、SearchMovieView.javaの主要箇所の抜粋は次の通りです。SearchMovieViewは、ViewScopeです。

SearchMovieView.java

	//Flashに渡すキーワード。他のViewから使うため、public。
	public static final String SEARCH_MOVIE_VIEW_MOVIES_IN_CART = "searchMovieView.moviesInCart";

	@Getter @Setter
	private List<Movie> moviesInCart;
	@Getter @Setter
	private Movie selectedMovie;
	…

	//カートに追加。10件までを制限としている。
	public String addCart()
	{
		if( moviesInCart.size() >= 10)
		{
			ViewUtil.AddErrorMessage("制限", "カートに入れられるのは10件までです。");
			return "success";
		}
		if( ! moviesInCart.contains(selectedMovie) )
		{
			moviesInCart.add(selectedMovie);
			movies.remove(selectedMovie);
			movieModel.setWrappedData(movies);
			selectedMovie = null;
			isSelected = false;
		}
		return "success";
	}

	//viewCart.xhtmlへの遷移。Flashにカートの中の映画を設定する。
	public String viewCart()
	{
		if( moviesInCart.size() == 0)
		{
			ViewUtil.AddErrorMessage("エラー", "カートが空です。");
			return "success";
		}
		ViewUtil.putToFlash(SEARCH_MOVIE_VIEW_MOVIES_IN_CART, moviesInCart);
		return "/viewCart.xhtml";
	}	
  • viewCartというメソッドで次の画面へ遷移しています。
  • 遷移する前に、FlashにmoviesInCartを設定していることに注目してください。

データを受け取る側のManagedBean

データを受け取るMovieCartView.javaです。こちらも、ViewScopeです。

	@Getter	@Setter
	private List<Movie> moviesInCart;//カートの映画
	@Getter	@Setter
	private List<Movie> moviesToBeLent;//借りたい映画

	@PostConstruct
	public void init() {
		moviesInCart = (List<Movie>) ViewUtil.getFromFlash(SearchMovieView.SEARCH_MOVIE_VIEW_MOVIES_IN_CART);
		moviesToBeLent = new ArrayList<Movie>();
	}
  • @PostConstructでアノテートされたメソッド内でFlashから取得するだけです。

その他の方法

スコープは、Viewスコープよりも長いものになると思います。
ELResolverから、セッション以上のスコープのBeanをビーン名で取得する方法。ViewUtil.javaに実装してあります。

	public static SessionInfo getSessionInfo() {
		ELContext elContext = FacesContext.getCurrentInstance().getELContext();
		SessionInfo sessionInfo = (SessionInfo) FacesContext.getCurrentInstance()
				.getApplication().getELResolver()
				.getValue(elContext, null, "sessionInfo");
		return sessionInfo;
	}

@ManagedPropertyで注入するやり方。これが普通かと思います。下の例は、メッセージリソースのバンドルを注入しています。

	@ManagedProperty( "#{msgs}" )
	private ResourceBundle bundle;

FlowScopeを定義するやり方。大変そうなので次の機会に取り上げたいと考えています。