IFRAMEを使用したAJAX風画像アップローダ(サムネイル表示付)

【よく分かる本記事執筆に至る背景】

1、もともとFileApiを利用してAjaxで画像をアップロードする仕組みが実装されていた。

2、暫定的にIE9はsubmitでのアップロードとして処理を切り替える方針でいた。

3、先の工程で、どうしてもIE9Ajax的に画面を切り替えずアップロードする必要が出てきた。(FileApiが使えるのはIE10から)

4、「IE死ね、IE死ね」とぼやきながら情報を集める羽目になる。

5、同時にFileApi(javascript)で行っていたdataUrlの作成もどうにかする必要が出てきた。

6、「IE死ね、IE死ね」とぼやきながら(ry

やりたくないけどやるべき事】

1、ファイルをサーバへ送る(画面遷移なし)

2、送信完了後にサムネイルに送信した画像を表示する。

【最終的にやった事まとめ】

1、画面に非表示のIFRAMEを埋め込む。

2、ターゲットを非表示のIFRAMEにして、ファイルアップデート処理をsubmitで行う。

3、サーバ(java)側でファイルをbase64エンコードして、dataUrlとしてクライアントに返却する。

4、レスポンスのContents-typeがapplication/jsonだとIEjson電文をダウンロードしようとするので、text/htmlに変更する。

5、サーバ側レスポンスのdataUrlからサムネイルを表示する。

こちらのサイトを参考にさせていただきました。

<IFRAME関連>

画面遷移なしでファイルアップロードする方法 - 30からのBlog

Ajax的に画像などのファイルをアップロードする方法 ::ハブろぐ

画面遷移なしでファイルアップロードする方法 と Safariの注意点 (groundwalker.com)

IEJSONダウンロードしようとする問題>

internet explorer - Json response download in IE(7~10) - Stack Overflow

<サムネイル表示問題>

インラインで画像をHTMLに埋め込むData URLスキーム at softelメモ

JavaでBase64エンコード/デコード処理を行う 030_産業システム部| システム開発ブログ(システム開発のアイロベックス|東京都新宿区の業務システム開発会社)

「Java」Base64 エンコード デコード - プログラム日記

クライアントサイド編

<form id="uploadForm" action="アップロード用アクションパス" method="post" enctype="multipart/form-data" target="dummyFrame">
  <div id="thumbnail">
  <input type="file" name="imageFile" />
</form>
<iframe name="dummyFrame" style="display:none;"></iframe>

target="dummyFrame"がミソ。 style="display:none;"に関してはsafariで正常に動かないようなので注意が必要の様子。 でも今回はsafariは関係ないのでこのまま進める。

// ファイル選択時に実行するfunction
$("#imageFile").change(function() {

  // 申し訳程度の拡張子チェック
  var uploadFileName = $("#imageFile").prop('files')[0].name;
  if (!/\.jpe?g$/.test(uploadFileName) &amp;&amp; !/\.JPE?G$/.test(uploadFileName)) {
    alert("その拡張子はだめよ。");
    return false;
  }
  
  $('form[name="uploadForm"]').submit();
  return false;
}

// IFRAME制御
$('form[name="uploadForm"]').submit(function(){
  // submitされた時点で、loadイベントをbind
  $('iframe[name="dummyFrame"]').unbind().bind('load', function() {
    var response = $('iframe[name="dummyFrame"]').contents();
    // サムネイル表示用のfunctionを呼び出す。
    fileLoad(response);
  });
});

function fileLoad(response) {
  // レスポンスをjson電文としてパースする。
  var json = $.parseJSON(response[0].body.innerHTML);
  // エラー時はエラーメッセージをアラート表示
  if (json.errorFlg === true) {
    alert(json.errorMessage);
  } else {
    document.getElementById("thumbnail").innerHTML = "<img src="&quot; + json.dataUrl + &quot;" alt="" />";
  }
}

上から順に処理が実行されるイメージ。 uploadFormをsubmitするけれども、targetはdummyFrameのため、メインの画面は変わらない。 そしてdummyFrameは非表示なので、見た目上は何も起こっていないように見える。 けども、ちゃんとsubmitは行われる。

処理結果は非表示のdummyFrameのロードイベントで拾って、親画面のthumbnailにdataUrlからファイルを表示する。

サーバ(java)サイド編

以下の例はSAStruts使ってます。 Base64UtilもSAStrutsのライブラリなので必要に応じて適当に読み替えてください。

@Execute(validator = false)
public String "jspで指定したアクションパスに対応するメソッド"() {

    boolean errorFlg = false;
    String errorMessage = "";

    // ファイルアップロード処理をこの辺に書く
    // ここは今回の話から外れるので割愛。
    // 画面から送られた<input name="imageFile" type="file" />の内容をアップロードしましょう

    // レスポンスデータの構築
    
    // dataUrlの接頭語(今回はjpegなので決め打ち)
    String dataUrl = "data:image/jpeg;base64,";

    // <input name="imageFile" type="file" />の内容のロード
    FormFile file = sampleForm.imageFile;
    int fileLen = (int) file.getFileSize();
    byte[] data = new byte[fileLen];
    InputStream fis = null;
    try {
        fis = file.getInputStream();
        fis.read(data);
        // BASE64エンコードを行い、dataUrlを生成する。
        dataUrl = dataUrl.concat(Base64Util.encode(data));
    } catch (final IOException e) {
        errorFlg = true;
        errorMessage = "エラーだよ:" + e;
    } finally {
        IOUtils.closeQuietly(fis);
    }

    // レスポンスを生成
    Map&lt;String, Object&gt; responseData = new HashMap&lt;String, Object&gt;();
    responseData.put("dataUrl", dataUrl);
    responseData.put("errorFlg", errorFlg);
    responseData.put("errorMessage", errorMessage);

    // データを返却
    ResponseUtil.write(JSON.encode(responseData), "text/html");

    return null;

}

BASE64へのエンコード結果をレスポンスとして返却している点がポイント。 その際にレスポンスを"text/html"にしているのはIEでのダウンロード回避のため。

以上の実装で、きっとajax的な画面ができたはず! くっそぅ。。。こんなことする羽目になったのもすべてIEのせいだ! IEなんて滅びてしまえ!!