iPod & iPad & iPhoneで、クリックイベントを発火させて、それを使えるようにする! その3

考えるほどに、また無駄に長くなってしまいます。^^;
「シングルトン」、あ〜簡易版、簡易版、簡易版。(毎度の事ながら、思考はどっちに向いているのだろう?(涙))
マウスイベントもオブジェクトにしました。

<!DOCTYPE html>
<title></title>
  <meta name="viewport" content="width=640">
  <style>
  h1 {
    font-size: large;
  }
  h3 {
    margin-bottom : 0;
  }
  h3 + p {
    font-size: small;
    color: gray;
    margin : 0 2em;
  }
  em {
    color: green;
    font-size:x-large;
  }
  li.focus {
    border-bottom : 2px red dotted;
  }
  </style>
<body>

<h1>
  <em>iPod</em> &amp;
  <em>iPad</em> &amp;
  <em>iPhone</em> で、クリックイベントを有効にする
</h1>

<h3>クリックイベントが発生する状態</h3>
<ul>
  <li>document をクリック(タップ)した時
  <li>フォームの input要素(button)など
  <li>要素のインラインに書いたイベント
</ul>

<h3>クリックイベントの代わりに、touchstart, touchmove, touchend を利用する</h3>
<p>(3つのイベントを組み合わせて、クリックイベントを実現する)
<ul>
  <li>touchstart イベントが発生したら、スタート位置を記憶する
  <li>touchmove を監視し、現在位置との差が範囲外なら、止める
  <li>touchend イベントオブジェクトからは、位置が拾えないので、スタート位置を基準とし、
      クリックイベントを発生させる
</ul>

<h3>使い方</h3>
<ul>
  <li>
    <p>面倒なので略。
</ul>


<script>

document.addEventListener ('click', function (e) {
  var n = e.target;
  n.className = n.className ? '': 'focus';
}, false);

//_________________________________

//つまらないコードから。(シングルトン)
// Singleton
function createSingleton () {

  var stock_object = [];
  var stock_option = [];
  
  function getIndex (obj) {
    return stock_object.indexOf (obj);
  }
  
  return {
    'contains' :
      (function (/*obj*/) {
        for (var i = 0, obj; obj = arguments[i++]; )
          if (-1 < getIndex (obj))
            return true;
    
        return false;
      }),
   
   
   'add' :
     (function (obj, option) {
       if (! this.contains (obj)) {
         stock_object.push (obj);
         stock_option.push (option);
       }
     }),
   
   
   'remove' :
     (function remove (/*obj*/) {
       var i, idx;

       for (i, obj; obj = arguments[i++]; ) {
         idx = getIndex (obj);

         if (-1 < idx) {
           stock_object.splice (idx, 1);
           stock_option.splice (idx, 1);
         }
       }
     }),
   
   
   'length' :
     (function () {
       return stock_object.length;
     }),
   
   
   'getOption' :
     (function (obj) {
        var idx = getIndex (obj);
        return (-1 < idx) ? stock_option[idx]: null;
     }),
   

   'replaceOption' :
     (function (obj, option) {
        var idx = getIndex (obj);
        
        if (-1 === idx)
          return false;
        
        stock_option[idx] = option;
        return true;
     })
  };
}


//#################


(function () { // createMouseEvent

  var defMouseState = {
    'type'          : null,
    'canBubble'     : true,
    'cancelable'    : true,
    'view'          : null,
    'detail'        : 1,
    'screenX'       : 0,
    'screenY'       : 0,
    'clientX'       : 0,
    'clientY'       : 0,
    'ctrlKey'       : false,
    'altKey'        : false,
    'shiftKey'      : false,
    'metaKey'       : false,
    'button'        : 0,
    'relatedTarget' : null
  }

  function MouseEventArguments (arg) {
    this.arg = arg;
  }


  function cloneObject (obj) {
    var result = {};
    
    for (var i in obj)
      if (obj.hasOwnProperty (i))
        result[i] = obj[i];
    
    return result;
  }
  

  function replaceObject (obj) {
    for (var i in obj)
      if (obj.hasOwnProperty (i))
        if (this.hasOwnProperty (i))
          this[i] = obj[i];
  }


  function create (arg) {
    var obj = new MouseEventArguments;
    var state = { };
    var view;
    var doc;
    
    obj.configure =
      (function (o) {
        if (o)
          replaceObject.call (state, o)
      });
    
    
    obj.reset =
      (function () {
        state = cloneObject (defMouseState)
        this.configure (arg);
      });
    
    
    obj.fire =
      (function (target) {
        if (target && state.type) {
          var doc = (9 === target.nodeType)
                    ? target
                    : target.ownerDocument;
          var view = doc.defaultView;
          var evt = doc.createEvent ('MouseEvents');
          evt.initMouseEvent (
            state.type,
            state.canBubble,
            state.cancelable,
            state.view,
            state.detail,
            state.screenX,
            state.screenY,
            state.clientX,
            state.clientY,
            state.ctrlKey,
            state.altKey,
            state.shiftKey,
            state.metaKey,
            state.button,
            state.relatedTarget
          
          );
          target.dispatchEvent (evt);
        }
      });


    obj.reset ();
    return obj;
  }
  
  this.createMouseEvent = create;
})();



