IFRAMEを使用したAJAX風画像アップローダ(サムネイル表示付)
【よく分かる本記事執筆に至る背景】
1、もともとFileApiを利用してAjaxで画像をアップロードする仕組みが実装されていた。
2、暫定的にIE9はsubmitでのアップロードとして処理を切り替える方針でいた。
3、先の工程で、どうしてもIE9もAjax的に画面を切り替えずアップロードする必要が出てきた。(FileApiが使えるのはIE10から)
4、「IE死ね、IE死ね」とぼやきながら情報を集める羽目になる。
5、同時にFileApi(javascript)で行っていたdataUrlの作成もどうにかする必要が出てきた。
【やりたくないけどやるべき事】
1、ファイルをサーバへ送る(画面遷移なし)
2、送信完了後にサムネイルに送信した画像を表示する。
【最終的にやった事まとめ】
1、画面に非表示のIFRAMEを埋め込む。
2、ターゲットを非表示のIFRAMEにして、ファイルアップデート処理をsubmitで行う。
3、サーバ(java)側でファイルをbase64エンコードして、dataUrlとしてクライアントに返却する。
4、レスポンスのContents-typeがapplication/jsonだとIEがjson電文をダウンロードしようとするので、text/htmlに変更する。
5、サーバ側レスポンスのdataUrlからサムネイルを表示する。
こちらのサイトを参考にさせていただきました。
<IFRAME関連>
画面遷移なしでファイルアップロードする方法 - 30からのBlog
Ajax的に画像などのファイルをアップロードする方法 ::ハブろぐ
画面遷移なしでファイルアップロードする方法 と Safariの注意点 (groundwalker.com)
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) && !/\.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="" + json.dataUrl + "" 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<String, Object> responseData = new HashMap<String, Object>(); 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なんて滅びてしまえ!!