Play 2.1 + Scala だと、非同期通信に関する強化やアップデートがいろいろあるようだけど‥‥
参考:こことか。
でも、Play 2.0 + Java でも非同期通信したいっ!というのが今回のハナシ。
非同期通信を使って、何をどんなふうに実現したいかというところをまずまとめておきます。
うーんと、こんなふうかな。
1) ビュー上で、ユーザーが画面フォーム上の Select 要素の選択肢を変更
2) jQuery.ajax メソッドを使って、Select 要素の選択肢を非同期で送信
3) コントローラーでは、非同期で受信した内容を条件に、DBから関連データを抽出
4) 抽出したデータを JSON 形式に変換して、ビューへ返す
5) ビュー上で、受け取った JSON 形式のデータをもとに、新たな Select 要素を生成する
非同期通信を使わなくても実現可能な内容ではあるけれど、
いまどき非同期のほうがスマートかな?というのが個人的な印象です。
これらの内容、いろいろ冗長かもですが、せっかくなので細かくまとめておくことにします。
さて、じゃあまず 1) の画面フォームの準備から。
いつものようにフォームヘルパーなどの機能は使わずに書いてます。
|
<form name="form1" method="POST" action="@routes.Test.showResult" id="test_form"> <div class="form_pats"> <span class="form_parts_label">都道府県</span> <select name="param1" id="pref_list"> <option value="東京">東京</option> <option value="京都">京都</option> <option value="福岡">福岡</option> </select> </div> <div class="form_pats"> <span class="form_parts_label">市区町村</span> <select name="param2" id="local_list"> <option value="全て">全て</option> </select> </div> </form> |
よくある感じにまとめてみました。
都道府県を選んだら、選んだ内容に合わせて市区町村の選択肢を切り替える、
ということをやってみたいと。
次は 2) jQuery.ajax メソッドを使って非同期通信を行う部分です。
都道府県が変更されたときに、非同期通信を行うように jQuery で記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
/* 都道府県変更時の動作 */ $("#parent_list").change(function(){ $.ajax({ type: 'post', url: '@routes.Index.changePref', data: {'pref':$('#pref_list option:selected').val()}, cache : false, success: function(data, status, jqXHR) { var childList = $.parseJSON(data); /* 市区町村を更新 */ $("#local_list").children().remove(); var items = new Array(); items.push('<option value="全て">全て</option>'); $.each(childList, function(idx){ items.push('<option value="' + childList[idx].local + '">' + childList[idx].local + '</option>'); }); $("#local_list").append(items.join()); }, error: function(jqXHR, status, error) { window.alert("通信に失敗"); } }); }); |
こんな感じです。
都道府県の change イベントに処理をバインドします。
type は post 、url には routes の定義を記載します。
データには、都道府県の選択肢をバインドします。
select ボックスで選択した内容は、$(‘#pref_list option:selected’).val() という風に取得できます。
ここでのポイントは cache を false としておくことです。
ブラウザやユーザーの環境によっては、戻り値の json データがキャッシュされてしまうのを防ぐためです。
success 時の処理は 5) 受け取った JSON 形式のデータをもとに、新たな Select 要素を生成する処理です。
select ボックスへの option の追加処理は、
以前の記事のようにie6対応の書き方にしています。
ie6を意識しないのであれば、もっとスマートに書けるんですけどね。
続いて、3) コントローラーで受信した内容を条件に、DBから関連データを抽出します。
ビューからの非同期 Request をコントローラーのアクションにバインドするために、
routes には次の定義を行っています。
|
# async action POST /test/changePref controllers.Test.changePref() |
コントローラーの Test クラスの changePref メソッドをコールします。
changePref メソッドはこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
/** * 都道府県選択 */ public static Result changePref() { Form<FormPref> formValue = form(FormPref.class).bindFromRequest(); if (formValue.hasErrors()) { return ok(views.html.test.render("FormPref.hasErros:" + formValue.errors().toString(), "")); } FormPref formPref = formValue.bindFromRequest().get(); if (formPref == null) { return ok(views.html.test.render("data was null", "")); } // 選択した都道府県をもとにしてデータを取得 Finder<Integer, T02_PREF_LOCAL> finder = new Finder<Integer, T02_PREF_LOCAL>(Integer.class, T02_PREF_LOCAL.class); String whereStr = ""; // 履歴・年度 whereStr += "pref='" + formPref.pref + "'"; // 指定した条件で抽出 Query<T02_PREF_LOCAL> query = finder.where(whereStr).orderBy("SORT_ODR ASC"); List<T02_PREF_LOCAL> results = query.findList(); // JSONファイルの書き出し String retStr = ""; try { retStr = net.arnx.jsonic.JSON.encode(results); } catch (Exception e) { e.printStackTrace(); } return ok(retStr); } |
ちょっと冗長かも・・・。
フォームの値をフォーム用のクラスにバインドしています。
このあたりはなくてもいいんじゃない?という意見もあるかもですね。
取得したフォームの値をもとに、DBから値を取得します。
T02_PREF_LOCAL は、DBのテーブル用のクラスです。
フォームの値のための FormPref 、DBのテーブルのための T02_PREF_LOCAL はそれぞれこんな感じ。
|
package forms; import play.data.validation.Constraints.Required; public class FormPref { @Required public String pref; /* 都道府県 */ public String toString() { return "FormPref [" + "pref=" + pref + "]"; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
package models; import javax.persistence.Entity; import javax.persistence.Table; import com.avaje.ebean.validation.NotNull; import play.db.ebean.Model; @Entity @Table(name = "dbo.T02_PREF_LOCAL") public class T02_PREF_LOCAL extends Model { @NotNull public int seqno; @NotNull public String pref; /* 都道府県 */ @NotNull public String local; /* 市区町村 */ @Override public String toString() { return "T02_PREF_LOCAL [" + "seqno=" + seqno + ", pref=" + pref + ", local=" + local + "]"; } } |
DBにはクラスの定義と同じ名称・形のテーブルを準備しておきます。
続いて、4) 抽出したデータを JSON 形式に変換して、ビューへ返します。
|
// JSONファイルの書き出し String retStr = ""; try { retStr = net.arnx.jsonic.JSON.encode(results); } catch (Exception e) { e.printStackTrace(); } return ok(retStr); |
Play! では、Scala に JSON 用のライブラリが用意されています。
これらのライブラリ、Java からも利用はできるようなのですが、
今回はいろいろの事情であえて jsonic を使うことにしました。
(検証用の時間が足りなかったりっだとか‥‥いろいろです)
JSON へのエンコードだけだと、上記のように非常に簡単な記述で済みます。
JSON への変換が済んだ文字列をそのまま戻り値とします。
戻り値を受け取ったあとの 5) の処理は 1) の箇所に記載済みです。
|
var childList = $.parseJSON(data); /* 市区町村を更新 */ $("#local_list").children().remove(); var items = new Array(); items.push('<option value="全て">全て</option>'); $.each(childList, function(idx){ items.push('<option value="' + childList[idx].local + '">' + childList[idx].local + '</option>'); }); $("#local_list").append(items.join()); |
受け取った戻り値 data を $.parseJSON() でパースします。
パースした値は、$.each(value, function(id){…}) で、データごとの値を取得します。
パースしたデータの値は、テーブル用のクラスの変数名をキーにして格納されていますので、
市区町村名は childList[idx].local といった感じで取得できます。
こんな感じで、非同期通信でのやり取りが書けちゃいます。
Java で割と簡単に書けちゃうというところがポイントでしょうか。