Play Framework 2.0 Session?いえいえ Cookie でしょ

Play Framework 2.0 で、ページを戻った時に、以前の選択した内容を再現したいなーというお話。
となると、値はどこかに保存しておかなきゃいけないわけで。
Session? いえいえ、ステートレスな Play ! だからこそ、Session の概念はポイしなきゃ。
値の保存は Cookie に頼りましょ。

Java であれこれ作ってきて、サーブレット + jsp に慣れ親しんだ身としては、
情報の保持を考えるときに、ついつい Session に頼りがちです。
でも、ステートレスを特徴とする Play Framework には Session は似合いません。
ステートレスってなあに?という人にはこことかが分かりやすいです。
クラウドベース、複数サーバー、非同期通信で随時接続、サーバーからの情報の頻繁なプッシュ‥‥
これらを考慮したうえで軽量なサーバー環境を実現するのはステートレスでないと割が合わないのです。
(参照:こことか

それでも、「単に軽量なフレームワークとして Play! を選んだだけ!」
「だから Session を使いたい!」という方もいるはず。

Play! にも Session と呼ばれるものは存在します。
(参照:Play! Japan Doc. 2.0.4 セッションとフラッシュスコープ
ただし、このセッション機能は以下のような注意点があります。
「Cookie を利用してる」「サーバーにデータは残らない」
「上限は 4KB」「格納できるのは String のみ」
ここで Session と呼ばれているものは、実際には Cookie だということ。

「これじゃダメ! Servlet の HttpSession がどうしても使いたいんだ!」という方には、
プラグインやライブラリを導入することをお勧めしておきます。
(参照:Play2 で HttpSession を使う

せっかくなのでここからは、ビュー側で jQuery を使って cookie を扱う方法をまとめときましょう。
jQuery で cookie を扱う時には jQuery のプラグイン jquery-cookie を使うととっても便利。

github などからダウンロードしたら、ビューのあたまでインポートしておくことを忘れずに。

cookie への値のセットと、セットした値の参照、値のクリアはこんな感じ。

セットした値をクリアしたいときは null をセットします。
こうしておけば、ブラウザを閉じるまでは null 値が cookie に残っちゃいますが、
ブラウザを閉じると、キーのセットごとクリアしてくれます。

また、cookie の対象とするパスや有効期限( expires )を手書きで設定することもできます。
こんな感じ。

こんな風に、第3引数に配列で指定します。
特定の時分秒に expires させたいときなども、Date 関数で日付を作ってしまえば簡単。

ちなみに、第3引数が設定されていない場合は、
path はそのスクリプトが動作したパスが、expires は -1 が、それぞれセットされます。

最後は、ビューで受け取った値を cookie にセットする方法と、
値の存在チェックをして、実際に値を使う時の例をまとめておきましょう。
Play! の機能としての Session は特に使わず、ベタベタな方法で cookie を利用しています。

値をセットするビューの頭で、Scala のメソッドを定義します。
ビューで受け取った値を cookie にそのまま放り込んじゃう感じ。

先ほど Scala で定義したメソッドを javascript 側で使います。
わざわざ Scala のメソッドを使う意味はあまりないかもですけど、
条件分岐なんかがある場合はこの方法が便利なはず。

そして、cookie に保存した値を別のビューの javascript で、
値の存在をチェックしたうえで使います。

こんな感じ。
ここでは、連結された cookie を分割して配列に保存しています。

好みの問題になりますが、cookie に複数の値を保存するときは、
たくさんのキーに散らばった状態で保存するよりも、
ここで上げたように、文字列を連結して保存したほうが、管理が楽な気がします。
もちろん、連結文字を扱わないように気を付けてあげる必要が出てきちゃいますが‥‥。

というわけで、今日は Play! で Session と cookie について考えたというお話でした。

Play Framework 2.0 ‥‥絶望的?複数チェックボックスの値を送信する

最初に。余裕のある人は、Play Framework 2.1 を使うことをおすすめしますよ~‥‥

ということで、Java + Play! 2.0 で、複数チェックボックスの値を送信する方法です。
調べた限りでは、どうやら Java + Play! 2.0 の環境では、
multiple checkbox の binding 処理にバグがあるみたい‥‥。
(参考:こことか。)
もちろん、select ボックスの複数選択も同じくできないようですね。

で、この内容、Play! 2.1 では修正されているみたいなんです。(参考:こことか。)
なので、それでもあえて Java + Play! 2.0 で複数チェックボックスを扱わなきゃいけない!
という人以外には、下記の方法はおすすめしないかもしれません。

さて、では本題です。
バグがある。正攻法じゃダメ。じゃあどうするか‥‥ javascript に頼るしかないですねえ。
値を区切り文字を挟んで連結して、hidden 項目に入れて送信しちゃいましょう。
やや面倒ですが、ほかに思いつく楽な方法がありませんから‥‥しょうがないです。

手順はこんな感じ。
1. フォームの submit イベントを拾う。
2. チェックボックスの値を区切り文字を挟みながら連結する。
3. 連結した値は hidden 項目に格納。
4. フォームを送信。
5. 受け取った内容を区切り文字で分割。

ソースを見ていきましょう。
まず html から

チェックボックスは submit 時に値を付ける必要がありませんので、name を宣言していません。

javascirpt はこんな感じです。

今回はカンマ区切りで連結してみました。
hidden フォームに格納してしまえば、あとは普通のフォームの値と同様に扱えます。

半分以上がおまけの機能=全てチェックするためのボタンのための処理です。
「全てチェック」は、よくチェックボックスで機能を用意してある場合がありますが、
動作の整合性を取るための機能の実装に混乱をきたしがちです。
だったらいっそのことボタンにしちゃったほうが楽でしょ?

次にコントローラー側では、受け取ったフォームの値を区切り文字で分割してあげないといけません。

受け取った値は、ここには書いていない TestFormData クラスで受け取ることとします。
また、DB から取得した内容は、これまたここには書いていない PREF_TBL クラスで受け取ることとします。
java.util.regex.Pattern.split で分割した値を java.util.Arrays.asList で配列に保存しています。
あとは org.apache.commons.lang3.StringUtils.join で
区切り文字を入れながら再度連結なんてこともしています。

routes については書いてませんが、一般的な Form の POST 処理で OK ですので、
悩ましいところはないはずです。

いかがでしたでしょうか。
複数チェックボックスの値を扱えない‥‥というところがコマッタさんでしたが、
javascript を使えば難なくクリアできちゃいますよというお話でした。

Play Framework 2.0 で jQuery の ajax メソッドを使って非同期通信

Play 2.1 + Scala だと、非同期通信に関する強化やアップデートがいろいろあるようだけど‥‥
参考:こことか
でも、Play 2.0 + Java でも非同期通信したいっ!というのが今回のハナシ。

非同期通信を使って、何をどんなふうに実現したいかというところをまずまとめておきます。
うーんと、こんなふうかな。
1) ビュー上で、ユーザーが画面フォーム上の Select 要素の選択肢を変更
2) jQuery.ajax メソッドを使って、Select 要素の選択肢を非同期で送信
3) コントローラーでは、非同期で受信した内容を条件に、DBから関連データを抽出
4) 抽出したデータを JSON 形式に変換して、ビューへ返す
5) ビュー上で、受け取った JSON 形式のデータをもとに、新たな Select 要素を生成する

