Android ですと、View パーツを console.log
するとプロパティでイベントが参照できたりするので、そこそこ自動化しやすいのですが、iOS も視野にいれると、はい。クロスプラットフォームですしね。
というわけで、がんばります。
最新版の Alloy 1.5.1 で動作確認していますが、プライベート変数にアクセスしたり、Alloy がコンパイルしたソースコードを正規表現で抽出したりと無茶をしている所があるので、Alloy がバージョンアップしたりすると動かなくなる恐れがあります。
alloy.jmk は Alloy がコンパイルする前後に処理をフックすることができます。プロジェクトフォルダへ移動し、$ alloy generate jmk
とコマンドラインで実行してみましょう。projectDir/app/alloy.jmk が作られていると思います。以下のコードをコピペしてください。
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
へ上記のコードを書いています。
- wrench で全コントローラをさらいます(BaseController.js は除く)
- 正規表現で
addEventListener
している箇所を抜き出します。ここで注意してもらいたいのが、addEventListener
しているコードを正規化したいので、View でonClick
等、onHogehoge
しているイベントだけに限定します(自分でaddEventListener
しているなら自分でremoveEventListener
してくださいね) - コントローラ名、View の ID、ハンドラ名で JSON を作ります。View に ID をふっていない?大丈夫です。Alloy が勝手にふってくれています。Resources の中のコードを見るとわかりますが、
__alloyIdXX
な感じです - Resources/[platform]/EventMapper.js を書き出します
これで、コントローラの中で var EventMappper = require('EventMapper');
できるようになりました。では実際に画面を閉じた時にイベントを外してみましょう。
<Alloy>
<Window onClose="doClose">
<Label onClick="doClick">このウィンドウを閉じるとこのラベルのクリックイベントが勝手に removeEventListener されるよ!</Label>
</Window>
</Alloy>
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 です。下記のような感じです。
{
"index": { // これコントローラ名
"label": [ // これ View ID
{
"type": "click",
"handler": "doClick"
}
]
}
}
今回はなるべく楽したいので、ウィンドウのクローズイベントをコピペで済ませたいのです。自分自身のコントローラ名を毎回書かないといけない?いやいや、プライベート変数にあるのですよ、$.__controllerPath
が。これでこのコントローラのイベントを全て取得しましょう。第2引数を省略すれば View ID も意識する必要がありません。
あとは _.each
で回しながら removeEventListener
するだけです。eval で。