LoginSignup
5
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-10-06

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 へ上記のコードを書いています。

  1. 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'); できるようになりました。では実際に画面を閉じた時にイベントを外してみましょう。

<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 で。

5
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4