Electronでファイルやフォルダの選択

> ※こちらの記事の内容はElectronの現行のVersion 1.x系では動作しません
> http://qiita.com/khirose/items/11dae9f4650767d20c4f
> こちらにversion 1.x系でのの焼き直しをしていますので参照ください



# はじめに


HTML5/CSS/JavaScriptといったWeb技術でおなじみの技術でネイティブアプリも作れるよ！という触れ込みの[Electron](http://electron.atom.io/)
ネイティブアプリなら自分のPCの中にあるファイルやフォルダを扱いたいわけで

## 普通のHTML + JavaScriptでやろうとしたら失敗した

画面のHTMLに

```html
<input type="file">
<input type="file" webkitdirectory directory>
```
を書いてこいつのchangeイベントをひっかけて・・・とやったけどダメ
あくまでもChromiumのサンドボックスの世界からは出ないブラウザとしての振る舞いになるので、入ってくるファイル名などは C:\fakeroot\ファイル名 となって、PCのファイルシステム上のパスではないのでそのままファイル名として使えない
ファイル名は重要じゃなく、中身だけが必要だという場合であれば[ここからReader作って...](http://qiita.com/khirose/items/d384b77c84d0fc5f5eb9)でいいんだけど、今回は必要な要件の中にファイル名もあるのでこの方法は無理筋と判断

## そこでプロセス間通信APIを使えばいい

画面提供側（レンダラプロセス）から

```js
var remote = require('remote');
```

でnode.jsベースで動いているメインプロセスを取得できる
HTML側のclickイベントを補足して、そのハンドラ内でブラウザ側のjsではなくメインプロセス側のAPIを叩けばネイティブのファイルを選ぶダイアログ、フォルダを選ぶダイアログを表示したり、選択内容(PCのファイルシステム上のフルパス名)を引っ張れる
あとはこれをHTML側に統合されているnode.jsのfsモジュールとかで操作すればよい

以下はサンプル（jQueryとBootstrapをローカルに入れて使ってます)

```json:package.json
{
  "name": "example",
  "version": "0.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "NYSL",
  "keywords": [],
  "description": ""
}
```

```js:index.js
'use strict';

var app = require('app');
var BrowserWindow = require('browser-window');

require('crash-reporter').start();

var mainWindow = null;

app.on('window-all-closed', function() {
	if (process.platform != 'darwin') app.quit();
});

app.on('ready', function() {
	mainWindow = new BrowserWindow({width: 1024, height: 768});

	//	↓を入れるとデフォルトで開発ツールが開いた状態になる
	mainWindow.openDevTools();

	mainWindow.loadUrl('file://' + __dirname + '/index.html');
	mainWindow.on('closed', function() {
		mainWindow = null;
	});
});
```

```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Example</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<div class="container">
	<button id="fileSelect" type="button" class="btn btn-primary btn-lg btn-block">ファイル選択</button>
	<button id="folderSelect" type="button" class="btn btn-default btn-lg btn-block">フォルダ選択</button>
</div>

<script>
// node_integrationを行うとmodule.exports関係の事情でjQuery/$がundefinedになるので
// それの回避コード
window.jQuery = window.$ = require('./js/jquery-2.1.4.min.js');
</script>
<script src="./js/bootstrap.min.js"></script>
<script>
/**
* ここからメインディッシュ
*/
var remote = require('remote');
var dialog = remote.require('dialog');
var browserWindow = remote.require('browser-window');

//	以下選択されたファイルをいじりたい場合はnode.jsのfsが使える
var fs = require('fs');

$(function(){
	// ボタンが押されたときの挙動
	$('#fileSelect').on('click', function(){
		var focusedWindow = browserWindow.getFocusedWindow();

		dialog.showOpenDialog(focusedWindow, {
			properties: ['openFile'],
			filters: [{
				name: 'テキストファイル',
				extensions: ['txt']				
			}]
		}, function(files){
			files.forEach(function(file){
				console.log(file);
			});
		});
	});

	$('#folderSelect').on('click', function(){
		var focusedWindow = browserWindow.getFocusedWindow();

		dialog.showOpenDialog(focusedWindow, {
			properties: ['openDirectory']
		}, function(directories){
			directories.forEach(function(directory){
				console.log(directory);
			});
		});
	});
});
</script>
</body>
</html>
```

ダイアログを出した上で何も選択しないとfilesやdirectoriesがundefinedになり、undefined.forEachを呼ぼうとしてエラーが出るっぽい
しかしクライアントサイドのJavaScriptに何も考えずrequire()書けるのは不思議な感覚