Alloy プロジェクトでウィンドウを閉じたら配下の View のイベントを remove したい。ほぼ自動で

Android ですと、View パーツを ```console.log``` するとプロパティでイベントが参照できたりするので、そこそこ自動化しやすいのですが、iOS も視野にいれると、はい。クロスプラットフォームですしね。

というわけで、がんばります。

最新版の Alloy 1.5.1 で動作確認していますが、プライベート変数にアクセスしたり、Alloy がコンパイルしたソースコードを正規表現で抽出したりと無茶をしている所があるので、Alloy がバージョンアップしたりすると動かなくなる恐れがあります。

alloy.jmk は Alloy がコンパイルする前後に処理をフックすることができます。プロジェクトフォルダへ移動し、```$ alloy generate jmk``` とコマンドラインで実行してみましょう。projectDir/app/alloy.jmk が作られていると思います。以下のコードをコピペしてください。

```js
task("pre:compile", function(event,logger) {
});

task("post:compile",function(event,logger){
	var fs = require('fs'),
		path = require('path'),
		wrench = require('wrench'),
		_ = require('alloy/Alloy/lib/alloy/underscore'),
		platforms = require('alloy/platforms/index'),
		CONST = require('alloy/Alloy/common/constants');

	var titaniumFolder = platforms[event.alloyConfig.platform].titaniumFolder,
		controllerFolder = path.join(event.dir.resources, titaniumFolder, CONST.ALLOY_RUNTIME_DIR, CONST.DIR.CONTROLLER),
		files = wrench.readdirSyncRecursive(controllerFolder),
		js,
		controller,
		matches,
		events = {};

	_.each(files, function(_file){
		if (_file === 'BaseController.js') {
			return;
		}

		if (_file.match(/\.js$/)) {
			js = fs.readFileSync(path.join(controllerFolder, _file), 'utf-8');
			controller = _file.replace(/\.js$/, '')

			if (matches = js.match(/(.+?)\s\?\s\$\.__views\.(.+?)\.addEventListener\(["'](.+?)["'],\s(.+?)\)\s:\s__defers\[["'](.+?)["']\]\s=\strue;/ig)) {
				_.each(matches, function(_event){
					var tmp = _event.split(/\s[?:=]\s/),
						handler = tmp[0].replace(/\s/ig, ''),
						type = tmp[2].split('!')[1],
						id = tmp[1].split('.')[2];

					if (!_.has(events, controller)) {
						events[controller] = {};
					}

					if (!_.has(events[controller], id)) {
						events[controller][id] = [];
					}

					events[controller][id].push({
						type: type,
						handler: handler
					});
				});
			}
		}
	});

	var code = 'var Alloy = require(\'alloy\'),\n\tBackbone = Alloy.Backbone,\n\t_ = Alloy._;\n\n' +
		'var map = ' + JSON.stringify(events) + ';\n\n' +
		'exports.get = function(_controller, _id){\n' +
		'\t_id = _id || null;\n' +
		'\n' +
		'\tif (!_.has(map, _controller)) {\n' +
		'\t\treturn null;\n' +
		'\t}\n' +
		'\n' +
		'\tif (_id) {\n' +
		'\t\tif (_.has(map[_controller], _id)) {\n' +
		'\t\t\treturn map[_controller][_id]\n' +
		'\t\t} else {\n' +
		'\t\t\treturn null;\n' +
		'\t\t}\n' +
		'\t} else {\n' +
		'\t\treturn map[_controller];\n' +
		'\t}\n' +
		'};';

	fs.writeFileSync(path.join(event.dir.resources, titaniumFolder, 'EventMapper.js'), code);
});
```

今回は Alloy がコンパイルした後のコードを触るので、```post:compile``` へ上記のコードを書いています。

1. [wrench](https://www.npmjs.org/package/wrench) で全コントローラをさらいます（BaseController.js は除く）
2. 正規表現で ```addEventListener``` している箇所を抜き出します。ここで注意してもらいたいのが、```addEventListener``` しているコードを正規化したいので、View で ```onClick``` 等、```onHogehoge``` しているイベントだけに限定します（自分で ```addEventListener``` しているなら自分で ```removeEventListener``` してくださいね）
3. コントローラ名、View の ID、ハンドラ名で JSON を作ります。View に ID をふっていない？大丈夫です。Alloy が勝手にふってくれています。Resources の中のコードを見るとわかりますが、```__alloyIdXX``` な感じです
4. Resources/[platform]/EventMapper.js を書き出します

これで、コントローラの中で ```var EventMappper = require('EventMapper');``` できるようになりました。では実際に画面を閉じた時にイベントを外してみましょう。

```xml
<Alloy>
  <Window onClose="doClose">
    <Label onClick="doClick">このウィンドウを閉じるとこのラベルのクリックイベントが勝手に removeEventListener されるよ！</Label>
  </Window>
</Alloy>
```

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

function doClick() {
  alert('ウィンドウのクローズイベントもね！');
}

function doClose() {
  var events = EventMapper.get($.__controllerPath)

  _.each(events, function(_events, _id){
    var _view = $.getView(_id);

    _.each(_events, function(_event){
      _view.removeEventListener(_event.type, eval(_event.handler));
    });
  });
}
```

```EventMapper.get``` は第1引数にコントローラ名（.js が無いファイル名ですね）、第2引数に View の ID でイベントのタイプ、ハンドラを取得することができます。第2引数は省略可能です。

返却値は JSON です。下記のような感じです。

```json
{
  "index": { // これコントローラ名
    "label": [ // これ View ID
      {
        "type": "click",
        "handler": "doClick"
      }
    ]
  }
}
```

今回はなるべく楽したいので、ウィンドウのクローズイベントをコピペで済ませたいのです。自分自身のコントローラ名を毎回書かないといけない？いやいや、プライベート変数にあるのですよ、```$.__controllerPath``` が。これでこのコントローラのイベントを全て取得しましょう。第2引数を省略すれば View ID も意識する必要がありません。

あとは ```_.each``` で回しながら ```removeEventListener``` するだけです。**eval** で。