Tomomi Imura

Tomomi Imura

An Open Web advocate and front-end engineer, who loves everything mobile, and writes about HTML5, CSS, JS, UX, tech events, gadgets, etc. She unintentionally got 15min of fame by creating The HTTP Status Cats. Also, the opinions expressed here are solely her own and do not express the views or opinions of my employer.

Twitter LinkedIn Github Flickr

Creative Commons License
My articles are licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

HTML5 File API & XHR2 with Node.js Express

Note: This article is written in Japanese for 東京Node学園祭2012. I may or may not re-post this in English later only when I feel like to.
KTHXBAI

file upload demo screenshot

こんにちは、そして(たぶん)はじめまして。GirlieMac! Blog 初の日本語の記事を「東京Node学園祭」のために書かせてもらう事になりました。私はサンフランシスコ在住なので残念ながら学園祭参加はできないのですが、ブログの参加で遠く離れたこの地で応援されていただきます。

先に説明しておきますが、私はフロントエンド、しかもモバイルの UX ディベロッパーなので Node.js の記事というとちょっと畑違いかもしれませんが、フロントエンド目線で HTML5 File API と XHR2 そして Node を一緒に使ってみた画像ファイルアップロードのチュートリアルを書いてみます。

ここで使われた HTML5 フィーチュアはまだブラウザによってはサポートされていません。したがってコンパティビリティについては Can I use File API? と、Can I use XHR2? でチェックしてみてください。

それでは、HTML5 File API を使って画像のローカルファイルを読み込んでみましょう。

まず、HTML マークアップ側で、フォームを作成します。
<input type="file"> エレメントを使います。ここでは、accept 属性を image/* と指定して画像ファイルのみを扱ってみましょう。

<form id="uploadForm" enctype="multipart/form-data" method="post" action="/upload">
  <input type="file" accept="image/*" name="imagefile" id="imagefile">
  <input type="button" value="Upload" id="upoadButton">
</form>

<img id="preview">

JavaScript 側です。ユーザが画像を選択した際に File オブジェクトのリストを返します。

document.getElementById('imagefile').addEventListener('change', function() {
  fileSelected();
}, false);

FileReader オブジェクトをインスタンスとして読み取ります。
さてここで、readAsDataURL() を呼び、 DataURL を使ってアップロード前に画像プレビューを表示してみましょう。

function fileSelected() {
  var localFile = document.getElementById('imagefile').files[0];
  var imgFmt = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;
  if (!imgFmt.test(localFile.type)) { 
    ...  // 画像ファイルではありません、というエラーメッセージなど
  }

  var preview = document.getElementById('preview');
  // 画像ファイルを data URL として読み取ります
  var reader = new FileReader();
  reader.readAsDataURL(localFile);
  reader.onload = function(e){
  // e.target.result は DataURL を含みます
    preview.src = e.target.result;
  }
}

XMLHttpRequest オブジェクトを作ります。次のイベント(progress, load, error, abort)のイベントリスナーを登録し、XMLHttpRequest Level 2 でサポートされ始めた FormData を使ってデータを send() メソッドで POST します。

document.getElementById('upoadButton').addEventListener('click', function() {
  startUpload();   
}, false);

function startUpload() {
  var formData = new FormData(document.getElementById('uploadForm'));
  var xhr = new XMLHttpRequest(); 

  xhr.upload.addEventListener('progress', uploadProgress, false);
  xhr.addEventListener('load', uploadFinish, false); 
  xhr.addEventListener('error', uploadError, false);
  xhr.addEventListener('abort', uploadAbort, false);
  xhr.open('POST', '/upload');
  xhr.send(formData);   
}

function uploadProgress() { ... }

さて、ここでやっと、Node.js の登場です。 Express を使ってファイルを指定先にアップロードしましょう。
アップロードされたファイルの情報は req.files に含まれています。例えばファイルサイズ (byte) の情報は size プロパティ、ファイル名は name プロパティ、といった感じです。

app.js Server-side:

/* Server configuration */
app.configure(function(){
  app.use(express.methodOverride());
  app.use(express.bodyParser({keepExtensions: true})); 
  app.use(express.static(__dirname + '/public'));
});
var fs = require('fs'); 
app.post('/upload', function(req, res) {
  // 仮のファイル置き場
  var tmp_path = req.files.imagefile.path;
  // 実際のファイル置き場
  var target_path = './public/user/' + req.files.imagefile.name;
  // ファイルを仮の場所から移します
  fs.rename(tmp_path, target_path, function(err) {
    if (err) throw err;
    // 仮ファイルを削除します
    fs.unlink(tmp_path, function() {
    if (err) throw err;
      res.send('File uploaded to: ' + target_path + ' - ' + req.files.imagefile.size + ' bytes');
        });
    });
});

と、こんな感じです。かなりはしょりましたが大まかな流れがわかっていただけたでしょうか。

久々の日本語のブログでちょっと手こずってしまいましたが、楽しんでいただけたら幸いです。コードや説明に間違いを見つけたり感想があった場合は遠慮なくコメントください。コメントはスパム防止のために承認制にしてあるのですが時差もあるのでちょっと遅れるかもしれません。

References



comments powered by