JavaScript
HTML5
jQuery

JavaScript でファイル保存・開くダイアログを出して読み書きするまとめ

<a download=""><input type="file"> を設置して、ユーザーが操作したタイミングで…ではなく、JavaScript の実行中にファイル保存・開くダイアログを出したかったので、メモ。
元々は、単なる <input type="button"> の click イベントのハンドラ内でダイアログを出したかったという自分のワガママから。
タイトルにわざわざ「ファイル保存・開くダイアログを出して」と入れているのは、「ファイルを読み書き」とだけ書いてしまうと、一時ファイルに読み書きする記事と混ざりそうな気がしたからです。

以下、 jQuery を使用して HTML 要素を作成していますが、Document.createElement() で作ったり、もともと display: none; で HTML に記述していても同様のことができます。

1. ファイル保存 (data URI)

$('<a>', {
    href: 'data:text/plain,' + encodeURIComponent('test text'),
    download: 'test.txt'
})[0].click();

必要に応じて text/plain 'test text' 'test.txt' の部分を書き換えてください。
text や json などで、ファイルサイズがあまり大きくないときに使えそうです。

[0].click() については後ほど…。

参考「data URIs | MDN
参考「javascript でテキストをローカルに保存する方法 | TM Life

2. ファイル保存 (createObjectURL)

href の部分を dara URI ではなく window.URL.createObjectURL()Blob から生成した URL からダウンロードさせる方法。

$('<a>', {
    href: window.URL.createObjectURL(new Blob(['test text'], {type : 'text/plain'})),
    download: 'test.txt'
})[0].click();

最近のブラウザならこれだけでも大丈夫だと思いますが、古いバージョンでは Chrome などが window.webkitURL の名前になっているので、気にする場合は以下のようなコードで対処します。

window.URL = window.URL || window.webkitURL;

参考「window.URL.createObjectURL - Web API インターフェイス | MDN
参考「【JavaScript】各ブラウザでダウンロード処理を実装する(Chrome, Firefox, IE, MS Edge, Safari) | Black Everyday Company
参考「例: オブジェクト URL で画像を表示 - Web アプリケーションからファイルを扱う - Web API インターフェイス | MDN

ちなみに、UTF-8 の BOM を付けたいときに new Blob([bom, content], {'type': mime}) のように書けるそうです。

参考「JavaScriptでファイルダウンロード処理を実現する - Qiita

[0].click() について

jQuery オブジェクトの [0] に、HTMLElement オブジェクト (普通の DOM のアレ) が入っているので、その click() メソッドでイベントを発火します。
jQuery の click()trigger('click') だとそのままでは動作しませんでした。ただし、子孫要素に設定すれば動くようです。

参考「jQueryのtriggerでaタグのhrefへ遷移させる方法 - あずまや

ファイル名を指定しなくて良いなら、window.location.hrefwindow.open(url, '_blank') によるファイル保存も考えられそうです。

参考「JavaScriptで動的に作成したテキストファイルをダウンロード - Qiita
参考「【JavaScript】各ブラウザでダウンロード処理を実装する(Chrome, Firefox, IE, MS Edge, Safari) | Black Everyday Company

3. ファイルを開く

var reader = new FileReader();
reader.onload = function(event) {
    console.log(event.target.result);
};

$('<input type="file" accept=".txt, text/plain">').on('change', function(event) {
    reader.readAsText(event.target.files[0]);
})[0].click();

保存の時と同じく、HTML 要素を作ってクリックイベントを発火してファイルダイアログを表示させます。

ここではテキストファイルの中身を JavaScript でまるっと取得するために readAsText() メソッドを用いていますが、画像ファイルを読み込んでプレビュー表示などをする場合は、いろいろ工夫するといいらしいです…。

参考「input[type=file]で投稿画像を即時表示する際のメモ - Qiita

Promise を使うとき。

コメント「Promise使うとこんな感じに書けるように...

<input type="file"> について

ここでは、見える所に DOM を追加していないので画面上には表示されません。

元々、「ファイルを開く」ボタンを自由に設置したくて、イベントハンドラ内で実行できるように上のように書きましたが、「ファイルを開く」ボタンを自由に設置するのはこれ以外に

  • <input type="file">display: none; して HTML に記述しておき、他から click() を呼ぶ
  • <input type="file">display: none; して <label> で囲む (<label> のクリックに反応する)
  • <input type="file"> を他の HTML 要素にかぶせ、opacity: 0; する

などすれば、独自のボタンを設置できそうです (<input type="button"> だと一番上の方法しか使えなさそうですが…) 。
検索すると色々出てきます。