struts2とseasar2でCRUD処理(colorbox編)

前々回前回CRUDアクション作成で入力値チェックを含むCRUD操作を実現しました。
今回は少し画面遷移を工夫して、入力画面はモーダルウィンドウを表示させて入力完了後に閉じるという形にしてみます。

jQuery UIにもDialogというウィジェットが用意されているんですが、
http://jqueryui.com/dialog/#modal-form
今回はもう少し機能が多いものを使いたいと思います。
こことかこことかこことか見ればいろいろ見つかりますが、今回はcolorboxというものを使ってみます。
http://www.jacklmoore.com/colorbox/
選んだ理由は、下記のようなものです。

  • 無償
  • iframe対応
  • 開発が止まっていない
  • それなりに使われてそう

今回は次のような構成です

  • colorbox導入
  • 画面修正
  • カスタマイズ(close画面の共通化、close時の親ウィンドウのリロード制御)

colorbox導入

まずはcolorboxのWebページよりDownloadボタンを押してソースをダウンロードしてzipを解凍します。
下記のようなフォルダ構成になっていると思います。(バージョンによって異なると思いますが)
f:id:kamegu3:20130502121014p:plain

使用するのは「jquery.colorbox.js」「jquery.colorbox-min.js」とexampleフォルダ内の「colorbox.css」「images」フォルダです。
どのcssおよび画像ファイルを使うかは、colorboxのWebページのデモ画面からどのデザインがいいかを決めてください。
今回はexample2を選びました。
もちろん、CSSや画像ファイルは別途作成してもいいですし、既存のものを編集してもいいです。

これらのファイルを次のように配置します。
f:id:kamegu3:20130502121747p:plain

jsp-crud.jsp

headタグ内を下記のように変えます。今回はstruts2-jquery-pluginとbootstrapを使っているので、そのファイルも読み込むようになっています。(struts2でjqueryとbootstrapを使う)
jQueryは必須ですので必ず読み込んでください。

<head>
  <meta charset="UTF-8">
  <sj:head locale="ja" loadAtOnce="true"/>
  <link href="<s:url value="/bootstrap/css/bootstrap.min.css"/>" rel="stylesheet" media="screen">
  <link href="<s:url value="/colorbox/colorbox.css"/>" rel="stylesheet">
  <script src="<s:url value="/bootstrap/js/bootstrap.min.js"/>"></script>
  <script src="<s:url value="/colorbox/jquery.colorbox-min.js"/>"></script>
  <script>
    var bootstrapBtn = $.fn.button.noConflict();
    $.fn.bootstrapBtn = bootstrapBtn;
  </script>
</head>

jquery.colorbox.js」と「jquery.colorbox-min.js」は両方は必要ないのですが、開発中にコードを見たいこともあると思うので両方配置しておいて損はないと思います。

i18nフォルダには多言語化(日本語化も含む)のファイルが含まれているので、日本語化したい場合はこれもwebapp以下に配置して読み込むようにしてください。今回は使いません。

画面修正

前回までは一覧画面と入力画面を画面遷移させて切り替えていましたが、今回は入力画面はモーダルウィンドウを表示して登録後にモーダルを閉じて一覧を再表示させるようにします。
今回、iframeが使えるという条件を入れたのは、モーダル内での画面遷移を簡単に実現するためです。
これがない場合、Java側で制御をいれたり、javascript側でバリデーションをしたりしなければいけなくなります。

iframeで入力画面を表示

src/main/webapp/WEB-INF/content/fwtest/jsp-crud.jsp

まずは編集・新規ボタンにCSSクラスを追加(あとのセレクタで指定できればなんでもいいです)

~~略~~
          <a type="button" class="btn btn-small iframe cbox" href="jsp-crud!input?customer.id=${id}">編集</a>
~~略~~
  <a type="button" class="btn btn-small iframe cbox" href="<s:url method="input"/>">新規</a>
</body>
~~略~~

このリンク先をcolorbox化します。

~~略~~
</body>

<script>
$(function() {
  $('.cbox.iframe').colorbox({iframe:true, width:"400px", height:"400px"});
});
</script>
</html>

「$(function() {処理});」で囲っているのは、ページロード時に中の処理を実行するという意味です。
「$(document).ready(function() {処理});」と書くのも同じ意味です。
http://api.jquery.com/ready/
colorbox()メソッドの引数にiframe:trueを渡すのが肝です。また、widthとheightも指定してください。iframeの場合は指定しないと見えなくなるようです。

ここまでで入力画面がモーダルウィンドウで表示されるようになったはずです。
ただし、登録後にiframe内の表示が一覧画面となっています。なので、これを閉じるように変えます。
もちろんJavaではこれを閉じることはできないので、一度画面を違うjspに遷移させてそこで閉じるためのjavascriptメソッドを呼ぶようにします。

登録後にモーダルウィンドウを閉じる

src/main/java/kamegu/action/fwtest/JspCrudAction (変更)
~~略~~
	public String save() {
		if (customer.id != null) {
			jdbcManager.update(customer).execute();
		} else {
			jdbcManager.insert(customer).execute();
		}
		return "close";
	}

	public String delete() {
		jdbcManager.delete(customer).execute();
		return "close";
	}
~~略~~

return "list"; から return "close";に変えます。
こうすることでフレームワーク側でjsp-crud-close.jspにマッピングします。
これにより、クラスにつけたResultsアノテーションは不要になります。

src/main/webapp/WEB-INF/content/fwtest/jsp-crud-close.jsp (新規作成)
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <script>
    parent.$.colorbox.close();
  </script>
