OSDN Git Service

Version 0.6.87, fix SuperConstructor @ X.Class & force convert to PointerEvent @...
[pettanr/clientJs.git] / 0.6.x / js / 00_core / 06_XEventDispatcher.js
index 8e66c07..e71c638 100644 (file)
@@ -1,4 +1,10 @@
 /**\r
+ * X.EventDispatcher\r
+ * \r
+ *  1. as3 の EventDispatcher ライクなクラス。そのまま使ったり、継承したり。コールバック中にイベントを追加したら?削除したら?にも対処している。\r
+ *  2. _rawObject メンバがいる場合、addEventListener, attachEvent, on 等で生のブラウザオブジェクトにリスナを登録する。\r
+ *     window, document, HTMLElement, Image, XHR などが _rawObject\r
+ * \r
  * use X.Callback\r
  * \r
  * https://developer.mozilla.org/ja/docs/Web/API/EventTarget.addEventListener\r
  */\r
 \r
 X.Event = {\r
-       COMPLETE      : 1,\r
-       SUCCESS       : 2,\r
-       ERROR         : 3,\r
-       PROGRESS      : 4,\r
-       BEFORE_CANCEL : 5,\r
-       CANCELED      : 6,\r
-       TIMEOUT       : 7,\r
-       _LAST_EVENT   : 7\r
+       COMPLETE               :  1,\r
+       READY                  :  2,\r
+       SUCCESS                :  3,\r
+       ERROR                  :  4,\r
+       PROGRESS               :  5,\r
+       BEFORE_CANCEL          :  6,\r
+       CANCELED               :  7,\r
+       TIMEOUT                :  8,\r
+       BEFORE_KILL_INSTANCE   :  9,\r
+       KILL_INSTANCE          : 10,\r
+       KILL_INSTANCE_CANCELED : 11,\r
+       _LAST_EVENT            : 11\r
+};\r
+\r
+// ------------------------------------------------------------------------- //\r
+// ------------ local variables -------------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
+var X_EventDispatcher_once       = false,\r
+       X_EventDispatcher_needsIndex = false,\r
+       X_EventDispatcher_temp       = {},\r
+       \r
+       X_EventDispatcher_safariPreventDefault = false; //  // Safari3-\r
+\r
+if( X.UA.MacIE ){\r
+       X_EventDispatcher_temp.DOM_W3C    = true;\r
+       X_EventDispatcher_temp.EVENT_DOM0 = true;\r
+} else\r
+if( X.UA.IE4 ){ // ie4 & iemobi4\r
+       X_EventDispatcher_temp.DOM_IE4    = true;\r
+       X_EventDispatcher_temp.EVENT_DOM0 = true;\r
+} else\r
+if( document.getElementById ){\r
+       X_EventDispatcher_temp.DOM_W3C = true;\r
+       if( document.addEventListener ){\r
+               X_EventDispatcher_temp.EVENT_W3C = true;\r
+       } else\r
+       if( document.attachEvent ){\r
+               X_EventDispatcher_temp.EVENT_IE = true;\r
+       } else {\r
+               X_EventDispatcher_temp.EVENT_DOM0 = true;\r
+       };\r
+} else\r
+if( document.all ){\r
+       X_EventDispatcher_temp.DOM_IE4    = true;\r
+       X_EventDispatcher_temp.EVENT_DOM0 = true;\r
+} else\r
+if( document.layers ){\r
+       \r
+} else {\r
+       \r
 };\r
 \r
+// ------------------------------------------------------------------------- //\r
+// --- interface ----------------------------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
 \r
