DOMNodeInsertedIntoDocument イベントのクロスブラウザ対応試作

Firefox や、 Opera で DOMNodeInsertedIntoDocument イベントがあまりイケてなかったので、比較的マシな DOMNodeInserted イベント使って、似たような動きになるようなモノを作ってみた。


Firefox 2.0.0.12 と Opera 9.25 と Safari 3.0.4 で動作確認してます。
IE は、今、手元に環境が無いから試してないけど多分動くんじゃないかな。動けばいいな。

var InsertedIntoDocument = function(){};
(function(){
  
/*************************/
  /**
   * Original Source :
   * jQuery (jquery.com)
   */
  var ua = navigator.userAgent.toLowerCase();
  var browser = {
  	version: (ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
  	safari: /webkit/.test(ua),
  	opera: /opera/.test(ua),
  	msie: /msie/.test(ua) && !/opera/.test(ua),
  	mozilla: /mozilla/.test(ua) && !/(compatible|webkit)/.test(ua)
  };
/*************************/

  this.uniqid = function(prefix, table) {
    var id = prefix + ((new Date()).getTime() + Math.random());
    return (typeof table[id] != 'undefined') ? arguments.callee(prefix, table) : id;
  }

  if (!browser.msie && !browser.safari) {
    this.handlers = {};
    document.addEventListener('DOMNodeInserted',function(evt) {
      var handler =  InsertedIntoDocument.handlers[evt.target.__handler_id__];
      if (typeof handler != 'object') return;
/* target 周りが上手く行かないからとりあえず DOMNodeInserted を渡す。
      var e = document.createEvent("MutationEvents");
      e.initMutationEvent(
        "DOMNodeInsertedIntoDocument",
        evt.cancelBubble,
        evt.cancelable,
        undefined,
        evt.prevValue,
        evt.newValue,
        evt.attrName,
        evt.attrChange
      );
 */
      for (var i in handler) handler[i].call(evt.target,evt);
    },true);
  }

  this.add = (function() {
    // for Internet Explorer
    /* @cc_on
    return function(element, fn, useCapture) {
      target.attachEvent('onreadystatechange',fn);
    }
    @*/
    if(browser.safari) {
      return function(element, fn, useCapture) {
        var type =  (browser.version < 3)
            ? 'readystatechange' : 'DOMNodeInsertedIntoDocument';
        element.addEventListener(type, fn, useCapture);
      }
    } else if(!browser.msie) {
      return function(element, fn, useCapture) {
        if (typeof element.__handler_id__ == 'undefined')  
          element.__handler_id__ =  this.uniqid('handler_', this.handlers);
        var id = element.__handler_id__;
        if (typeof this.handlers[id] != 'object') this.handlers[id] = {};
        var listener_id = this.uniqid('listener_', this.handlers[id])
        fn.listener_id = listener_id;
        this.handlers[id][listener_id] = fn;
      }
    }
  })();
  this.remove = (function() {
    // for Internet Explorer
    /* @cc_on
      return function(element, fn, useCapture) {
        target.detachEvent('onreadystatechange',fn);
      }
    @*/
    if(browser.safari) {
      return function(element, fn, useCapture) {
        var type =  (browser.version < 3)
            ? 'readystatechange' : 'DOMNodeInsertedIntoDocument';
        element.removeEventListener(type, fn, useCapture);
      }
    } else if(!browser.msie) {
      return function(element, fn, useCapture) {
        delete this.handlers[element.__handler_id__][fn.listener_id];
        if (this.countProperty(this.handlers[element.__handler_id__]) == 0) 
          delete this.handlers[element.__handler_id__];
        delete fn.listener_id;
        delete element.__handler_id__;
      }
    }
  })();
  this.countProperty = function(obj) {
    var count = 0;
    for(var i in obj) count++;
    return count;
  }
  return this;
}).call(InsertedIntoDocument);

使い方はこんな感じ。

var e1 = document.createElement('div');
e1.id = "e1";
InsertedIntoDocument.add(e1,function(evt){
  console.log("DOMNodeInsertedIntoDocument !");
  console.dir(evt);
},true);
setTimeout(function() {
  document.body.appendChild(e1);
  console.log('added');
},1000);

イベントハンドラやリスナの管理とかするために、DOM オブジェクトや Function オブジェクトにそれぞれの ID 振ったりしてるのがあまり綺麗じゃないので、いい方法思いついたらなんとかしたい。
あと、ちゃんと DOMInsertedIntoDocument イベントをリスナに渡せるようにしたい。