クロスブラウザに対応したEventに書き換える

JavascriptのEvent Handlerの引数で受け取るEvent、多言語で言うところの"EventArgs"をクロスブラウザに対応させる。

例えば、以下のようなイベントハンドラがあるとする。

function clickEventHandler(event){
  alert('clicked.');
  event.stopPropagation();
}
このままではIEの場合、stopPropagationというメソッドはeventのメンバにないので、イベント伝播を止められない。
これに対応するために通常なら『event.cancelBubble = true;』を書き加えるだろう。

つまり、どう対応するかは予め決まっている。それならば、事前にそのような処理に書き換えれば良いのではないか?と考えた。



イベント伝播を止めるには以下のような処理を行う。
if(event.stopPropagation){
  event.stopPropagation();
}else{
  event.cancelBubble = true;
}
stopPropagationメソッド自体を上のような処理を行うように書き換えてしまえば、イベントハンドラ側でブラウザの違いを気にする必要がなくなり、IEでもそれ以外のモダンブラウザでもstopPropagationメソッドを使えばイベント伝播を止めることが出来るようになる。

まず、イベントハンドラをフックするための関数。
function addEvent(elm, type, fn){

  function handler (){
    arguments[0] = eventFix( arguments[0] || window.event );
    fn.apply( this, arguments );
  }
  
  //Modern Browser
  if (document.addEventListener) {
    elm.addEventListener(type, handler, false);
  }
  //IE
  else 
    if (document.attachEvent) {
      elm.attachEvent('on' + type, handler);
    }
    else {
      elm['on' + type] = handler;
    }
}

『イベント発生 → fn』
    ↓
『イベント発生 → hendler → fn』

handler関数を間に挟むと考えると分かりやすいだろうか。
このhandler関数で下記のeventFix関数を呼び出し、eventを書き換えてからfn関数に渡す。という流れ。

ブラウザ間の違いを吸収するための関数
function eventFix(e){
  var
   //Event Clone
  cloneEvent = {}, 
  //Original Event
  originalEvent = e;
  
  //Copy Event
  for (var prop in originalEvent) {
    cloneEvent[prop] = originalEvent[prop];
  }
  
  
  /* stopPropagation
   * イベントフローにおいてこれ以上イベントが伝えられるのを止める。
   */
  cloneEvent.stopPropagation = (function(){
    return originalEvent.stopPropagation ? function(){
      originalEvent.stopPropagation();
    } : function(){
      originalEvent.cancelBubble = true; //IE
    };
  })();
  
  
  /* preventDefault
   *  イベントがキャンセル可能な場合、
   * そのイベントの結果として通常は実装により実行される
   * デフォルトのアクションをキャンセルする。
   */
  cloneEvent.preventDefault = (function(){
    return originalEvent.preventDefault ? function(){
      originalEvent.preventDefault();
    } : function(){
      originalEvent.returnValue = false; //IE
    };
  })();
  
  return cloneEvent;
}


HTML:
<html>
<body>
<div id='wrap'><span>wrap</span>
  <a id='button' href="javascript:void(0)">Button</a>
</div>
</body>
</html>

Usage:
addEvent(window, 'load', function(){
  addEvent(document.getElementById('button'), 'click', function(e){
    alert( 'Button Clicked.' );
    e.stopPropagation();
    //e.preventDefault();
  });
  
  addEvent(document.getElementById('wrap'), 'click', function(e){
    alert( 'Wrap Clicked.' );
  });
});
Buttonエレメントをクリックしても親要素であるWrapエレメントに関連付けられたclickイベントは発生しない。

もちろんこれは完璧なものではない。ただ、こうすれば可能、というもので使用可能なレベルかは分からない。

先人さんが既に実装していた

これを書きながら「私が考えることなど先人が既に考えたのでは?」ということに気付き、ちょっと調べるとjQueryでは既に対応していることが分かった。
jQuery.Event辺りのソースを見ると、実装されていることが分かる。

例えば、jQueryでは押されたキーのkeycodeを取得するにはevent.whichで統一して取得できるようになっている。


ということで...
jQueryを使えばeventもクロスブラウザに対応しているので、ブラウザ間の違いを気にせずに書ける。

0 Comments:

Recent Posts