いまさらHTML5 （FileAPI編）

## はじめに

HTML5でファイル操作をやってみます。
ついでに、ドラッグ・アンド・ドロップで、ファイルの選択をしてみます。

## ファイル情報取得

初期設定で`dragover`と`drop`イベントを設定します。

`dragover`イベントは、ファイルをドラッグして対象領域の上に来た時に発生するイベントです。
イベントキャンセルを行わないと、そのままブラウザにファイルが読み込まれてしまいます。
（アイコンの種類を`link`としていますが、Chrome以外適用されていない気がします。）

`drop`イベントは、ファイルをドロップした時に発生するイベントです。
イベントキャンセル後、ドロップされたファイルを取得します。
`event.originalEvent.dataTransfer.files`、JQueryでイベント登録している為
オリジナルのイベントデータが欲しい場合`originalEvent`を指定しなければなりません。
（JQueryを利用しない場合は、不要`event.dataTransfer.files`こんな感じです。）

これは、FileListオブジェクトで、簡単に言うとFileオブジェクトの配列となっています。
これらについて、`name``lastModified``type``size`を表示します。

HTML5のFileオブジェクトはBlobオブジェクトを継承しています。
`name`と`lastModified`は、Fileオブジェクトの属性です。
`type`と`size`は、Blobオブジェクトの属性です。

```html:fileapi01.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>File API Sample</title>
    <style type="text/css">
        #filedrop {
            border: 1px solid black;
            padding: 2em;
            width: 300px;
            text-align: center;
        }
        table {
            width: 600px;
        }
        td {
            border: 1px solid black;
            white-space: nowrap;
        }
    </style>
    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript">
        // 初期処理
        function init() {
            // ファイルドロップイベント設定
            $("#filedrop").on("dragover", eventStop).on("drop", filedrop);
        }
        // ファイルがドラッグされた場合
        function eventStop(event) {
            // イベントキャンセル
            event.stopPropagation();
            event.preventDefault();
            // 操作をリンクに変更
            event.originalEvent.dataTransfer.dropEffect = "link";
        }
        // ファイルがドロップされた場合
        function filedrop(event) {
            try {
                // イベントキャンセル
                event.stopPropagation();
                event.preventDefault();
                // ファイル存在チェック
                if (event.originalEvent.dataTransfer.files) {
                    // ファイル取得
                    var files = event.originalEvent.dataTransfer.files;
                    // ファイル情報を空に設定
                    $("[data-name='fileinfo']").empty();
                    // ファイル数分ループ
                    for (var i = 0; i < files.length; i++) {
                        // ファイル取得
                        var file = files[i];
                        // テーブル生成
                        var table = $("<table>");
                        // ファイル名設定
                        table.append($("<tr>")
                            .append($("<td>").text("name"))
                            .append($("<td>").text(file.name)));
                        // ファイル最終更新日時設定
                        table.append($("<tr>")
                            .append($("<td>").text("lastModifiedDate"))
                            .append($("<td>").text(file.lastModifiedDate)));
                        // ファイルサイズ設定（Blobのプロパティ）
                        table.append($("<tr>")
                            .append($("<td>").text("size"))
                            .append($("<td>").text(file.size)));
                        // ファイルタイプ設定（Blobのプロパティ）
                        table.append($("<tr>")
                            .append($("<td>").text("type"))
                            .append($("<td>").text(file.type)));
                        // 区切り追加
                        $("[data-name='fileinfo']").append($("<hr>"));
                        // テーブル追加
                        $("[data-name='fileinfo']").append(table);
                    }
                }
            } catch (e) {
                // エラーの場合
                alert(e.message);
            }
        }
        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <div id="filedrop">ここにファイルをドロップして下さい。</div>
    <div data-name="fileinfo"></div>
</body>
</html>
```

ちなみに、`input`タグからも同じように取得できます。
タグの`files`でFileListオブジェクトが取得できます。
（JQueryを利用しているので`get(0)`で元オブジェクトを取得しています。）

```html
<input type="file" data-name="inputfile" />
<script type="text/javascript">
        function getFile() {
            var file = null;
            var files = $("[data-name='inputfile']").get(0).files;
            if (files.length > 0) {
                file = files[0];
            }
            return file;
        }
</script>
```

## ファイル読込＆表示

~~ファイルを読み込んでみます。（今回は１ファイルのみとしています。）
Fileオブジェクトの`type`属性によって、FileReaderオブジェクトの、`readAsText`、`readAsDataURL`メソッドを使い分けてファイルを読み込みます。~~

と思ったんですが、srcに設定する値は`URL.createObjectURL`の方が、簡単で軽い事が判明しました。

