form が持つデータを JavaScript から取得するための API として、 FormData
というクラスが存在する。
これを利用すると、 Ajax でファイルをアップロードすることができるようになる。
ところが、この FormData
は IE8, 9 では利用できない(IE10 以上なら利用できる)。
IE8 でも非同期で form のデータを送信したい場合は、 <iframe>
タグを利用した(若干トリッキーな)方法が存在する。
環境
Web ブラウザ
IE11 (開発者モードで IE8 をエミュレートさせて確認)
AP サーバー
Payara 4.1.154
Java
JDK 1.8.0_60
仕組み的な
-
<form>
タグにtarget
属性を設定する。 -
target
には、同じ<html>
内に存在する<iframe>
のname
を指定する。 -
<iframe>
タグは、display: none;
などを使って見えないようにしておく。 -
<form>
をサブミットすると、リクエストの結果はtarget
で指定した<iframe>
に出力される。 - このため、画面遷移は起こらず Ajax で非同期通信したかのような動作になる。
実装的な
クライアント
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>sample</title>
<style>
.invisible {
display: none;
}
</style>
</head>
<body>
<form id="form" action="sample-servlet" method="POST" target="_frame">
<input type="text" name="text" />
<input type="submit" value="Submit" />
<iframe id="frame" name="_frame" class="invisible"></iframe>
</form>
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="script.js"></script>
</body>
</html>
$(function() {
$('#frame').on('load', function() {
var json = $(this).contents().text();
var response = $.parseJSON(json);
alert('foo=' + response.foo + ', bar=' + response.bar);
});
});
サーバー
package jta_sample;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/sample-servlet")
public class SampleServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try (BufferedReader br = req.getReader();) {
br.lines().forEach(System.out::println);
}
// ★ Content-Type に text/plain を指定
resp.setHeader("Content-Type", "text/plain");
try (PrintWriter pw = resp.getWriter();) {
pw.println("{\"foo\": \"xxx\", \"bar\": true}");
}
}
}
動作確認
開発者モードで IE8
にして実行。
↓サブミット
サーバー側出力
2015-11-08T23:18:16.533+0900|情報: text=abcdefg
説明
レスポンスの受け取りには
$('#frame').on('load', function() {
var json = $(this).contents().text();
var response = $.parseJSON(json);
alert('foo=' + response.foo + ', bar=' + response.bar);
});
-
<iframe>
のload
イベントを監視することで、サーバーからのレスポンスを受け取れるようになる。 - ステータスコードとかは参照できないので、レスポンスに成功かエラーか分かる識別子を渡す必要がありそう。
コンテンツタイプに text/plain を指定する
resp.setHeader("Content-Type", "text/plain");
json を返すんだから application/json
にしたいところだが、そうすると IE はファイルのダウンロードを開始してしまう。
Content-Type を application/json にした場合
気持ち悪いけど、 text/plain
にしてあげる必要があるっぽい。
(JAX-RS を使っていると、 JSON 変換のための MessageBodyWriter
と連携するためにメソッドを @Produces(MediaType.APPLICATION_JSON)
でアノテートしたくなるけど、それするとファイルダウンロードになってしまうので歯がゆい)