+/**\r
+ * イベントターゲット(widnow, document, Image, XHR 等)をラップする場合、通常は new 時に渡します。参照:コンストラクタ実体 {@link X.EventDispatcher.Constructor}\r
+ * アプリケーション独自のイベントをやり取りしたいだけ、という場合、イベントターゲットは不要です。\r
+ * @class\r
+ * @classdesc EventTarget オブジェクトをラップしたり、アプリケーションで独自に定義したイベントを発信するためのクラスです。\r
+ *     listen, unlisten, dispatch という addEventListener, removeEventListener, dispatchEvent に対応する関数を持ちます。\r
+ *     さらに listening という as3 の hasEventListener に相当する関数を持ちます。\r
+ *     イベントターゲットオブジェクト(widnow, document, HTMLElement, XHR 等)が _rawObject に設定されていた場合に、それらへ実際のイベント登録・解除も行います。\r
+ *     このイベントの登録・解除はクロスブラウザで、IE5~8 の独自イベントの差異を吸収し、DOM0 に対しても複数のコールバックを登録することができます。\r
+ *     またコールバックに対して、this コンテキストや、追加の引数を指定もできます。 this コンテキストを指定しなかった場合、EventDispatcher インスタンスがコールバックの this になります。 \r
+ * @param {object=} opt_rawObject\r
+ */\r
 X.EventDispatcher =\r
        X.Class.create(\r
                'EventDispatcher',\r
+               \r
+       /** @lends {X.EventDispatcher.prototype} */\r
                {\r
-                       _listeners    : null,\r
-                       _dispatching  : 0, // dispatch 中の unlisten で使用\r
-                       _unlistens    : null, // dispatch 中の unlisten で使用\r
-                       _needsIndex   : false, // listening で index を返す\r
-                       _reserves     : null,\r
-                       _killReserved : false,\r
+\r
+           /**\r
+            * @namespace\r
+            * @memberof X.EventDispatcher\r
+            */\r
+\r
+               /**\r
+                * イベントリスナをイベント名(string)や数値(1~,フレームワーク内で定義)をキーとするArrayで記憶します。\r
+                * Arrayには、{k:種類,x:コンテキスト(thisObject),f:関数,s:サプリメントする引数の配列} というハッシュ、または関数が蓄えられています。\r
+                * @private\r
+                * @type {Object.<(number|string), Array.<(callbackHash|function)>>}\r
+                */\r
+                       '_listeners'    : null,\r
+\r
+               /**\r
+                * _rawObject には HTMLElement, window, document, XHR といったイベントターゲットオブジェクトを設定します。\r
+                * _rawObject が設定されていると on(), off() 時に addEventListener(DOM Level2) や detachEvent(ie5~8), on~(DOM0) 等を操作します。\r
+                * _rawObject は最初の on() 前に設定しておかないと addEventListener 等が意図したように行われません。\r
+                * X.Dom.Node では非同期に HTMLElement を生成していて、要素生成以前に on, off を呼び出すことができます。これは適宜に migrateEvent, restoreEvent を呼んで解決しているためです。\r
+                * @private\r
+                * @type {Object}\r
+                */\r
+                       '_rawObject'    : null,\r
+               \r
+               /**\r
+                * _rawObject がある場合、イベントターゲットオブジェクトに対して addEventListener, attachEvent, onXX 時に実際に渡される関数。\r
+                * イベントタイプが変わっても、単一の オブジェクトを使いまわす。(但し例外は、IE8以下の XHR|MSXML に対して。)最初の listen で生成されて、最後の unlisten で削除される。\r
+                * @private\r
+                * @type {X.Callback}\r
+                */\r
+                       '_handleEvent'  : null,\r
                        \r
-                       listen : function( type, arg1, arg2, arg3 ){\r
-                               var list = this._listeners,\r
-                                       i, l, r, f;\r
-                               if( this._dispatching ){\r
-                                       if( !this._reserves ) this._reserves = [];\r
-                                       this._reserves[ this._reserves.length ] = [ type, arg1, arg2, arg3, X.EventDispatcher._once ];\r
-                                       return this;\r
-                               };\r
-                               \r
-                               if( X.Type.isArray( type ) ){\r
-                                       for( i = 0, l = type.length; i < l; ++i ){\r
-                                               this.listen( type[ i ], arg1, arg2, arg3 );\r
-                                       };\r
-                                       return this;\r
-                               };\r
-                               \r
-                               if( this.listening( type, arg1, arg2, arg3 ) ) return this;\r
+               /*\r
+                * dispatch 中に dispatch が呼ばれた際に、そのネストの深さを保存する。\r
+                * dispatch() 終了時に _dispatching が 0 の場合に、現在のインスタンスの dispatch がすべて終わったことになる。\r
+                * @private\r
+                * @type {number}\r
+                */\r
+                       '_dispatching'  : 0,     // dispatch 中の unlisten で使用\r
 \r
-                               if( !list ) list = this._listeners = {};\r
-                               if( !( list = list[ type ] ) ) list = this._listeners[ type ] = [];\r
-                               \r
-                               f = X.Callback._classifyCallbackArgs( arg1, arg2, arg3, this );\r
-                               list[ list.length ] = f;\r
-                               f.once = X.EventDispatcher._once;\r
-                               \r
-                               return this;\r
+               /*\r
+                * dispatch 中に listen が呼ばれた場合に、配列のindexがずれることを避けるため、一旦保持する。\r
+                * _dispatching が 0 のときに _reserves を使って listen() を呼び出す。\r
+                * @private\r
+                * @type {Object.<(number|string), Array.<(X.Callback|function)>>}\r
+                */\r
+                       '_reserves'     : null,\r
+               \r
+               /*\r
+                * dispatch 中に unlisten が呼ばれた場合に、配列のindexがずれることを避けるため、一旦保持する。\r
+                * _dispatching が 0 のときに _unlistens を使って unlisten() を呼び出す。\r
+                * @private\r
+                * @type {Object.<(number|string), Array.<(X.Callback|function)>>}\r
+                */\r
+                       '_unlistens'    : null,\r
+                       \r
+               /*\r
+                * dispatch 中に kill が呼ばれた場合に、X.EventDispatcher インスタンスの削除を dispatch 後にずらすために立てるフラグ。\r
+                * @private\r
+                * @type {boolean}\r
+                */\r
+                       '_killReserved' : false,\r
+                       \r
+           /**\r
+            * X.EventDispatcher のコンストラクタの実体。\r
+            * @constructs\r
+            * @this {X.EventDispatcher}\r
+            * @params {object=} opt_rawObject\r
+            */\r
+                       Constructor : function( opt_rawObject ){\r
+                               if( opt_rawObject ){\r
+                                       this._rawObject = opt_rawObject;\r
+                               };\r
                        },\r
-                       listenOnce : function( type, arg1, arg2, arg3 ){\r
-                               X.EventDispatcher._once = true;\r
-                               this.listen( type, arg1, arg2, arg3 );\r
-                               X.EventDispatcher._once = false;\r
+\r
+               /**\r
+                * 登録されたイベントリスナを呼び出す。イベントリスナの返り値(数値)を OR したものを返す。\r
+                * @this {X.EventDispatcher}\r
+                * @return {number}\r
+                * @param {(eventHash|string|number)} e\r
+                */     \r
+                       dispatch : X_EventDispatcher_dispatch,\r
+\r
+           /**\r
+            * \r
+            * @this {X.EventDispatcher}\r
+            */\r
+                       on     : X_EventDispatcher_listen,\r
+                       listen : X_EventDispatcher_listen,\r
+               \r
+               /**\r
+                * 一度 dispatch された時に自動で unlisten されるフラグを立てて listen する。\r
+                * @this {X.EventDispatcher}\r
+                * @return {X.EventDispatcher}\r
+                * @param {(string|number|Array.<(string,number)>)} type\r
+                * @param {(listener|function|Array)=} opt_arg1\r
+                * @param {(function|Array=} opt_arg2\r
+                * @param {Array=} opt_arg3\r
+                */\r
+                       listenOnce : function( type, opt_arg1, opt_arg2, opt_arg3 ){\r
+                               X_EventDispatcher_once = true;\r
+                               this.listen( type, opt_arg1, opt_arg2, opt_arg3 );\r
+                               X_EventDispatcher_once = false;\r
                                return this;\r
                        },\r
-                       unlisten : function( type, arg1, arg2, arg3 ){\r
+                       \r
+                       off      : X_EventDispatcher_unlisten,\r
+                       unlisten : X_EventDispatcher_unlisten,\r
+\r
+               /**\r
+                * イベントリスナの登録状況を真偽値で返す。戻り値が数値(index)の場合もあるが、これは内部のみで使用。\r
+                * listening() と type を省略した場合、一つでも登録があれば true を返す。\r
+                * listening( type ) と type だけを与えた場合、その type に登録があれば true を返す。\r
+                * type と イベントリスナの組み合わせが登録されているかを調べる場合は、listen 時の thisObject や args(Array) も一致させて渡す必要がある。\r
+                *  this.listen( [ 'myevent', 'yourevent' ], this, onMyEvent, args = [ 1, 'a' ] );\r
+                *  this.listening( 'myevent', this, onMyEvent, args ) === true;\r
+                * @this {X.EventDispatcher}\r
+                * @return {(number|boolean)}\r
+                * @param {(string|number)=} opt_type\r
+                * @param {(listener|function|Array)=} opt_arg1\r
+                * @param {(function|Array=} opt_arg2\r
+                * @param {Array=} opt_arg3\r
+                */                     \r
+                       listening : function( opt_type, opt_arg1, opt_arg2, opt_arg3 ){\r
                                var list = this._listeners,\r
-                                       _list, reserves, unlistens, i, f;\r
-                               if( !list ) return this;\r
-                               \r
-                               if( X.Type.isArray( type ) ){\r
-                                       for( i = type.length; i; ){\r
-                                               this.unlisten( type[ --i ], arg1, arg2, arg3 );\r
-                                       };\r
-                                       return this;\r
-                               };\r
+                                       unlistens, i, f, hash;\r
                                \r
-                               if( type === undefined ){\r
-                                       // 全て削除\r
-                                       for( type in list ){\r
-                                               _list = list[ type ];\r
-                                               for( i = _list.length; i; ){\r
-                                                       this.unlisten( type, _list[ --i ] ); // override されていることがあるので、必ず unlisten を使用\r
-                                               };\r
-                                               // this.unlisten( type ); これは無茶!\r
-                                       };\r
-                                       return this;\r
-                               } else\r
-                               if( arg1 === undefined ){\r
-                                       // 同一タイプを全て削除\r
-                                       if( _list = list[ type ] ){\r
-                                               for( i = _list.length; i; ){\r
-                                                       this.unlisten( type, _list[ --i ] ); // override されていることがあるので、必ず unlisten を使用\r
-                                               };\r
-                                       };\r
-                                       return this;\r
-                               } else\r
-                               if( reserves = this._reserves ){\r
-                                       for( i = reserves.length; i; ){\r
-                                               f = reserves[ --i ];\r
-                                               if( f[ 0 ] === type && f[ 1 ] === arg1 && f[ 2 ] === arg2 && f[ 3 ] === arg3 ){\r
-                                                       reserves.splice( i, 1 );\r
-                                                       if( !reserves.legth ) delete this._reserves;\r
-                                                       return this;\r
-                                               };\r
-                                       };\r
-                               };\r
+                               if( opt_type === undefined ) return !!list;\r
+                               if( !list || !( list = list[ opt_type ] ) ) return false;\r
+                               if( opt_arg1 === undefined ) return true;\r
                                \r
-                               this._needsIndex = true;\r
-                               i = this.listening( type, arg1, arg2, arg3 );\r
-                               delete this._needsIndex;\r
-                               if( i === false ) return this;\r
-\r
-                               f = ( _list = list[ type ] )[ i ];\r
-                               if( unlistens = this._unlistens ){\r
-                                       ( unlistens = unlistens[ type ] ) ?\r
-                                               ( unlistens[ unlistens.length ] = f ) :\r
-                                               ( this._unlistens[ type ] = [ f ] );\r
+                               if( opt_arg1.k ){\r
+                                       hash = opt_arg1;\r
                                } else {\r
-                                       delete f.once;\r
-                                       f.kill === X.Callback._kill && f.kill();\r
-                                       _list.splice( i, 1 );\r
-                                       if( !_list.length ){\r
-                                               delete this._listeners[ type ];\r
-                                               if( X.isEmptyObject( this._listeners ) ) delete this._listeners;\r
-                                       };\r
+                                       hash = X.Callback._classifyCallbackArgs( opt_arg1, opt_arg2, opt_arg3, this );\r
                                };\r
-                               return this;\r
-                       },\r
-                       listening : function( type, arg1, arg2, arg3 ){\r
-                               var list = this._listeners, unlistens, i, f, hash;\r
-                               if( type === undefined ) return !!list;\r
-                               if( !list || !( list = list[ type ] ) ) return false;\r
-                               if( arg1 === undefined ) return true;\r
                                \r
-                               if( arg1.k ){\r
-                                       hash = arg1;\r
-                               } else {\r
-                                       hash = X.Callback._classifyCallbackArgs( arg1, arg2, arg3, this );\r
-                               };\r
-                               \r
-                               if( ( unlistens = this._unlistens ) && ( unlistens = unlistens[ type ] ) ){\r
+                               if( ( unlistens = this._unlistens ) && ( unlistens = unlistens[ opt_type ] ) ){\r
                                        for( i = unlistens.length; i; ){\r
                                                f = unlistens[ --i ];\r
                                                if( f === hash || ( f.x === hash.x && f.f === hash.f && f.s === hash.s ) ) return false;\r
@@ -151,115 +237,455 @@ X.EventDispatcher =
                                };\r
                                for( i = list.length; i; ){\r
                                        f = list[ --i ];\r
-                                       if( f === hash || ( f.x === hash.x && f.f === hash.f && f.s === hash.s ) ) return this._needsIndex ? i : true;\r
+                                       if( f === hash || ( f.x === hash.x && f.f === hash.f && f.s === hash.s ) ) return X_EventDispatcher_needsIndex ? i : true;\r
                                };\r
                                return false;\r
                        },\r
-                       /*\r
-                        * dispatch 中に dispatch が呼ばれるケースがあるため、\r
-                        * _dispatching では その深さを保存する\r
-                        * _dispatching が 0 のときに unlistens を削除する\r
-                        *\r
-                        */\r
-                       dispatch : function( e ){\r
-                               // dispatch 中の listen は?\r
-                               var list  = this._listeners,\r
-                                       ret   = X.Callback.NONE,\r
-                                       type  = e.type,\r
-                                       unlistens, i, l, f, r, sysOnly;\r
-                               \r
-                               if( !list ) return ret;\r
-                               \r
-                               // 数値, 文字が渡された場合\r
-                               if( !type ) e = { type : type = e };\r
-                               e.target = e.target || this;\r
-                               \r
-                               if( !( list = list[ type ] ) ) return ret;\r
-                               \r
-                               ++this._dispatching;\r
+\r
+               /**\r
+                * delay(ミリ秒)後にイベントを dispatch する。戻り値は uid = X.Timer.add() のタイマーID(数値)。X.Timer.remove(uid) でタイマーを解除して dispatch を中止できる。\r
+                * @this {X.EventDispatcher}\r
+                * @return {number}\r
+                * @param {number=} delay\r
+                * @param {(eventHash|string|number)=} e\r
+                */                     \r
+                       asyncDispatch : function( delay, e ){\r
+                               return X.Timer.add( delay, 1, this, this.dispatch, [ e ] );\r
+                       }\r
+               }\r
+       );\r
+\r
+// ------------------------------------------------------------------------- //\r
+// --- implements ---------------------------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
+\r
+/**\r
+ * 登録されたイベントリスナを呼び出す。イベントリスナの返り値(数値)を OR したものを返す。イベントハッシュでなく、string|number を渡すと内部でイベントハッシュを作る\r
+ * @this {X.EventDispatcher}\r
+ * @return {number} X.Callback で定義された数値\r
+ * @param {(eventHash|string|number)} e\r
+ */\r
+function X_EventDispatcher_dispatch( e ){\r
+       var list  = this[ '_listeners' ],\r
+               ret   = X.Callback.NONE,\r
+               type  = e[ 'type' ],\r
+               unlistens, i, l, f, r, sysOnly;\r
+       \r
+       if( !list || !( list = list[ type || e ] ) ) return ret;\r
+       \r
+       // 数値, 文字が渡された場合\r
+       if( !type ){\r
+               e = { type : type = e };\r
+       };\r
+       e.target        = e.target || this;\r
+       e.currentTarget = e.currentTarget || this;\r
+       \r
+       ++this._dispatching;\r
+       \r
+       // todo:\r
+       // type も保存\r
+       this._unlistens = this._unlistens || {};\r
+       unlistens = this._unlistens[ type ];\r
+       \r
+       for( i = 0; i < list.length; ++i ){\r
+               f = list[ i ];\r
+               if( !unlistens ){\r
+                       unlistens = this._unlistens[ type ];\r
+               };\r
+               if( unlistens && unlistens.indexOf( f ) !== -1 ) continue;\r
+               \r
+               r = X.Callback.NONE;\r
+               if( f.k ){\r
+                       f.a = [ e ];\r
+                       r = X.Callback._proxyCallback( f );\r
+               } else {\r
+                       r = f.call( this, e );\r
+               };\r
+               \r
+               if( f.once || r & X.Callback.UN_LISTEN ){\r
+                       // dispatch 中に unlisten が作られることがある\r
+                       if( !unlistens ){\r
+                               unlistens = this._unlistens || ( this._unlistens = {} );\r
+                               unlistens = unlistens[ type ] || ( unlistens[ type ] = [] );\r
+                       };\r
+                       unlistens.indexOf( f ) === -1 && ( unlistens[ unlistens.length ] = f );\r
+               };\r
+\r
+               if( r & X.Callback.STOP_NOW ){\r
+                       sysOnly = true;\r
+               };\r
+               ret |= X.Type.isFinite( r ) ? r : 0;\r
+       };\r
+       \r
+       if( ( --this._dispatching ) === 0 ){\r
+               // dispatch 中に unlisten された要素の削除\r
+               unlistens = this._unlistens;\r
+               delete this._dispatching;\r
+               delete this._unlistens;                                 \r
+               \r
+               for( type in unlistens ){\r
+                       list = unlistens[ type ];\r
+                       for( i = list.length; i; ){\r
+                               this.unlisten( type, list[ --i ] );\r
+                       };\r
+                       list.length = 0;\r
+                       delete unlistens[ type ];\r
+               };\r
+               \r
+               if( this._killReserved ){\r
+                       this.kill();\r
+               } else\r
+               if( list = this._reserves ){\r
+                       for( i = 0, l = list.length; i < l; ++i ){\r
+                               f = list[ i ];\r
+                               X_EventDispatcher_once = f[ 4 ];\r
+                               this.listen( f[ 0 ], f[ 1 ], f[ 2 ], f[ 3 ] );\r
+                               X_EventDispatcher_once = false;\r
+                               f.length = 0;\r
+                       };\r
+                       list.length = 0;\r
+                       delete this._reserves;\r
+               };\r
+       };\r
+       \r
+       return ret;\r
+};\r
+\r
+/**\r
+ * \r
+ * @this {X.EventDispatcher}\r
+ * @return {X.EventDispatcher}\r
+ * @param {(string|number|Array.<(string,number)>)} type\r
+ * @param {(listener|function|Array)=} opt_arg1\r
+ * @param {(function|Array=} opt_arg2\r
+ * @param {Array=} opt_arg3\r
+ */\r
+function X_EventDispatcher_listen( type, opt_arg1, opt_arg2, opt_arg3 ){\r
+       var list = this._listeners,\r
+               i, raw, f;\r
+       \r
+       if( this._dispatching ){\r
+               if( !this._reserves ) this._reserves = [];\r
+               this._reserves[ this._reserves.length ] = [ type, opt_arg1, opt_arg2, opt_arg3, X_EventDispatcher_once ];\r
+               return this;\r
+       };\r
+       \r
+       if( X.Type.isArray( type ) ){\r
+               for( i = type.length; i; ){\r
+                       this.listen( type[ --i ], opt_arg1, opt_arg2, opt_arg3 );\r
+               };\r
+               return this;\r
+       };\r
+       \r
+       raw = this._rawObject || this._ie4getRawNode && this._ie4getRawNode();\r
+       raw && ( !list || !list[ type ] ) && X.Type.isString( type ) && X_EventDispatcher_actualAddEvent( this, type, raw );\r
+       \r
+       if( this.listening( type, opt_arg1, opt_arg2, opt_arg3 ) ) return this;\r
+\r
+       if( !list ) list = this._listeners = {};\r
+       if( !( list = list[ type ] ) ) list = this._listeners[ type ] = [];\r
+       \r
+       f = X.Callback._classifyCallbackArgs( opt_arg1, opt_arg2, opt_arg3, this );\r
+       list[ list.length ] = f;\r
+       f.once = X_EventDispatcher_once;\r
+       \r
+       return this;\r
+};\r
+\r
+/**\r
+ * \r
+ * @this {X.EventDispatcher}\r
+ * @return {X.EventDispatcher}\r
+ * @param {(string|number|Array.<(string,number)>)=} opt_type\r
+ * @param {(listener|function|Array)=} opt_arg1\r
+ * @param {(function|Array=} opt_arg2\r
+ * @param {Array=} opt_arg3\r
+ */\r
+function X_EventDispatcher_unlisten( opt_type, opt_arg1, opt_arg2, opt_arg3 ){\r
+       var list = this._listeners,\r
+               _list, reserves, unlistens, i, f, raw;\r
+       if( !list ) return this;\r
+       \r
+       if( X.Type.isArray( opt_type ) ){\r
+               for( i = opt_type.length; i; ){\r
+                       this.unlisten( opt_type[ --i ], opt_arg1, opt_arg2, opt_arg3 );\r
+               };\r
+               return this;\r
+       };\r
+       \r
+       if( opt_type === undefined ){\r
+               // 全て削除\r
+               for( opt_type in list ){\r
+                       _list = list[ opt_type ];\r
+                       for( i = _list.length; i; ){\r
+                               this.unlisten( opt_type, _list[ --i ] ); // override されていることがあるので、必ず unlisten を使用\r
+                       };\r
+                       // this.unlisten( opt_type ); これは無茶!\r
+               };\r
+               return this;\r
+       } else\r
+       if( opt_arg1 === undefined ){\r
+               // 同一タイプを全て削除\r
+               if( _list = list[ opt_type ] ){\r
+                       for( i = _list.length; i; ){\r
+                               this.unlisten( opt_type, _list[ --i ] ); // override されていることがあるので、必ず unlisten を使用\r
+                       };\r
+               };\r
+               return this;\r
+       } else\r
+       if( reserves = this._reserves ){\r
+               for( i = reserves.length; i; ){\r
+                       f = reserves[ --i ];\r
+                       if( f[ 0 ] === opt_type && f[ 1 ] === opt_arg1 && f[ 2 ] === opt_arg2 && f[ 3 ] === opt_arg3 ){\r
+                               reserves.splice( i, 1 );\r
+                               if( !reserves.legth ) delete this._reserves;\r
+                               return this;\r
+                       };\r
+               };\r
+       };\r
+       \r
+       X_EventDispatcher_needsIndex = true;\r
+       i = this.listening( opt_type, opt_arg1, opt_arg2, opt_arg3 );\r
+       X_EventDispatcher_needsIndex = false;\r
+       if( i === false ) return this;\r
+\r
+       f = ( _list = list[ opt_type ] )[ i ];\r
+       if( unlistens = this._unlistens ){\r
+               ( unlistens = unlistens[ opt_type ] ) ?\r
+                       ( unlistens[ unlistens.length ] = f ) :\r
+                       ( this._unlistens[ opt_type ] = [ f ] );\r
+       } else {\r
+               delete f.once;\r
+               // f.kill === X.Callback._kill && f.kill();\r
+               _list.splice( i, 1 );\r
+               if( !_list.length ){\r
+                       delete this._listeners[ opt_type ];\r
+                       raw = this._rawObject || this._ie4getRawNode && this._ie4getRawNode();\r
+                       raw && X.Type.isString( opt_type ) && X_EventDispatcher_actualRemoveEvent( this, opt_type, raw );\r
+                       if( X.isEmptyObject( this._listeners ) ) delete this._listeners;\r
+               };\r
+       };\r
+       return this;\r
+};\r
+\r
+\r
+var X_EventDispatcher_actualAddEvent =\r
+       // Days on the Moon DOM Events とブラウザの実装 \r
+       // http://nanto.asablo.jp/blog/2007/03/23/1339502\r
+       // Safari 2 では関数オブジェクトしか EventListener として使えませんが、Safari のナイトリービルドでは handleEvent メソッドを持つオブジェクトも EventListener として使えるようです。\r
+       X_EventDispatcher_temp.EVENT_W3C /* && ( X.UA.WebKit < 525.13 || X.UA.Opera7 || X.UA.NetFront < 4 ) */ ? // Safari3-\r
+               (function( that, type, raw ){\r
+                       var i;\r
+                       \r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       \r
+                       if( X.Type.isArray( type ) ){\r
+                               for( i = type.length; i; ){\r
+                                       X_EventDispatcher_actualAddEvent( that, type[ --i ], raw );\r
+                               };\r
+                       } else {\r
+                               that._handleEvent || ( that._handleEvent = X.Callback.create( that, X_EventDispatcher_actualHandleEvent ) );\r
                                \r
-                               // todo:\r
-                               // type も保存\r
-                               this._unlistens = this._unlistens || {};\r
-                               unlistens = this._unlistens[ type ];\r
+                               if( raw.addEventListener ){\r
+                                       raw.addEventListener( type, that._handleEvent, false );\r
+                               } else {\r
+                                       // Safari は Image, Opera7 は window\r
+                                       raw[ 'on' + type ] = that._handleEvent;\r
+                               };                              \r
+                       };\r
+               }) :\r
+       X_EventDispatcher_temp.EVENT_IE ?\r
+               (function( that, type, raw ){\r
+                       //if( type === 'load' && that._tag && X.Dom.Event._LOAD_FIX_TAGS[ that._tag ] ){\r
+                       //      type = 'readystatechange';\r
+                       //};\r
+                       if( that._isXHR ){\r
+                               // ie8- の XHR は window.event が更新されないため, eventType 毎に callback を指定する\r
+                               raw[ 'on' + type ] = X.Callback.create( that, X_EventDispatcher_dispatch, [ type ] );\r
+                       } else {\r
+                               type = X.Dom.Event.Rename[ type ] || type;\r
                                \r
-                               for( i = 0; i < list.length; ++i ){\r
-                                       f = list[ i ];\r
-                                       if( !unlistens ){\r
-                                               unlistens = this._unlistens[ type ];\r
+                               if( X.Type.isArray( type ) ){\r
+                                       for( i = type.length; i; ){\r
+                                               X_EventDispatcher_actualAddEvent( that, type[ --i ], raw );\r
                                        };\r
-                                       if( unlistens && unlistens.indexOf( f ) !== -1 ) continue;\r
+                               } else{\r
+                                       that._handleEvent || ( that._handleEvent = X.Callback.create( that, X_EventDispatcher_actualHandleEvent ) );\r
                                        \r
-                                       if( f.k ){\r
-                                               f.a = [ e ];\r
-                                               r = X.Callback._proxyCallback( f );\r
+                                       if( raw.attachEvent ){\r
+                                               raw.attachEvent( 'on' + type, that._handleEvent );\r
                                        } else {\r
-                                               r = f.call( this, e );\r
-                                       };\r
-                                       \r
-                                       if( f.once || r & X.Callback.UN_LISTEN ){\r
-                                               // dispatch 中に unlisten が作られることがある\r
-                                               if( !unlistens ){\r
-                                                       unlistens = this._unlistens || ( this._unlistens = {} );\r
-                                                       unlistens = unlistens[ type ] || ( unlistens[ type ] = [] );\r
-                                               };\r
-                                               unlistens.indexOf( f ) === -1 && ( unlistens[ unlistens.length ] = f );\r
-                                       };\r
+                                               raw[ 'on' + type ] = that._handleEvent;\r
+                                       };                              \r
+                               };                              \r
+                       };\r
+               }) :\r
+               (function( that, type, raw ){\r
+                       var i;\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       \r
+                       if( X.Type.isArray( type ) ){\r
+                               for( i = type.length; i; ){\r
+                                       X_EventDispatcher_actualAddEvent( that, type[ --i ], raw );\r
+                               };\r
+                       } else {\r
+                               raw[ 'on' + type ] = that._handleEvent || ( that._handleEvent = X.Callback.create( that, X_EventDispatcher_actualHandleEvent ) );\r
+                       };\r
+               });\r
 \r
-                                       if( r & X.Callback.STOP_NOW ){\r
-                                               sysOnly = true;\r
-                                       };\r
-                                       ret |= r;\r
+\r
+var X_EventDispatcher_actualRemoveEvent =\r
+       X_EventDispatcher_temp.EVENT_W3C /*&& ( X.UA.WebKit < 525.13 || X.UA.Opera7 || X.UA.NetFront < 4 )*/ ? // Safari3-\r
+               (function( that, type, raw, opt_skip ){\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       \r
+                       if( X.Type.isArray( type ) ){\r
+                               for( i = type.length; i; ){\r
+                                       X_EventDispatcher_actualRemoveEvent( that, type[ --i ], raw, !!i );\r
+                               };\r
+                       } else {\r
+                               if( raw.addEventListener ){ // Image\r
+                                       raw.removeEventListener( type, that._handleEvent, false );\r
+                               } else {\r
+                                       raw[ 'on' + type ] = null;\r
                                };\r
+                               if( !opt_skip && !that._listeners ){\r
+                                       X.Callback._correct( that._handleEvent );\r
+                                       delete that._handleEvent;\r
+                               };              \r
+                       };\r
+               }) :\r
+       X_EventDispatcher_temp.EVENT_IE ?\r
+               (function( that, type, raw, opt_skip ){\r
+                       var i;\r
+                       //if( type === 'load' && that._tag && X.Dom.Event._LOAD_FIX_TAGS[ that._tag ] ){\r
+                       //      type = 'readystatechange';\r
+                       //};\r
+                       if( that._isXHR ){\r
+                               X.Callback._correct( raw[ 'on' + type ] );\r
+                               raw[ 'on' + type ] = X.emptyFunction;\r
+                               raw[ 'on' + type ] = '';\r
+                       } else {\r
+                               type = X.Dom.Event.Rename[ type ] || type;\r
                                \r
-                               if( ( --this._dispatching ) === 0 ){\r
-                                       // dispatch 中に unlisten された要素の削除\r
-                                       unlistens = this._unlistens;\r
-                                       delete this._dispatching;\r
-                                       delete this._unlistens;                                 \r
-                                       \r
-                                       for( type in unlistens ){\r
-                                               list = unlistens[ type ];\r
-                                               for( i = list.length; i; ){\r
-                                                       this.unlisten( type, list[ --i ] );\r
-                                               };\r
-                                               list.length = 0;\r
-                                               delete unlistens[ type ];\r
+                               if( X.Type.isArray( type ) ){\r
+                                       for( i = type.length; i; ){\r
+                                               X_EventDispatcher_actualRemoveEvent( that, type[ --i ], raw, !!i );\r
+                                       };\r
+                               } else {\r
+                                       if( raw.attachEvent ){\r
+                                               raw.detachEvent( 'on' + type, that._handleEvent );\r
+                                       } else {\r
+                                               raw[ 'on' + type ] = X.emptyFunction;\r
+                                               raw[ 'on' + type ] = '';\r
                                        };\r
                                        \r
-                                       if( this._killReserved ){\r
-                                               this.kill();\r
-                                       } else\r
-                                       if( list = this._reserves ){\r
-                                               for( i = 0, l = list.length; i < l; ++i ){\r
-                                                       f = list[ i ];\r
-                                                       X.EventDispatcher._once = f[ 4 ];\r
-                                                       this.listen( f[ 0 ], f[ 1 ], f[ 2 ], f[ 3 ] );\r
-                                                       X.EventDispatcher._once = false;\r
-                                                       f.length = 0;\r
-                                               };\r
-                                               list.length = 0;\r
-                                               delete this._reserves;\r
+                                       if( !opt_skip && !that._listeners ){\r
+                                               X.Callback._correct( that._handleEvent );\r
+                                               delete that._handleEvent;\r
                                        };\r
                                };\r
-                               \r
-                               return ret;\r
-                       },\r
+                       };\r
+               }) :\r
+               (function( that, type, raw, opt_skip ){\r
+                       var i;\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       if( X.Type.isArray( type ) ){\r
+                               for( i = type.length; i; ){\r
+                                       X_EventDispatcher_actualRemoveEvent( that, type[ --i ], raw, !!i );\r
+                               };\r
+                       } else {\r
+                               raw[ 'on' + type ] = X.emptyFunction;\r
+                               raw[ 'on' + type ] = '';\r
+                               if( !opt_skip && !that._listeners ){\r
+                                       X.Callback._correct( that._handleEvent );\r
+                                       delete that._handleEvent;\r
+                               };\r
+                       };\r
+               });\r
+\r
+\r
+// handleEvent を拡張可能にするために、クロージャに移動した\r
+// Is this in regard to the Safari 1.x preventDefault bug on click/dblclick?\r
+// https://groups.google.com/forum/#!msg/comp.lang.javascript/uYEuCHjHxnw/yKoHtZJPa1QJ\r
+var X_EventDispatcher_actualHandleEvent =\r
+       X.UA.IE4 || X_EventDispatcher_temp.EVENT_IE ? // ie45678 EVENT_IE & EVENT_DOM0 for ie4\r
+               (function(){\r
+                       var ret;\r
+                       \r
+                       //if( event.type === 'readystatechange' && this._tag && X.Dom.Event._LOAD_FIX_TAGS[ this._tag ] ){\r
+                               //type = 'readystatechange';\r
+                       //};\r
+                       \r
+                       ret = this.dispatch( new X.Dom.Event( event, this, this._rawObject ) );\r
+\r
+                       if( ret & X.Callback.STOP_PROPAGATION ){\r
+                               event.cancelBubble = true;\r
+                       };\r
+                       if( ret & X.Callback.PREVENT_DEFAULT ){\r
+                               this._tag === 'A' && this._rawObject.blur();\r
+                               return event.returnValue = false;\r
+                       };\r
+               }) :\r
+       //X.Dom.EVENT_W3C & EVENT_DOM0\r
+               (function( e ){\r
+                       var ev  = new X.Dom.Event( e, this ),\r
+                               ret = X.Callback.NONE,\r
+                               i, l;\r
                        \r
-                       onKill : function(){\r
-                               if( this._dispatching ){\r
-                                       this._killReserved = true;\r
-                                       return false;\r
+                       // touch event -> pointer\r
+                       if( X.Type.isArray( ev ) ){\r
+                               if( ev.length === 0 ){\r
+                                       // TouchEvent の後に発生した MouseEvent のキャンセル\r
+                                       ret = X.Callback.STOP_PROPAGATION | X.Callback.PREVENT_DEFAULT;\r
+                               } else {\r
+                                       for( i = 0, l = ev.length; i < l; ++i ){\r
+                                               ret |= this.dispatch( ev[ i ] ) || 0;\r
+                                       };                              \r
                                };\r
-                               this._listeners && this.unlisten();\r
-                       },\r
+                       } else {\r
+                               ret = this.dispatch( ev );\r
+                       };\r
                        \r
-                       asyncDispatch : function( delay, e ){\r
-                               return X.Timer.add( delay, 1, this, this.dispatch, [ e ] );\r
-                       }\r
-               }\r
-       );\r
+                       if( ret & X.Callback.STOP_PROPAGATION ){\r
+                               e.stopPropagation();\r
+                       };\r
+                       if( ret & X.Callback.PREVENT_DEFAULT ){\r
+                               this._tag === 'A' && this._rawObject.blur();\r
+                               e.preventDefault();\r
+                               if( X.UA.WebKit < 525.13 ){ // Safari3-\r
+                                       if( e.type === 'click' || e.type === 'dbclick' ){\r
+                                               X_EventDispatcher_safariPreventDefault = true;\r
+                                       };\r
+                               };\r
+                               return false;\r
+                       };\r
+               });\r
+\r
+if( X.UA.WebKit < 525.13 ){ // Safari3-\r
+       document.documentElement.onclick =\r
+       document.documentElement.ondbclick = function( e ){\r
+                       if( X_EventDispatcher_safariPreventDefault ){\r
+                               X_EventDispatcher_safariPreventDefault = false;\r
+                               e.preventDefault();\r
+                               return false;\r
+                       };\r
+               };\r
+};\r
+\r
+// イベントの退避、dom が画面から抜かれる場合に実施しておく\r
+// 退避したイベントの復帰\r
+function X_EventDispatcher_toggleAllEvents( that, add ){\r
+       var hash = that._listeners,\r
+               raw  = that._rawObject || that._ie4getRawNode && that._ie4getRawNode(),\r
+               f    = add ? X_EventDispatcher_actualAddEvent : X_EventDispatcher_actualRemoveEvent,\r
+               type;\r
+       if( !hash || !raw ) return;\r
+       for( type in hash ){\r
+               // 数字イベントの除外\r
+               '' + parseFloat( type ) !== type && f( that, type, raw );\r
+       };\r
+};\r
 \r
-X.EventDispatcher._once = false;\r
 \r
 console.log( 'X.Core.EventDispatcher' );\r