WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

Google Wave も活用する Gears ドラッグ&ドロップ API の使い方

Google I/O でセンセーショナルなデビューを果たした Google Wave が話題です。ソーシャルでリアルタイム性の高い諸機能もさることながら、デスクトップ・アプリケーションと同等、もしくはそれ以上に操作性の高いたユーザー・インターフェースに驚かされます。その中でも特に画期的なのが、ファイルのドラッグ&ドロップです。従来、ファイルのアップロードは Web アプリケーションで最も面倒な作業のひとつだったので、これはたいへんな衝撃です。

実はこの機能は Gears の最新版で追加されたドラッグ&ドロップ API で実現されています。ということは、その気になれば他のサイトにも自由に実装できるはずなのですが、ドキュメントがまだ用意されておらず、そのために事実上は利用できないという残念な状況でした。 Google がドキュメントを更新すると言っているものの時期は不明ですし、ましてや日本語ドキュメントなんていつになるかわかりません。そこで、ここは頑張って自力で調べてみることにしました。オープンソース万歳です。

ということで、本日は Gears のドラッグ&ドロップ API の調査結果をご報告しようと思います。この記事を読めば皆さんのサイトでもドラッグ&ドロップによるファイルアップロードを実現できますので、ぜひご覧ください。

※ この記事の内容は私が独自に調査したものですので、間違いがあったり、非公開機能が混じっている可能性があります。そのあたりご理解の上、自己責任でご利用ください。

必要な環境

Gears のドラッグ&ドロップ API を使うには、Gears 0.5.21.0 (現在の最新版)以降がブラウザにインストールされていなければなりません。対応しているブラウザは Firefox, Safari, IE で、これらのブラウザならインストールページから簡単にインストールできます。なお、 Google Chrome は Gears を標準で内蔵していますが、 Gears 0.5.21.0 は(今のところ)開発版でしか利用できないので、あまりお勧めしません。

Gears が既にインストール済みの場合は、上記のインストールページでバージョンが確認できます。通常は自動で最新版に更新されているはずですが、もしバージョンが古い場合は同ページで更新できます。

また、私が試した限り、 Mac OS X ではドラッグ&ドロップ API が動作しませんでした。 Windows もしくは Linux を使ってください。すいません、 Safari なら動きました。 Mac OS X 版の Firefox のみ動かないようです。

使ってみる

環境が用意できたら、さっそくドラッグ&ドロップ API を使ってみましょう。簡単なサンプルを用意したので、まずはそちらで試してみてください。灰色の矩形のなかにデスクトップから適当なファイルをドラッグ&ドロップすると、そのファイルの各種情報(ファイル名、ファイルサイズ、先頭の数バイトの値など)が表示されます。

Gears ドラッグ&ドロップ API のサンプル

このように、Gears のドラッグ&ドロップ API を使うと、ドロップされたファイルの情報を JavaScript で取得して、自由に利用できます。この API は HTML5 のドラッグ&ドロップ API (こちらはDOM 要素のドラッグ処理が対象)がベースとなっており、さらに Gears の Desktop クラスに追加されたいくつかのメソッドを利用することで実現されています。

それでは、ドラッグ&ドロップの実現方法を詳しく見ていきましょう。以下で例示しているコードは上記サンプルのものより簡略化しているので、必要に応じてサンプルのソースも参照すると、より理解が深まると思います。

Gears の初期化と Desktop オブジェクトの作成

まずは Gears 関連の基本インターフェースを定義する gears_init.js を読み込みます。このファイルは Google Code のこちらのページで取得できますので、それを自分のサイトの適当な場所に保存して利用してください。

<script type="text/javascript" src="gears_init.js"></script>