|タイプ|説明|
|:--|:--|
|text/*|テキスト形式、`readAsText`で読み込んで、タグに表示<br>エンコードの種類を指定できるようにしてあります。|
|image/*|画像形式、`URL.createObjectURL`のurlを`img`タグで表示|
|video/*|動画形式、`URL.createObjectURL`のurlを`video`タグで表示|
|audio/*|音楽形式、`URL.createObjectURL`のurlを`audio`タグで表示|

非同期で読み込みをするので、読み込みが完了してない場合はまずいので、
ドロップ後に`reader.abort()`で読み込みをキャンセルしています。
また、`URL.createObjectURL`で作成したurlは、`URL.revokeObjectURL`で削除しないとリソース解放されないそうです。

```html:fileapi02.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>File API Sample</title>
    <style type="text/css">
        #filedrop {
            border: 1px solid black;
            padding: 2em;
            width: 300px;
            text-align: center;
        }
        div {
            width: 600px;
        }
        table {
            width: 600px;
        }
        td {
            border: 1px solid black;
            white-space: nowrap;
        }
    </style>
    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript">
        // FileReaderオブジェクト
        var reader = null;
        // URL
        var url = null;
        // 初期処理
        function init() {
            // ファイルドロップイベント設定
            $("#filedrop").on("dragover", eventStop).on("drop", filedrop);
            // FileReaderオブジェクト生成
            reader = new FileReader();
        }
        // ファイルがドラッグされた場合
        function eventStop(event) {
            // イベントキャンセル
            event.stopPropagation();
            event.preventDefault();
            // 操作をリンクに変更
            event.originalEvent.dataTransfer.dropEffect = "link";
        }
        // ファイルがドロップされた場合
        function filedrop(event) {
            try {
                // イベントキャンセル
                event.stopPropagation();
                event.preventDefault();
                // ファイル存在チェック
                if (event.originalEvent.dataTransfer.files) {
                    // ファイル取得
                    var files = event.originalEvent.dataTransfer.files;
                    if (files.length == 1) {
                        // 読込キャンセル
                        if (reader.readyState == 1) {
                            reader.abort();
                        }
                        // URL存在判定
                        if (url != null) {
                            URL.revokeObjectURL(url);
                            url = null;
                        }
                        // ファイル情報を空に設定
                        $("[data-name='fileinfo']").empty();
                        $("[data-name='content']").empty();
                        // ファイル取得
                        var file = files[0];
                        // テーブル生成
                        var table = $("<table>");
                        // ファイル名設定
                        table.append($("<tr>")
                            .append($("<td>").text("name"))
                            .append($("<td>").text(file.name)));
                        // ファイル・タイプ設定
                        table.append($("<tr>")
                            .append($("<td>").text("type"))
                            .append($("<td>").text(file.type)));
                        // ファイル・タイプ判別
                        if (("" + file.type).indexOf("text/") == 0) {
                            // テキストの場合、エンコード値取得
                            var label = $("[data-name='label']").val();
                            // エンコード設定
                            table.append($("<tr>")
                                .append($("<td>").text("label"))
                                .append($("<td>").text(label)));
                            // 読込完了時メソッド設定
                            reader.onload = loadedText;
                            // ファイル読込
                            reader.readAsText(file, label);
                        } else if (("" + file.type).indexOf("image/") == 0) {
                            // 画像の場合
                            url = URL.createObjectURL(file);
                            // 画像タグ生成
                            var img = $("<img>");
                            // 画像ソース設定
                            img.attr("src", url);
                            // 画像タグ設定
                            $("[data-name='content']").append(img);
                        } else if (("" + file.type).indexOf("video/") == 0) {
                            // 動画の場合
                            url = URL.createObjectURL(file);
                            // 動画タグ生成
                            var video = $("<video>");
                            // 動画ソース設定
                            video.attr("src", url);
                            // コントローラ設定
                            video.attr("controls", "controls");
                            // 動画タグ設定
                            $("[data-name='content']").append(video);
                        } else if (("" + file.type).indexOf("audio/") == 0) {
                            // 音楽の場合
                            url = URL.createObjectURL(file);
                            // 音楽タグ生成
                            var audio = $("<audio>");
                            // 音楽ソース設定
                            audio.attr("src", url);
                            // コントローラ設定
                            audio.attr("controls", "controls");
                            // 音楽タグ設定
                            $("[data-name='content']").append(audio);
                        }
                        // 区切り追加
                        $("[data-name='fileinfo']").append($("<hr>"));
                        // テーブル追加
                        $("[data-name='fileinfo']").append(table);
                    } else {
                        alert("ファイルを１つドロップしてください。");
                    }
                }
            } catch (e) {
                // エラーの場合
                alert(e.message);
            }
        }
        // テキスト読込完了時
        function loadedText(event) {
            // テキスト取得
            var text = event.target.result;
            // テキスト設定
            $("[data-name='content']").text(text);
        }
        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <div id="filedrop">ここにファイルをドロップして下さい。</div>
    <select data-name="label">
        <option value="utf-8" selected>utf-8</option>
        <option value="iso-2022-jp">iso-2022-jp</option>
        <option value="shift_jis">shift_jis</option>
        <option value="utf-16">utf-16</option>
    </select>
    <div data-name="fileinfo"></div>
    <div data-name="content"></div>
</body>
</html>
```

`readAsDataURL`メソッドは、以下の形式で読み込んでいるみたいです。
data: _meta-type_ ;base64, _data_
~~その為大きいファイルの場合、読み込みに時間がかかってしまします。
読み込む前にサイズチェックを入れたほうが良いかもしれません。~~

`URL.createObjectURL`の方が良さそうです。
`readAsDataURL`メソッドは何に使うのか、わからなくなりました。


## ファイルアップロード

単純にFileオブジェクトをFormDataオブジェクトに入れて、送信するだけです。

```js
// FormData生成
var formdata = new FormData();
// ファイル設定
formdata.append("file", file);
// ファイルアップロード
$.ajax("/sample/upload", {
    method: "POST",
    data: formdata,
    contentType: false,
    processData: false,
    complete: complete
});
```

JQueryの`ajax`メソッドを利用して送信しています。
オプションの`data`属性にFormDataオブジェクトを設定しています。
また、`contentType``processData`属性に、falseを設定しています。

```html:fileapi03.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>File API Sample</title>
    <style type="text/css">
        #filedrop {
            border: 1px solid black;
            padding: 2em;
            width: 300px;
            text-align: center;
        }
        div {
            width: 600px;
        }
        table {
            width: 600px;
        }
        td {
            border: 1px solid black;
            white-space: nowrap;
        }
    </style>
    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript">

        // 初期処理
        function init() {
            // ファイルドロップイベント設定
            $("#filedrop").on("dragover", eventStop).on("drop", filedrop);
        }
        // ファイルがドラッグされた場合
        function eventStop(event) {
            // イベントキャンセル
            event.originalEvent.preventDefault();
            // 操作をリンクに変更
            event.originalEvent.dataTransfer.dropEffect = "link";
        }
        // ファイルがドロップされた場合
        function filedrop(event) {
            try {
                // イベントキャンセル
                event.originalEvent.preventDefault();
                // ファイル存在チェック
                if (event.originalEvent.dataTransfer.files) {
                    // ファイル取得
                    var files = event.originalEvent.dataTransfer.files;
                    if (files.length == 1) {
                        // ファイル情報を空に設定
                        $("[data-name='fileinfo']").empty();
                        // ファイル取得
                        var file = files[0];
                        // テーブル生成
                        var table = $("<table>");
                        // ファイル名設定
                        table.append($("<tr>")
                            .append($("<td>").text("name"))
                            .append($("<td>").text(file.name)));
                        // ファイル・サイズ設定
                        table.append($("<tr>")
                            .append($("<td>").text("size"))
                            .append($("<td>").text(file.size)));
                        // 区切り追加
                        $("[data-name='fileinfo']").append($("<hr>"));
                        // テーブル追加
                        $("[data-name='fileinfo']").append(table);
                        // FormData生成
                        var formdata = new FormData();
                        // ファイル設定
                        formdata.append("file", file);
                        // ファイルアップロード
                        $.ajax("/sample/upload", {
                            method: "POST",
                            data: formdata,
                            contentType: false,
                            processData: false,
                            complete: complete
                        });
                    } else {
                        alert("ファイルを１つドロップしてください。");
                    }
                }
            } catch (e) {
                // エラーの場合
                alert(e.message);
            }
        }
        // 送信完了時
        function complete(jqXHR, textStatus) {
            alert("complete:" + textStatus);
        }
        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <div id="filedrop">ここにファイルをドロップして下さい。</div>
    <div data-name="fileinfo"></div>
</body>
</html>
```

サーバ側は、マルチパートの情報を出すだけの簡単なサンプルとします。

```java:UploadServlet.java
package sample;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		for (Part part: request.getParts()) {
			System.out.println("Name:" + part.getName());
			System.out.println("Type:" + part.getContentType());
			System.out.println("Size:" + part.getSize());
		}
	}
}
```

## 感想

ファイル選択して、アップロードボタン押してた頃に比べれば楽なのかもしれないけど
どういったシーンで利用するのか？って事を考えた時に、あんまり良い案が出ませんでした。
使いドコロが難しそうです。