非同期通信を使わなくても実現可能な内容ではあるけれど、
いまどき非同期のほうがスマートかな?というのが個人的な印象です。

これらの内容、いろいろ冗長かもですが、せっかくなので細かくまとめておくことにします。

さて、じゃあまず 1) の画面フォームの準備から。
いつものようにフォームヘルパーなどの機能は使わずに書いてます。

よくある感じにまとめてみました。
都道府県を選んだら、選んだ内容に合わせて市区町村の選択肢を切り替える、
ということをやってみたいと。

次は 2) jQuery.ajax メソッドを使って非同期通信を行う部分です。
都道府県が変更されたときに、非同期通信を行うように jQuery で記述します。

こんな感じです。
都道府県の 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 には次の定義を行っています。

コントローラーの Test クラスの changePref メソッドをコールします。
changePref メソッドはこんな感じ。

ちょっと冗長かも・・・。
フォームの値をフォーム用のクラスにバインドしています。
このあたりはなくてもいいんじゃない?という意見もあるかもですね。
取得したフォームの値をもとに、DBから値を取得します。
T02_PREF_LOCAL は、DBのテーブル用のクラスです。
フォームの値のための FormPref 、DBのテーブルのための T02_PREF_LOCAL はそれぞれこんな感じ。

DBにはクラスの定義と同じ名称・形のテーブルを準備しておきます。

