JavaScriptの値をローカルファイルに保存する方法について調べた

はじめに

Webブラウザーからローカルファイルを読み込みたいときはFile APIを使えば良さそうだけど、逆にデータをローカルファイルに保存したいときはどうしたら良いんだろう?ってことで、ちょっと調べてみました。

方法1

<a>タグのhrefに、保存したいデータを流し込む方法がありました。以下のHTMLファイルをブラウザー上で開き、何かメッセージを入力して保存ボタンをクリックして下さい。すると、ファイル保存を促すリンクが現れるので、右クリックでリンク先を保存して下さい。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Sample</title>
        <script type="text/javascript">
            function save () {
                var data = document.getElementById("text").value;
                if (data.length) {
                    var obj = document.getElementById("anchor");
                    obj.href = "data:application/octet-stream," +
                               encodeURIComponent(data);
                    obj.innerHTML = "右クリックでリンク先を保存して下さい";
                }
            }
        </script>
    </head>
    <body>
        <textarea id="text" cols="30" rows="5" wrap="soft"></textarea><br>
        <button onclick="save();">save</button>
        <a id="anchor" href=""></a>
    </body>
</html>

以下のように、メッセージを入力して保存ボタンを押すとリンクが表示されます。

表示されたリンクを右クリックして「リンク先を別名で保存」を選ぶと以下のようなダイアログが表示され、ローカルファイルとしてメッセージを保存することができました。ですが、ファイル名は指定できないみたいです。

方法2

方法1だと右クリックから保存を選ばせたり、ファイル名が指定できなかったりと、いろいろ不便なので別の方法を調べてみました。
ActionScriptのExternalInterfaceではJavaScriptActionScriptが連携でき、ActionScriptのFileReference.save()ではActionScriptのデータをローカルファイルに保存できるということが分かりました。ASは一度も触ったことがなかったので、勉強代わりに「ASを経由してJSのデータを保存するプログラム」を書いてみました。
先に結果だけ言うと、以下のサンプルは動かないです(^_^;)

まずは、以下のActionScriptをWriteFile.asという名前で保存します。

  • WriteFile.as
package {
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.external.ExternalInterface;
    import flash.net.FileReference;

    public class WriteFile extends Sprite {
        private var fileBody:String = "";
        private var fileName:String = "";

        function WriteFile () {
            // JSのwrite()が呼び出されるとASのbridge()を呼び出すように登録する
            ExternalInterface.addCallback("write", bridge);
            // マウスクリックイベントを検出するとdoWrite()を呼び出すように登録する
            this.addEventListener(MouseEvent.CLICK, doWrite);
        }

        private function bridge (body:String, name:String):void {
            // 保存するデータとファイル名を変数に保持する
            fileBody = body;
            fileName = name;
            // マウスクリックイベントを発生させる
            this.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
        }

        private function doWrite (eventObj:MouseEvent):void {
            var fr:FileReference = new FileReference();
            try {
                // ローカルファイルに保存できるか試みる
                fr.save(fileBody, fileName);
            } catch (error:Error) {
                // 保存できなければアラートを表示する
                ExternalInterface.call("function(){javascript:alert('だが断る " + error.message + "')}");
            }
        }
    }
}

このWriteFile.asでは、

  1. JSのwrite()メソッドが呼び出されるとASのbridge()メソッドを呼び出す
  2. 保存したいファイルの名前と内容をASの変数にコピーする
  3. マウスクリックイベントを発生させる
  4. マウスクリックイベントを検出してASのdoWrite()メソッドを呼び出す
  5. JSのwrite()メソッドから受け取った内容をローカルファイルに保存する

という流れでローカルファイルにデータを保存しています。3, 4の処理は無駄なように見えますが、FileReference.save()メソッドは

マウスクリックまたはキー押下イベントのイベントハンドラなど、ユーザイベントに応答する形でのみ実行してください。それ以外の場合はエラーが発生します。

書かれていたので3, 4の処理を行っています。


次に、WriteFile.asをコンパイルします。

 % mxmlc WriteFile.as
設定ファイル "/usr/local/flex_sdk_4.1/frameworks/flex-config.xml" をロードしています
/Library/WebServer/Documents/writefile/WriteFile.swf (946 バイト)


そして、コンパイルしてできたWriteFile.swfファイルと同じ階層に以下のHTMLファイルを置いてWebブラウザーからアクセスして下さい。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <script type="text/javascript">
            function save () {
                var body = document.getElementById("fbody").value;
                var name = document.getElementById("fname").value;
                if (!body || !name) return false;
                var wf = document["wf"];
                wf.write(body, name);
            }
        </script>
    </head>
    <body>
        <embed src="WriteFile.swf" name="wf" allowScriptAccess="always" width="0" height="0" />
        File Name : <input id="fname" type="text"><br>
        <textarea id="fbody" cols="30" rows="5" wrap="soft"></textarea><br>
        <button onclick="save();">save</button>
    </body>
</html>


以下のように、適当にファイル名とファイルに保存したいメッセージを書いてsaveボタンを押して下さい。

すると、ファイル保存ダイアログが表示されるはずですが…

WriteFile.as内のFileReference.save()で例外が投げられので、上のようなアラートが表示されてファイル保存に失敗しました。アラートを表示する際、Errorクラスのmessageプロパティも一緒に表示するようにしています。上の画像の『Error #2176』の部分です。このページによると、エラーコード2176というのは、

ポップアップウィンドウを表示するアクションなどの特定のアクションは、マウスをクリックしたりボタンを押したりするユーザー操作によってのみ呼び出すことができます。

と、言う事らしいです。これは「dispatchEvent()メソッドで発生させたマウスクリックイベントではFileReference.save()が実行できない」という意味だと思います。

おわりに

方法1だと"一応"ローカルファイルに保存することができました。方法2だとActionScriptと連携しているため、実行時に制限がありローカルファイルに保存することができませんでした。次は、ちゃんとしたマウスクリックイベント発生させ、ローカルファイルにデータを保存するプログラムを書いてみようと思います。