(function () {
  
  //_________________________________

  
  function Emulator (range, disabled) {
    this.range    = range;
    this.disabled = disabled;
  }


  var handler = (function () {
    var state = false;
    var point = { 'x' : 0, 'y' : 0 };
    
    return function (event) {
      if (this.disabled)
        return;

      var touch = event.touches;
      var eType = event.type;
      var range = this.range;
      
      if (state) {
        if (('touchmove' === eType) && (touch = touch[0])) {
          if ((Math.abs (touch.pageX - point.x) < range) ||
              (Math.abs (touch.pageY - point.y) < range)) {
                state = false;
          }
          else {
            point.x = touch.pageX;
            point.y = touch.pageY;
          }
        }
        else if ('touchend' === eType) {
          this.fire (event.target, point);
          event.preventDefault ();
        }
      }
      else if (('touchstart' === eType) && (1 === touch.length) && (touch = touch[0])) {
        point.x = touch.pageX;
        point.y = touch.pageY;
        state = true;
      }
    };
  })();
  
  
  // クリック・エミュレータ初期化
  function create (view, range, disabled) {
    if (! /iPhone|iPad|iPod/.test (navigator.userAgent)) // 簡易機種チェック
      return null;

    var doc = view ? view.document: document;
    var evt = createMouseEvent (doc);
    var emu;
    
    if (singleton.contains (doc)) {
      return singleton.getOption (doc);
    }

    emu = new Emulator (range || 10, !!disabled);
    emu.fire =
      (function (target, point) {
        var doc = target.ownerDocument;
        
        if (3 === target.nodeType)
          target = target.parentNode;
        
        evt.configure ({
          'type'          : 'click',
          'view'          : doc.defaultView,
          'screenX'       : point.x,
          'screenY'       : point.y,
          'clientX'       : point.x,
          'clientY'       : point.y,
          'relatedTarget' : doc.body
        });
        
        evt.fire (target);
    });
     
    singleton.add (doc, emu);
    
    doc.addEventListener ('touchstart', emu, false);
    doc.addEventListener ('touchmove', emu, false);
    doc.addEventListener ('touchend', emu, false);
    
    return emu;
  }
  
  Emulator.prototype.handleEvent = handler;
  
  var singleton = createSingleton ();

  this.ClickEmulator = create;
})();

ClickEmulator ();

</script>

その4

確かに、view を保存するようにする意味は無いですね

<!DOCTYPE html>
<title></title>
  <meta name="viewport" content="width=640">
  <style>
  h1 {
    font-size: large;
  }
  h3 {
    margin-bottom : 0;
  }
  h3 + p {
    font-size: small;
    color: gray;
    margin : 0 2em;
  }
  em {
    color: green;
    font-size:x-large;
  }
  li.focus {
    border-bottom : 2px red dotted;
  }
  </style>
<body>

<h1>
  <em>iPod</em> &amp;
  <em>iPad</em> &amp;
  <em>iPhone</em> で、クリックイベントを有効にする
</h1>

<h3>クリックイベントが発生する状態</h3>
<ul>
  <li>document をクリック(タップ)した時
  <li>フォームの input要素(button)など
  <li>要素のインラインに書いたイベント
</ul>