続いて、4) 抽出したデータを JSON 形式に変換して、ビューへ返します。

Play! では、Scala に JSON 用のライブラリが用意されています。
これらのライブラリ、Java からも利用はできるようなのですが、
今回はいろいろの事情であえて jsonic を使うことにしました。
(検証用の時間が足りなかったりっだとか‥‥いろいろです)
JSON へのエンコードだけだと、上記のように非常に簡単な記述で済みます。
JSON への変換が済んだ文字列をそのまま戻り値とします。

戻り値を受け取ったあとの 5) の処理は 1) の箇所に記載済みです。

受け取った戻り値 data を $.parseJSON() でパースします。
パースした値は、$.each(value, function(id){…}) で、データごとの値を取得します。
パースしたデータの値は、テーブル用のクラスの変数名をキーにして格納されていますので、
市区町村名は childList[idx].local といった感じで取得できます。

こんな感じで、非同期通信でのやり取りが書けちゃいます。
Java で割と簡単に書けちゃうというところがポイントでしょうか。

ie8やie9で動的に生成した項目の表示スタイルが乱れる

CSSにバグはない、javascriptも問題ない、なのになぜ?ieだから?

jQueryを使ってselectボックスの中身を動的に生成するようなケースで、ie8やie9で見てみると、
selectボックスの横幅がぐーんと伸びたのに、selectボックスを含むエレメントの横幅が伸びなくって、
ボックスが横へはみ出してしまうように見えちゃったというお話です。

文字だけだと説明がまどろっこしいですね‥‥。

今回はこのあたりを参考にボックス追加の処理を書いてます~。
引用すると、こんな感じ。

ええ、いまだにie6対応求められてるんですっ!
オキャクサマ都合とはいえ、システム屋さんの悲しい現実ですよねえ。

‥‥それはさておき
この時にieの「ズーム」機能を使ってズームを変更すると、
変更した時だけ正常に描画される!という現象が見られます。
でもその状態でまた動的に表示を変更すると、ズレが再発‥‥。

他のブラウザだと正常に表示されるので、CSSやjavascriptの問題ではないはず。
でもie8とie9だけはselectボックスとほかの要素が重なったり表示が乱れたり、
とにかくちゃんと画面が表示されません。ieのバグなわけ?

実はこの現象、ieの「互換表示設定」が原因です。
未確認ですが、ie10でも発生するかもしれません。
解消するには、「ツール」>「互換表示設定」で互換表示設定パネルを開きましょう。

ie9_gokan

ie9_gokan2
ここの「互換表示ですべてのWebサイトを表示する」をオフにします。

‥‥え、そこが最初からオフになってる場合はどうするんだって?
そうですね‥‥「互換表示ですべてのWebサイトを表示する」は、意図しない限りデフォルトではオフです。

では、今見ているページはローカルネットワークに作成したサイトではありませんか?
つまり「localhost」や「192.168.1….」なんて、IPアドレス直打ちでページを表示していませんか?
ポイントはイントラネット向けの設定がインターネット向けの設定とは別になっているところ。
「互換表示でイントラネットサイトを表示する」もオフにしましょう。

これで解決しました?

互換表示については普段意識しないだけでなく、
ローカルネットワークのページを表示している場合は、互換表示ボタンは表示されないんです。
そんなわけで、意外と気づきにくい落とし穴なのでしたー。