IE8 で form のデータを非同期で送信して json のレスポンスを受け取る

form が持つデータを JavaScript から取得するための API として、 `FormData` というクラスが存在する。
これを利用すると、 Ajax でファイルをアップロードすることができるようになる。

ところが、この `FormData` は IE8, 9 では利用できない（IE10 以上なら利用できる）。

IE8 でも非同期で form のデータを送信したい場合は、 `<iframe>` タグを利用した<span class="small">（若干トリッキーな）</span>方法が存在する。

# 環境
## Web ブラウザ
IE11 (開発者モードで IE8 をエミュレートさせて確認)

## AP サーバー
Payara 4.1.154

## Java
JDK 1.8.0_60

# 仕組み的な
![ie8_ajax_form.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/09fe6e7a-7805-4dc6-e3da-70522785f40a.jpeg)

- `<form>` タグに `target` 属性を設定する。
- `target` には、同じ `<html>` 内に存在する `<iframe>` の `name` を指定する。
- `<iframe>` タグは、 `display: none;` などを使って見えないようにしておく。
- `<form>` をサブミットすると、リクエストの結果は `target` で指定した `<iframe>` に出力される。
- このため、画面遷移は起こらず Ajax で非同期通信したかのような動作になる。

# 実装的な
## クライアント
```html:index.html
<!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>
```

```js:script.js
$(function() {
    $('#frame').on('load', function() {
        var json = $(this).contents().text();
        var response = $.parseJSON(json);
        alert('foo=' + response.foo + ', bar=' + response.bar);
    });
});
```

## サーバー
```java:SampleServlet.java
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_ajax_form.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/cc22bcd2-8c18-7606-1c13-569029a8b5bd.jpeg)

開発者モードで `IE8` にして実行。

↓サブミット

![ie8_ajax_form.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/885456e7-60e2-f9f7-0b69-c646cb260472.jpeg)

**サーバー側出力**

```text:
2015-11-08T23:18:16.533+0900|情報: text=abcdefg
```

## 説明
### レスポンスの受け取りには <iframe> の load イベントを利用
```js:script.js
    $('#frame').on('load', function() {
        var json = $(this).contents().text();
        var response = $.parseJSON(json);
        alert('foo=' + response.foo + ', bar=' + response.bar);
    });
```

- `<iframe>` の `load` イベントを監視することで、サーバーからのレスポンスを受け取れるようになる。
- ステータスコードとかは参照できないので、レスポンスに成功かエラーか分かる識別子を渡す必要がありそう。

### コンテンツタイプに text/plain を指定する
```java:SampleServlet.java
    resp.setHeader("Content-Type", "text/plain");
```

json を返すんだから `application/json` にしたいところだが、そうすると IE はファイルのダウンロードを開始してしまう。

**Content-Type を application/json にした場合**

![ie8_ajax_form.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/983b1068-884c-561e-2ee6-477129faaff8.jpeg)

気持ち悪いけど、 `text/plain` にしてあげる必要があるっぽい。
（JAX-RS を使っていると、 JSON 変換のための `MessageBodyWriter` と連携するためにメソッドを `@Produces(MediaType.APPLICATION_JSON)` でアノテートしたくなるけど、それするとファイルダウンロードになってしまうので歯がゆい）


# 参考
- [ajax-fileupload enable ie8+10 by iframe chokuryu/main.js| Gist](https://gist.github.com/chokuryu/d47abf89a7bef6e519e4)
- [Ajax的に画像などのファイルをアップロードする方法 ::ハブろぐ](http://havelog.ayumusato.com/develop/javascript/e171-ajax-file-upload.html)