そして、現在のブラウザが Gears をサポートしていることを確認した後、 Desktop クラスのオブジェクトを生成します。基本的には google.gears が定義されていれば Gears が有効であるということなのですが、バージョン違いなどで必要なオブジェクトの生成に失敗することもあるので、初期化コード全体を try-catch で囲んでしまうのがベストです。初期化に失敗したときの処理は任意ですが、 Gears のインストールページに誘導するのが通例です。

var desktop = null;
var action  = ['install', 'インストール'];
try {
  if(!window.google || !window.google.gears)
    throw null;
  action = ['upgrade', '更新'];
  desktop = google.gears.factory.create('beta.desktop');
} catch(e) {
  var message = 'Gears を' + action[1] + 'してください。'
  window.location.href =
    'http://gears.google.com/?action=' + action[0] +
    '&message=' + encodeURIComponent(message) +
    '&return=' + encodeURIComponent(window.location.href);
}

これで初期化は終了ですが、後に利用するためにブラウザの検出をしておきましょう。 Factory.getBuildInfo メソッドで Gears プラグインのビルド情報が取得できるので、それを利用するのが確実なようです。

var platform  = null;
var buildInfo = google.gears.factory.getBuildInfo();
platform = buildInfo.indexOf(';ie')      > -1 ? 'IE'      : platform;
platform = buildInfo.indexOf(';firefox') > -1 ? 'Firefox' : platform;
platform = buildInfo.indexOf(';safari')  > -1 ? 'Safari'  : platform;
platform = buildInfo.indexOf(';npapi')   > -1 ? 'Npapi'   : platform;

イベントハンドラの登録

ファイルのドロップを受け付けるには、ドロップターゲットになる DOM 要素に対して、 HTML5 の Drag and Drop API と同じイベントハンドラを登録します。登録すべきイベントハンドラは以下のとおりです。

イベント名発生条件
dragenter ドラッグ中にマウスカーソルが要素内に入ったとき
dragover ドラッグ中にマウスカーソルが要素内を移動したとき
dragleave ドラッグ中にマウスカーソルが要素内から出たとき
drop 要素内にファイルがドロップされたとき

ただし、 Firefox 3.0 ではイベント名が少し違っており、 dragleave, drop をそれぞれ dragexit, dragdrop に置き換えなければなりません。これは Firefox 3.0 の API のバージョンが古いためで、 Firefox 3.5 では修正されるそうです。

イベントハンドラの登録には、通常の DOM イベントと同じく addEventListener (IE では attachEvent) を使います。実際のコードは以下のようになるでしょう。

function addEvent(element, name, handler) {
  if(platform != 'IE') {
    element.addEventListener(name, handler, false);
  } else {
    element.attachEvent('on' + name, handler);
  }
}
 
window.onload = function() {
  var targetEl = document.getElementById('drop-target');
  addEvent(targetEl, 'dragenter', handleEnter);
  addEvent(targetEl, 'dragover',  handleOver);
  addEvent(targetEl, platform == 'Firefox' ? 'dragexit' : 'dragleave', handleLeave);
  addEvent(targetEl, platform == 'Firefox' ? 'dragdrop' : 'drop',      handleDrop);
};

ファイル情報の取得

各イベントハンドラに渡される event オブジェクトにはドラッグしているファイルの情報が含まれており、 Desktop.getDragData メソッド(クラスメソッドではなく、インスタンスメソッドです。以下、便宜的にインスタンスメソッドもこの書き方をします)で抽出できます。返り値として渡されるオブジェクトには、以下のメンバがあります。

メンバ名 内容
count ファイルの数
totalBytes 全ファイルの総容量
extensions ファイル拡張子の文字列配列
mimeTypes Mime Type の文字列配列
files File の配列(drop イベントのみ)

count, extensions, totalLengthdrop 以外のイベントでも参照できるので、ファイルのドロップを許可するかどうかの判定に利用できます。 filesFile オブジェクトの配列で、ドロップされた各ファイルの情報を格納しています。 File クラスのメンバには以下のものがあります。

