HTML5の機能調査がてら、フォルダのドラッグ&ドロップで、ファイル一覧をテキスト書き出ししてくれるWebアプリを作りました。
FilelistMaker [Chromeでお試しください]
以下、散漫とした内容ですが、制作メモになります。
1.ドロップしたフォルダの内容を読み取る
ドラッグ&ドロップのAPIは、ほとんどの主要なブラウザで実装されています。このAPIを使うと、(ブラウザによっては?)単一のファイルだけではなく、複数のファイルを操作することも可能なようです。
ただ、ドロップされたデータが「ファイル」なのか「ディレクトリ」なのかを取得したり、さらにディレクトリ内のデータを読み取ったりするには、File API (Directories and System) が必要になります。このAPIは現在ではまだChromeでしか実装されていません。
このような理由から、このアプリも残念ながら現在はChromeでしか動作しません。
参考
[HTML5: Drag and Drop a Folder] (http://www.rajeshsegu.com/2012/08/html5-drag-and-drop-a-folder/comment-page-1/)
キーワード
- File API (Directories and System)
- DataTransfer/DataTransferItemList/DataTransferItem
- FileReader/DirectoryReader
- Entry/FileEntry/DirectoryEntry
ざっくりとした流れ
/**
* 'drop'イベントハンドラ
*/
function onDataDrop(event) {
event.preventDefault();
var dataTransfer = event.dataTransfer;
// DataTransferオブジェクトからDataTransferItemListを参照
if (dataTransfer && dataTransfer.items){
var items = dataTransfer.items,
len = items.length;
// ドロップされたファイルが一つかチェック
if (len === 1) {
var item = items[0], entry;
// HTML5標準
if (item.getAsEntry) {
entry = item.getAsEntry();
// Webkit実装
} else if (item.webkitGetAsEntry *1) {
entry = item.webkitGetAsEntry();
}
// Entryをパース
traverseEntry(entry);
} else {
alert("フォルダを1つだけドロップしてください!");
}
}
}
/**
* Entryのパース
*/
function traverseEntry(entry) {
switch (true) {
// ファイルの場合
case (entry.isFile):
// entryのパスをリストに追加。
// 改行コードはwindowsの場合は、"\r\n"にする必要あり
listString += entry.fullPath + "\n";
break;
// ディレクトリの場合
case (entry.isDirectory):
var reader = entry.createReader();
// ディレクトリ内容の読み取り
reader.readEntries(
// 読み取り成功
function(results) {
// 再帰処理
for (var i = 0, len = results.length; i < len; i++) {
traverseEntry(results[i]);
}
},
// 読み取り失敗
function(error) {
alert("読み込みに失敗しました。");
}
);
break;
}
}
*1: このメソッドは、過去に「同期でEntryを取得できるべきでしょ」とか、「非同期の方が便利でしょ」とか、いろいろな議論があったようですが、現在は同期メソッドになっているようです。
2. テキストファイルを作成しダウンロードさせる
ファイルを保存するには、File API (File Writer)のBlobBuilderやFileWriterを使う方法もあるようですが、今回はBlobを使ったシンプルな実装にしました。
キーワード
ざっくりとした流れ
// listStringはファイルのリストを列挙した文字列
var blob = new Blob([listString], { type:'text/plain' });
// downloadButtonは<a>要素
downloadButton.href = URL.createObjectURL(blob);
downloadButton.download = "fileName.txt";
- フォルダ内のすべてのファイルのパスを列挙した文字列で、Blobオブジェクトを生成。
-
<a>
要素(ダウンロードボタン)のhref
属性をURL.createObjectURLメソッドで設定します。これがダウンロードするファイルのBlob URLになります。 -
<a>
要素のdownload
属性を設定します。これがダウンロードするファイルの名前になります。
3. オフラインでも利用できるようにする
クライアントサイドJSだけで動いているアプリだということもあり、せっかくなのでオフラインでも使えるようにしました。これにはApplicationCacheを利用します。とても便利な機能ですが、何かを更新する度に、マニフェストファイルの更新(バージョン番号を更新する等でOK)を忘れると、更新した内容が反映されない点を注意する必要があります。
参考
ざっくりとした流れ
- マニフェストファイルを用意します。今回はこんな感じで、アプリに必要なすべてのアセットをキャッシュするように設定しました。
- htmlのmanifest属性を指定します。
<html manifest="cache.appcache">
- マニフェストファイルはMIME typeとして
text/cache-manifest
を指定する必要があります。
今回はこちらのやり方を参考に、Expressフレームワークを使ったNodeアプリとしてheroku上にデプロイしているため、以下のような1行を追加しました。
express.static.mime.define({ 'text/cache-manifest': ['appcache'] });
- クライアント側はほぼ参考サイトのままです。
/**
* マニフェストファイルが更新されているかをチェック
*/
function checkUpdate() {
var appCache = window.applicationCache;
if (appCache) {
appCache.addEventListener('updateready',
function(event) {
if (appCache.status === appCache.UPDATEREADY) {
// 古いキャッシュを削除
appCache.swapCache();
// 更新を反映するために、ユーザにページをリロードしてもらいます
if (window.confirm('新しいバージョンのアプリに更新しますか?')) {
window.location.reload();
}
}
}
);
}
}
課題:フォルダ内容の読み取りをバックグラウンドで実行する
ディレクトリをパースするタスクを、ウェブワーカーで行おうとしてみたのですが、まだ実現できていません。これはワーカーに対しEntryオブジェクトをpostMessageで渡そうとすると、DataCloneErrorが発生してしまうためで、目下原因を調査中です!(ちなみにFileオブジェクトはpostMessageでウェブワーカーに渡せます。)