アップロード支援JSライブラリ「Fine Uploader」の必要最低限サンプル

# Fine Uploader は便利

[Fine Uploader](http://fineuploader.com/) は他ライブラリへの依存がなく、高機能で[主要ブラウザはもちろん、IE8+にも対応](http://docs.fineuploader.com/branch/master/browser-support.html)していて助かる。
助かるんだけど、機能がありすぎて少しとっつきにくいのが難でいつも忘れる。

ここでは、[高機能っぷり発揮のデモ](http://fineuploader.com/demos.html)ほどのものは不要で、**必要最低限でいいって時のサンプル**をメモ。

## 「CORE」のみを利用

Fine Uploader には **基本機能(CORE)** と **UI機能** というのがある。
UI機能は独自のテンプレートエンジンとか使って簡単にUI周りが作れるようなやつなんだけど、今回は割愛し、使うのは**CORE**の機能のみ。
（UI機能にはすべてのCORE機能が含まれる）

COREのみを利用する場合、インスタンスは以下のように作成する。

```javascript
var uploader = new qq.FineUploaderBasic({
    // ...コンフィグ...
});
```

`qq` というのは Fine Uploader の本体オブジェクトで、`qq` という名前で決まってる（何の略かは知らんけど）。
jQueryでいう `$` みたいなやつ。

ちなみにUI機能を使う時は `FineUploaderBasic` 部分が `FineUploader` (Basicがない) になる。

## 実現したい要件

今回は以下のような超シンプルなもの。

- `input type="file"` ではなくボタンのクリックで画像ファイルを選択
- 選択するとサムネイルをプレビュー
- 送信ボタンでアップロード開始
- アップロードできる拡張子を限定したい
- 送信完了したらなにかしたい

## コード

### HTMLとCSS

```html:HTMLとCSS部分
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>サンプル</title>
    <style>
    /* プレビュー画像のCSS */
    #preview {
        display: block;
        max-width:100px;
        max-height:100px;
        border:1px dashed #ddd;
    }
    </style>
</head>
<body>
    <button id="button">ファイル選択</button>
    <button id="upload">アップロード</button>

    <!-- プレビュー用画像 -->
    <img id="preview">
</body>
</html>
```

### JavaScript

すべての CORE 用オプションは[公式ドキュメントで](http://docs.fineuploader.com/branch/master/api/options.html)。
オプションに加え、用意されている [drawThumbnailメソッド](http://docs.fineuploader.com/branch/master/api/methods.html#drawThumbnail) と [uploadStoredFilesメソッド](http://docs.fineuploader.com/branch/master/api/methods.html#uploadStoredFiles) も使用している。

```javascript:JavaScript部分
// UI用の FineUploader でなく FineUploaderBasic を使用
var uploader = new qq.FineUploaderBasic({
  // 「送信」ボタンでアップロードするため false に
  autoUpload: false,
  
  // ファイル選択ボタン
  button: document.getElementById('button'),
  
  // 検証
  validation: {
    // アップロードできる拡張子を限定
    allowedExtensions: ['jpeg', 'jpg', 'gif', 'png']
  },
  
  // POST先
  request: {
    endpoint: location.href
  },
  
  // コールバックたち
  callbacks: {
    // エラーがあった時
    onError: function(id, filename, message, xhr) {
      alert(message);
    },
    
    // ファイルが選択された時
    onSubmit: function(id, filename) {
      // qq.FineUploaderBasic インスタンス
      var self = this;
      
      // drawThumbnail メソッドでプレビュー用画像に描画
      // （src 属性に data スキーマがセットされる）
      this.drawThumbnail(id, document.getElementById('preview'));
      
      // アップロードボタンにクリックイベントを付加
      document.getElementById('upload').addEventListener('click', function(e) {
        e.preventDefault();
        // クリックされたらアップロード開始
        self.uploadStoredFiles();
      }, false);
    },
    
    // アップロードが完了した時
    onComplete: function(id, filename, response, xhr) {
      alert('「' + filename + '」のアップロード完了！');
    }
  }
});
```

## 全体

```html:HTML全体
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>サンプル</title>
    <style>
    #preview {
        display: block;
        max-width:100px;
        max-height:100px;
        border:1px dashed #ddd;
    }
    </style>
</head>
<body>

    <button id="button">ファイル選択</button>
    <button id="upload">アップロード</button>
    <img id="preview">

<script src="https://cdnjs.cloudflare.com/ajax/libs/file-uploader/5.10.0/fine-uploader.min.js"></script>
<script>
var uploader = new qq.FineUploaderBasic({
  autoUpload: false,
  button: document.getElementById('button'),
  validation: {
    allowedExtensions: ['jpeg', 'jpg', 'gif', 'png']
  },
  request: {
    endpoint: location.href
  },
  callbacks: {
    onError: function(id, filename, message, xhr) {
      alert(message);
    },
    onSubmit: function(id, filename) {
      var self = this;
      this.drawThumbnail(id, document.getElementById('preview'));
      document.getElementById('upload').addEventListener('click', function(e) {
        e.preventDefault();
        self.uploadStoredFiles();
      }, false);
    },
    onComplete: function(id, filename, response, xhr) {
      alert('「' + filename + '」のアップロード完了！');
    }
  }
});
</script>
</body>
</html>
```

これで要件を満たした。

とりあえず上記コードを[CodePenで動作確認できるようにした](http://codepen.io/kijtra/pen/AXoOKb)。
（送信すると500エラーが出るけど大丈夫）


## サーバー側

サーバーへは [XMLHttpRequest](https://ja.wikipedia.org/wiki/XMLHttpRequest)(Ajax) で送信されるけど、普通に `multipart/form-data` でアップロードされたようなデータが来る。
返却は JSON 形式にし、成功なら `{"success": true}` 、エラーなら `{"success": false}` または `{"error": "メッセージ"}` を返す。
（返す JSON には `success` `error` 以外のデータも含んでOK）

ちなみに Fine uploader の強力な機能に **[chunking](http://docs.fineuploader.com/branch/master/features/chunking.html)** がある。
これはアップロードファイルを分割して送信する機能で、例えばモバイル等でアップロード中に接続が切れても途中から再開（resume）できる。
また、エラー時にリトライできたりもするので便利。
chunking を使う場合の流れは

1. JavaScriptで [chunkingオプション](http://docs.fineuploader.com/branch/master/api/options.html#chunking) を設定
1. ファイル送信（自動で分割。非同期で順不同）
1. サーバーで分割ファイルをひとつずつ一時保存（番号情報が来るので、その番号をファイル名にするといい）
1. 完了したら一時ファイルを順番に結合（完了用のURLを別にしたり可）

みたいになる。
（chunking 時の分割ファイルの mime type は `application/octet-stream` になるので注意）