久しぶりに記事を書きます。カンボジア記も絶賛放置中です・・

人生、何かあったわけではなく、最近、ブログを書くのが面倒くさいなーと思うようになっただけです。

あと、前のブログと今のブログを統一してやろうとか画策しているのであまり今のブログの記事を増やしたくない、というのもあります。画策しているだけで何も進んでいませんが。

本題

以前、ドラッグ&ドロップの仕組みを作ったときにremoveEventListenerができなくて、これじゃダメじゃん的な感じだったわけですが、それを解決する仕組みを考えました。

スコープの維持

イベントハンドラのコールバック関数のスコープはwindowオブジェクトになってしまうので、それを回避するために

var scope = this;
var callback = //do something...
window.addEventLisetner("load", function(event) {
  callback.call(scope, event);
}, false);

などとしてスコープを維持するのですが、この場合、コールバック関数は無名関数なので、この無名関数の外からはremoveEventListenerすることができません。なんかくやしい。

無名関数を特定する

そこで、このコールバック関数を一意の識別子と一緒にどこかに保管することを考えます。 一意の識別子はイベントタイプとコールバック関数名の組み合わせにします。例えば、1つの要素のクリックに対して同じコールバックを複数登録はしないはずなので。あと、コールバック関数名が必要なので、関数ではなく文字列として引数に渡すようにします。

var scope = this;
var callback = //do something...
window.addEventLisetner("load", function(event) {
  scope[callback](event);
}, false);

この識別子とコールバック関数の組み合わせをオブジェクトにして配列にぶち込んでおき、あとで識別子をキーにして取り出せるようにします。識別子はイベントタイプとコールバック関数名でできているので引数として渡せば取り出せます。

EventHandler = {
  stack: [],
  addListener: function(element, type, callback, context) {
    var id = "f_" + type + "_" + callback;
    var stack = this.stack;
    var func = function(e) { context[callback](e); };
    var handler = { id: id, func: func };

    for (var i = 0; i < stack.length; i++) {
      if (id==stack[i].id) return;
    }
    if (element.addEventListener) element.addEventListener(type, func, false);
    else if (element.attachEvent) element.attachEvent("on" + type, func);
    stack.push(handler);
  },
  removeListener: function(element, type, callback, context) {
    var id = "f_" + type + "_" + callback;
    var stack = this.stack;
    for (var i = 0; i< stack.length; i++) {
      if (id == stack[i].id) {
        if (element.removeEventListener) element.removeEventListener(type, stack[i].func, false);
        else if (element.detachEvent) element.detachEvent("on" + type, stack[i].func);
        stack.splice(i, 1);
      }
    }
  }
};
  • EventHandler.stack:識別子と関数名のオブジェクトを積んでおく配列です。
  • EventHandler.addListener:識別子の生成、コールバック関数の生成、スタックへ積み上げ。ついでにDOM LEVEL2のaddEventListenerとIEイベントのattachEventをラップします。同タイプ、同コールバックのリスナーは登録できないようにしています。
  • EventHandler.removeListener:スタックから識別子の一致するコールバックを取り出し、removeEventListenerします。ついでにDOM LEVEL2のremoveEventListenerとIEイベントのdetachEventをラップします。

(IE8になってもDOM LEVEL2 EVENTを実装してないってひどすぐる)

作ってみると、なんてことはない単なるオブジェクトなんですけどね・・イベントドリブンの奥は深いですね。