メンバ名内容
name ファイル名(パスは含まない)
blob ファイル内容にアクセスするための Blob オブジェクト

例として、ドロップされたファイルのファイル名を表示するコードを以下に示します。

function handleDrop(event) {
  var data   = desktop.getDragData(event, 'application/x-gears-files');
  var files  = data && data.files;
  var fnames = [];
  if(files) {
    for(var i = 0 ; i < files.length ; ++i) {
      results.push(files[i].name);
    }
  }
  alert(fnames.join('\n'));
}

実際にはファイル内容(File.blob)にアクセスしたい場合がほとんどだと思いますが、その方法については後述の「Blob の操作」を参照してください。

ドロップの受け入れ・拒否

以上でほとんどの処理が実装できましたが、この状態でファイルをドロップすると、ブラウザのデフォルトの挙動(ほとんどの場合、ドロップされたファイルを表示する)が起動してしまい、意図した動作が実行されません。ブラウザのデフォルトの挙動を抑制するには、各イベントハンドラで以下の処理を行う必要があります。

  1. dragenter, dragover, dragleave の各イベントで event.returnValuefalse を設定する。
  2. drop イベントで event.stopPropagation メソッドを呼び出す。ただし IE では必要ない。

逆に言えば、これらの処理を行わなければブラウザのデフォルトの挙動が実行されるということです。状況に応じて使い分けてください。例えば、ドラッグされているファイルがひとつのときのみ受け入れるには、以下のようにします。

function checkDragFiles(event) {
  var data   = desktop.getDragData(event, 'application/x-gears-files');
  var accept = data && data.count == 1;
  desktop.setDragCursor(event, accept ? 'copy' : 'none');
}
 
function handleEnter(event) {
  if(checkDragFiles(event))
    event.returnValue = false;
}
 
function handleOver(event) {
  if(checkDragFiles(event))
    event.returnValue = false;
}
 
function handleLeave(event) {
  if(checkDragFiles(event))
    event.returnValue = false;
}
 
function handleDrop(event) {
  if(checkDragFiles(event)) {
    event.stopPropagation && event.stopPropagation();
    // ドロップされたファイルの処理
  }
}

checkDragFiles 関数の中で実行している Desktop.setDragCursor は、マウスカーソルを変更するメソッドです。指定できる値は 'copy' (ファイルコピーと同じカーソル)と 'none' (ドロップ拒否)の二種類のみです。

Blob の操作

ここまでの知識でドラッグ&ドロップは実装できますが、受け入れたファイルの内容を取得・操作する方法がまだわかりませんね。ドロップされたファイルの内容には File.blob でアクセスでき、その実体は Blob クラスのオブジェクトです。

Blob クラスは Gears でバイナリデータを格納するための汎用クラスで、ローカル DB に保存したり HttpRequest.send でサーバーに送信したりできるほか、その内容を取得・加工するための機能もいくつか実装されています。以降では、この Blob オブジェクトの操作方法について見ていきましょう。

基本操作

Blob クラスのメソッドは、サイズの取得(Blob.length)、データの一部を抽出(Blob.slice)、バイナリデータを配列で取得(Blob.getBytes)の 3 つだけです。このうち、 Blob.lengthBlob.slice は文字列とまったく同じなので、説明の必要もないでしょう。 Gears のドキュメントにも掲載されています

Blob.getBytes は、バイナリデータの一部分を JavaScript の整数配列として取得するメソッドです。第一引数が取得する先頭のオフセット、第二引数が取得するバイト数です。例えば、先頭から 100 バイトを取得するには以下のようにします。

var array = blob.getBytes(0, 100);

ただし、一回で取得できるデータは最大 1023 バイトに制限されていますので、ファイル全体を取得するには以下のようにループを回す必要があります。

var bytes  = [];
for(var offset = 0 ; offset < blob.length ; offset += 1000) {
  var buf = blob.getBytes(offset, Math.min(blob.length - offset, 1000));
  bytes = bytes.concat(buf);
}