<h3>クリックイベントの代わりに、touchstart, touchmove, touchend を利用する</h3>
<p>(3つのイベントを組み合わせて、クリックイベントを実現する)
<ul>
  <li>touchstart イベントが発生したら、スタート位置を記憶する
  <li>touchmove を監視し、現在位置との差が範囲外なら、止める
  <li>touchend イベントオブジェクトからは、位置が拾えないので、スタート位置を基準とし、
      クリックイベントを発生させる
</ul>

<h3>使い方</h3>
<ul>
  <li>
    <p>面倒なので略。
</ul>


<script>

document.addEventListener ('click', function (e) {
  var n = e.target;
  n.className = n.className ? '': 'focus';
}, false);

//_________________________________

(function () {
  if (! /iPhone|iPad|iPod/.test (navigator.userAgent)) // 簡易機種チェック
    return null;

  var doc = document;
  var view = doc.defaultView;

  var Eventer =
    (function () {

      // object の複製
      function cloneObject (obj) {
        var result = { }, i;
        
        for (i in obj)
          if (obj.hasOwnProperty (i))
            result[i] = obj[i];
        
        return result;
      }
      
      var object = {
        'defArguments' : { // document.createEvent の default の 引数
          'type'          : null,
          'canBubble'     : true,
          'cancelable'    : true,
          'view'          : view,
          'detail'        : 1,
          'screenX'       : 0,
          'screenY'       : 0,
          'clientX'       : 0,
          'clientY'       : 0,
          'ctrlKey'       : false,
          'altKey'        : false,
          'shiftKey'      : false,
          'metaKey'       : false,
          'button'        : 0,
          'relatedTarget' : doc
        },
        
        'arguments' : { }, // (a) 実際に利用される引数
        
        'reset' : // 引数を default に戻す
          (function () {
            this.arguments = cloneObject (this.defArguments); }),
        
        'configure' : // 引数(a)を置き換える
          (function (obj) {
            for (var i in obj)
              if (obj.hasOwnProperty (i)) // obj に含まれるものだけ
                if (this.arguments.hasOwnProperty (i))
                  this.arguments[i] = obj[i]; }),
        
        'fire':
          (function (target) {
            var arg = this.arguments;
            if (target && arg.type) {
              var evt = doc.createEvent ('MouseEvents');

              evt.initMouseEvent (
                arg.type,
                arg.canBubble,
                arg.cancelable,
                arg.view,
                arg.detail,
                arg.screenX,
                arg.screenY,
                arg.clientX,
                arg.clientY,
                arg.ctrlKey,
                arg.altKey,
                arg.shiftKey,
                arg.metaKey,
                arg.button,
                arg.relatedTarget
              );
              target.dispatchEvent (evt);
              arg.type = null;
            }
          })
      }
      object.reset ();

      return object;
    })();



  var Emulator =
    (function () {
      var state = false;
      var point = { 'x' : 0, 'y' : 0 };

      var object = {
        'range'       : 10,
        'disabled'    : false,
        
        'configure'   :
          (function (parm) { Eventer.configure (parm); }),
        
        'handleEvent' : 
          (function (event) {
            if (this.disabled)
              return;

            var touch = event.touches;
            var eType = event.type;
              
            if (state) {
              if (('touchmove' === eType) && (touch = touch[0])) {
                if ((Math.abs (touch.pageX - point.x) < this.range) ||
                    (Math.abs (touch.pageY - point.y) < this.range)) {
                      state = false;
                }
                else {
                  point.x = touch.pageX;
                  point.y = touch.pageY;
                }
              }
              else if ('touchend' === eType) {
                this.fire (event.target, point);
                event.preventDefault ();
              }
            }
            else if (('touchstart' === eType) && (1 === touch.length) && (touch = touch[0])) {
              point.x = touch.pageX;
              point.y = touch.pageY;
              state = true;
            }
          }),
        
        'fire' :
          (function fire (target, point) {
            if (3 === target.nodeType)
              target = target.parentNode;
              
            this.configure ({
              'type'          : 'click',
              'screenX'       : point.x,
              'screenY'       : point.y,
              'clientX'       : point.x,
              'clientY'       : point.y,
            });
            Eventer.fire (target);
          })
      };
      
      return object;
    })();

  //___________________

  doc.addEventListener ('touchstart', Emulator, false);
  doc.addEventListener ('touchmove', Emulator, false);
  doc.addEventListener ('touchend', Emulator, false);

  this.ClickEmulator = Emulator;
})();

</script>