プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

JavaScriptで作ったちょっとしたスクリプトからファイルを保存したい

 JavaScriptはちょこっと何かを作ろうと思うと、一番使いやすいと思っている。なんせ特に何もいらない。エディタとブラウザがあればGUIを持った簡単なスクリプトなんかすぐ作れる。最近は簡単な物ならブラウザ内で全部完結して、エディタすらいらない。
 Excelマクロを習ってGUIの作成を習うよりずっと簡単だと私は思っている。なお、異論は認める。

 が、このJavaScriptは簡単ではあるが、ファイルの保存が出来ない。File APIもChromeしかない。
 私ならJavaScriptで何かして保存したいときはGreasemonkey使ったり、keysnail使ったりするって手段もあるっちゃある。
 でも、あまりプログラミングに詳しくない人が簡単に作って試して保存まで勢いで出来なければ意味が無い。


 だが、そんな方法はない。と、思っていたら意外とHTML5には別の手段で保存する方法があるようだ。
 以下のsaveTextを実行するだけで、とりあえずローカルに保存できる。ただし、ブラウザ設定によっては何も聞かずにDownloadフォルダに勝手に保存しちゃうのが玉にキズだけどね。

function saveText(text, fileName) {
  var a, blob, event;
  if (fileName == null) {
    fileName = "textfile.txt";
  }
  blob = new Blob([text], {
    type: "text/plain"
  });
  if (window.navigator.msSaveBlob != null) {
    window.navigator.msSaveBlob(blob, fileName);
  } else {
    a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.target = "_blank";
    a.download = fileName;
    event = document.createEvent("MouseEvents");
    event.initEvent("click", false, true);
    a.dispatchEvent(event);
  }
};


CoffeeScriptバージョン

saveText = (text,fileName)->
  if !fileName? then fileName = "textfile.txt"
  
  blob = new Blob [text],{type:"text/plain"}
  if window.navigator.msSaveBlob? then window.navigator.msSaveBlob blob,fileName
  else
    a = document.createElement "a"
    a.href = URL.createObjectURL blob
    a.target = "_blank"
    a.download = fileName
    event = document.createEvent "MouseEvents"
    event.initEvent "click",false,true
    a.dispatchEvent event
  return


参考:
JavaScriptだけでファイルの保存機能を実装する - 新人Webエンジニアの記録。
JavaScriptのクリックイベントを発火させる方法 - ゆうなんとかさんの雑記帳的な。yuuxxxx.hatenablog.com





ちょっと根性あるならnode-webkitという選択肢も

 去年ぐらいからちょくちょく耳にすることがあるnode-webkit。要するにHTML5とJavaScriptとそれの実行環境をひとまとめにしてアプリケーションにしましょうというものですかね。
 node.jsの機能が使えるのでファイルの保存とか出来ます

 詳しくはよく知らないけどね!(え 個人的には単体で動くブラウザのプラグインだと思っています。

 liginc.co.jp




まだ根性あるなら好みの関数を持つブラウザ作っちゃおうぜ

 回りくどいことせずに、JavaScriptからダイレクトに保存関数を呼べるブラウザを自作しちゃうってのも一つの手だ(え?


 例えば、Qtとか、JavaFXにはWebEngineがあるから、それを使うと、意外に簡単にJavaScriptで保存することが出来るブラウザを作れる。
 まぁ、確かに、最初のブラウザを作るのはちょい骨折れるけど、一回作ってしまったら、node-webkitの用に毎回アプリケーションにする必要なんて無いぞ!これは楽だ!




 ………いや、やっぱそれぐらいならJavaやらC#やらExcel VBAを覚えた方が絶対早いよ




 最後に載せたJavaFXのWebEngineを使った例に次のHTMLファイルを開かせると、ファイルの保存画面が開いてテキストファイルが保存される。





 さぁ、私の匙は投げられた。好きな方法を選ぶがよい。


test用HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
Files.saveText("内容");
</script>
</body>
</html>


自作ブラウザ

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.*;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.*;
import javafx.stage.FileChooser.ExtensionFilter;
import netscape.javascript.JSObject;

public class Browser extends Application{

  public static void main(final String[] args){
    System.setProperty("prism.lcdtext", "false");
    launch(args);
  }
  private WebView view;
  private TextField url;

  @Override
  public void start(final Stage stage) throws Exception{
    final WebView v = view=new WebView();
    JSFiles.install(v);

    final TextField url = this.url = new TextField();
    final Button open = new Button("Open");
    url.setOnAction(this::openURL);
    open.setOnAction(this::openURL);

    final BorderPane p = new BorderPane(v);
    HBox.setHgrow(url, Priority.ALWAYS);
    p.setTop(new HBox(url,open));
    p.setPrefSize(800, 600);

    stage.setScene(new Scene(p));
    stage.show();
  }


  public void openURL(final ActionEvent e){
    final String text = url.getText().trim();
    if(text == null) {
      return;
    }

    String url;
    try{
      url = Paths.get(text).toUri().toURL().toString();
    }catch(final Exception e1){
      url = text;
    }


    final WebEngine engine = view.getEngine();
    if(url.equals(engine.getLocation())){
      engine.reload();
    }else{
      engine.load(url);
    }

  }



  public static class JSFiles{

    public static void install(final WebView v){
      final JSFiles jsf = new JSFiles(v);
      final WebEngine engine = v.getEngine();
      final JSObject window = (JSObject)engine.executeScript("window");
      window.setMember("JavaFiles", jsf);

      final JSObject eval = (JSObject)engine.executeScript(
          "var Files = {};"
          + "Files.saveText = function(str,charset){"
          + "JavaFiles.__saveText__(str,charset);"
          + "};");
      System.out.println(eval);
    }

    private WebView view;

    public JSFiles(final WebView view){
      this.view = view;
    }


    public boolean __saveText__(final String str,final String charset){
      final FileChooser chooser = new FileChooser();
      chooser.getExtensionFilters().addAll(TEXTFILTER,ALL);
      final Window w = view.getScene().getWindow();
      final File file = chooser.showSaveDialog(w);

      if(file!=null){

        Charset c =null;
        try{
          if(charset != null){
            c= Charset.forName(charset);
          }
        }catch(final Exception e){}
        if(c == null) {
          c = Charset.defaultCharset();
        }

        @SuppressWarnings("resource")
        final Scanner scan = new Scanner(str);


        final Iterator<CharSequence> i = new Iterator<CharSequence>(){
          @Override
          public CharSequence next(){
            return scan.nextLine();
          }
          @Override
          public boolean hasNext(){
            return scan.hasNextLine();
          }
        };

        try {
          Files.write(file.toPath(),()->i, c);
          return true;
        } catch (final IOException e) {
          e.printStackTrace();
          return false;
        }
      }else{
        return false;
      }
    }
    private static ExtensionFilter TEXTFILTER=new ExtensionFilter("*.txt", "*.txt");
    private static ExtensionFilter ALL=new ExtensionFilter("*", "*");
  }

}