</head>
</html>

今回はリクエストを投げてその遷移後に閉じる処理を入れましたが、リクエストを投げず単純に閉じるだけのボタンを作りたい場合は

onclick="parent.$.colorbox.close();";

とか

  $(セレクタ).click(function(){parent.$.colorbox.close();});

みたいにすればOKです。

ここまででモーダルを閉じるところまではできました。
しかし閉じた後に一覧画面が更新されていません。

画面を閉じる際に元の画面を更新する

方法としては大きく2つ考えられます。

  • モーダルウィンドウ側で親画面をリロードする
  • colorboxを呼ぶ際にonClosedプロパティを指定する

ここでは、より一般的な用途にまでケースを広げて考えて見ます。

前者の場合下記のようにします。

src/main/webapp/WEB-INF/content/fwtest/jsp-crud-refresh.jsp (ほぼjsp-crud-close.jspと同じ)
  <script>
    parent.document.location.reload(true);
  </script>

アクションクラスのreturn "close";をreturn "refresh";に変えてください。
親ウィンドウをリロードするとモーダルウィンドウも消滅するのでclose()は呼び出す必要ありません。

後者の場合は下記のようにします。

src/main/webapp/WEB-INF/content/fwtest/jsp-crud.jsp
<script>
$(function() {
  window.RELOAD = function() {location.href = "<s:url includeParams="all" escapeAmp="false"/>";};
  $('.cbox.iframe').colorbox({iframe:true, width:"400px", height:"400px",
    onClosed:window.RELOAD
  });
});
</script>

onClosedでlocation.reload(true)を呼んでもいいんですが、親ウィンドウがPOSTの画面という可能性もあるためあえてリロードではなく同じURLに再アクセスするようにしています。上記のようにstrutsのurlタグを使えばこれが簡単にできます。

後者の場合、このままではcolorbox自体のcloseボタンを押したときにもリロードされてしまうため、
ここでは前者の方法で進めます。

以上で、モーダルウィンドウを使ってのCRUD操作ができました。

カスタマイズ

ここからは、colorbox自体をいろいろなところで共通化して使えるように手を加えていきます。

colorboxのclose画面の共通化

上のjsp-crud-close.jspのようにアクションごとにclose用のjspを書いていくのは非効率なのでこれをアプリケーション全体で共通化します。
まずは、「jsp-crud-close.jsp」と「jsp-crud-reflesh.jsp」を/content/common/に移動して「close.jsp」「refresh.jsp」とリネームします。

続いて、アクションで"close"、"refresh"が返された場合の設定を行います。

struts.xml

packageタグ内に下記を追加します。

    <global-results>
      <result name="close" type="dispatcher">/WEB-INF/content/common/close.jsp</result>
      <result name="refresh" type="dispatcher">/WEB-INF/content/common/refresh.jsp</result>
    </global-results>

これで共通化できました。
JspCrudActionの動作も問題ないはずです。

モーダルclose時の親ウィンドウのリロード制御

このままだと、モーダルウィンドウ側でリロードするかどうかを判断するため少し面倒なケースも出てきます。

  • 共通のモーダル処理を複数の親ウィンドウから呼び出す場合に、ある画面ではリロードさせるが他の画面では一部要素のみをajaxで再読み込みさせたい
  • モーダルウィンドウでデータ更新を行うがそのまま閉じずに引き続きモーダル上で操作ができる場合、colorboxのcloseボタンを押すと親ウィンドウはリロードされない

そこで、少し手を加えたいと思います。

src/main/webapp/WEB-INF/content/common/refresh.jsp (scriptタグを書き換え)
  <script>
    parent.BOX_NEED_REFRESH_PARENT = true;
    parent.$.colorbox.close();
  </script>

親ウィンドウをリロードさせるのではなく、フラグをtrueにしてリロード処理自体は親ウィンドウに任せます。

親ウィンドウの画面(jsp-crud.jsp)のscriptタグを書き換え
<script>
$(function() {
  window.RELOAD = function() {location.href = "<s:url includeParams="all" escapeAmp="false"/>";};
  $('.cbox.iframe').colorbox({iframe:true, width:"400px", height:"400px",
    onOpen:function(){
      delete window.BOX_NEED_REFRESH_PARENT;
    },
    onClosed:function(){
     if (window.BOX_NEED_REFRESH_PARENT) {
       window.RELOAD();
     }
    }
  });
});
</script>

フラグ「window.BOX_NEED_REFRESH_PARENT」を新しく定義し、このフラグを見てリロードするかどうか判断します。
フラグがtrueの場合はリロード処理を行います。

リロード制御の共通化

上の処理を共通化したものをcolorboxのプラグインとして作成しています。
ブログ
Github
demo

これを使うのは下記のようにするだけです。

jsp-crud.jsp
~~略~~
  <script src="<s:url value="/colorbox/jquery.colorbox-min.js"/>"></script>
  <script src="<s:url value="/colorbox/jquery.colorbox.reloadOnClosed.js"/>"></script>
~~略~~
<script>
$(function() {
  $('.cbox.iframe').colorbox({iframe:true, width:"400px", height:"400px"});
});
</script>
~~略~~

jsファイルを読み込んで、colorbox呼び出しを書き換えます。

refresh.jsp
  <script>
    parent.$.colorbox.reloadOnClosed();
    parent.$.colorbox.close();
  </script>

閉じる前に$.colorbox.reloadOnClosed()を呼びます。

以上