このような制限が設けられている理由ははっきりしませんが、とてつもなく巨大なファイルがドロップされる可能性もあるので、安易にファイル全体を読み込むのは望ましくないという配慮かもしれません。上記のようにループを回す場合、実用的な範囲で上限を設けておくのが無難です。

メタデータの取得

Desktop.getMetaData メソッドを利用すると、 Blob からメタデータを抽出できます。

var metaData = desktop.getMetaData(blob);

metaData はメタデータを格納したオブジェクトで、以下のメンバがあります。

メンバ名内容
mimeType データの Mime Type(以下参照)
width 画像の横サイズ(JPEG or PNG の場合のみ)
height 画像の縦サイズ(JPEG or PNG の場合のみ)

mimeType は 'image/jpeg', 'image/png', 'application-octet-stream' のいずれかになります。つまり、 JPEG / PNG ファイル以外はすべて octet-stream 扱いになります。ですので、正確なデータタイプを判別する目的では使えません。あくまで width / height が有効か判断するためのもの、と考えるのが妥当でしょう。

BlobBuilder の使い方

Gears 0.5.21.0 で追加された BlobBuilder を使うと、複数の Blob などを連結して新しい Blob を生成できます。 BlobBuilder クラスのメソッドは以下の 2 つのみです。

メソッド名機能
append 指定した文字列、 Blob などを現在の内容に追加する
getAsBlob 現在の内容を Blob オブジェクトとして取得する

append メソッドは Blob 以外にも整数、文字列、配列を追加できます。整数の場合は 1 バイトの追加、文字列なら文字列を UTF-8 に変換して追加、配列なら各要素をそのデータ型に応じて追加、という挙動になります。

例として、ちょっと手抜きですが以下のような感じで multipart/form-data のフォームに送信できます。

function uploadBlob(url, type, blob, onComplete) {
  // multipart/form-data を作成
  var builder  = google.gears.factory.create('beta.blobbuilder');
  var boundary = '----------------------' + ('' + Math.random()).replace(/[^0-9]/g, '') + '\r\n';
  builder.append('Content-Type: multipart/form-data; boundary=' + boundary +
                 '\r\n' +
                 '--' + boundary +
                 'Content-Disposition: form-data; name="file"; filename="mypicture.jpg"\r\n' +
                 'Content-Type: ' + type + '\r\n' +
                 '\r\n');
  builder.append(blob);
  builder.append('--' + boundary);
 
  // HttpRequest で送信
  var request = google.gears.factory.create('beta.httprequest');
  request.open('POST', url);
  request.setRequestHeader('Content-Type', 'multipart/form-data');
  request.onreadystatechange = function() {
    if (request.readyState == 4) {
      onComplete();
    }
  };
  request.send(builder.getAsBlob());
}

実際に動作テストはしていないので、バグってたら申し訳ありません・・・(^^;

Blob 操作の注意点

最後に、 Blob 操作時の注意点をひとつ。Blob を操作する際は、例外のハンドリングに注意する必要があります(サンプルコードでは手抜きでなにもしていませんが ^^;)。 Blob オブジェクトが実際に保持しているのはファイルへの参照のみで、データ本体は実際にアクセスされた際にはじめて読み込まれます。このとき、もし参照先のファイルが削除されていたりすると、 Gears は例外を発生させます。 Blob を操作(データの読み込みも含む)する際は、常に例外の発生に備えるようにコーディングしてください。

以上、本日は Gears のドラッグ&ドロップ API の使い方をご紹介しました。既存の Web アプリケーションでも、この機能を利用することで大幅に使いやすくなるものがたくさんあるのではないでしょうか。よく利用するサイトを GreaseMonkey で無理矢理対応させてしまうのも面白いかもしれませんね。ぜひうまく活用して、より豊かな Web 環境を実現していきましょう!

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles