OSDN Git Service

Version 0.6.73, move ._rawNode from X.Dom.Event to X.EventDispatcher.
[pettanr/clientJs.git] / 0.6.x / js / 00_core / 06_XEventDispatcher.js
index 8e66c07..b1134d7 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
@@ -19,118 +25,118 @@ X.Event = {
        BEFORE_CANCEL : 5,\r
        CANCELED      : 6,\r
        TIMEOUT       : 7,\r
+       // INSTANCE_KILLED\r
+       // KILL_INSTANCE_CANCELED\r
        _LAST_EVENT   : 7\r
 };\r
 \r
+// --- define / local variables ----------------------------\r
+var x_eventdispatcher_once       = false,\r
+       x_eventdispatcher_needsIndex = false,\r
+       x_eventdispatcher_temp       = {};\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
+ * イベントターゲットをラップする場合(widnow, document, Image, XHR 等)、通常は new 時に渡します。参照:コンストラクタ実体 {@link X.EventDispatcher.Constructor}\r
+ * アプリケーション独自のイベントをやり取りしたいだけ、という場合、イベントターゲットは不要です。\r
+ * @constructor\r
+ * @classdesc EventTarget オブジェクトをラップしたり、アプリケーションで独自に定義したイベントを発信するためのクラスです。\r
+ * listen, unlisten, dispatch という addEventListener, removeEventListener, dispatchEvent に対応する関数を持ちます。さらに listening という as3 の hasEventListener に相当する関数を持ちます。\r
+ * as3 の EventDispatcher に相当する機能に加え、イベントターゲットオブジェクト(widnow, document, HTMLElement, XHR 等)が設定されていた場合に、それらへ実際のイベント登録・解除も行います。\r
+ * このイベントの登録・解除はクロスブラウザ対策が施されていて、IE5~8の独自イベントの差異を吸収し、DOM0 に対しても複数のイベントリスナを登録します。\r
+ * さらに、コールバックに対して、this コンテキストや、追加の引数を指定できます。 this コンテキストを指定しなかった場合、EventDispatcher インスタンスがコールバック関数の this になります。 \r
+ * @param {object=} opt_argument \r
+ */\r
 X.EventDispatcher =\r
        X.Class.create(\r
                'EventDispatcher',\r
                {\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.<(callbackDef|function)>>}\r
+                */\r
                        _listeners    : null,\r
-                       _dispatching  : 0, // dispatch 中の unlisten で使用\r
-                       _unlistens    : null, // dispatch 中の unlisten で使用\r
-                       _needsIndex   : false, // listening で index を返す\r
-                       _reserves     : 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
+                       _rawNode      : null,\r
+                       _handleEvent  : null,\r
+                       \r
+                       _dispatching  : 0,     // dispatch 中の unlisten で使用\r
+                       _unlistens    : null,  // dispatch 中の unlisten で使用\r
+                       _reserves     : null,  // dispatch中に unlisten されたイベントリスナ\r
                        _killReserved : false,\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
+            * X.EventDispatcher のコンストラクタの実体。\r
+            * @private\r
+            * @this {X.EventDispatcher}\r
+            */\r
+                       Constructor : function( rawObject ){\r
+                               if( rawObject ){\r
+                                       this._rawNode = rawObject;\r
                                };\r
-                               \r
-                               if( this.listening( type, arg1, arg2, 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( arg1, arg2, 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
+            */\r
+                       on     : x_eventdispatcher_on,\r
+                       listen : x_eventdispatcher_on,\r
+                       \r
                        listenOnce : function( type, arg1, arg2, arg3 ){\r
-                               X.EventDispatcher._once = true;\r
+                               x_eventdispatcher_once = true;\r
                                this.listen( type, arg1, arg2, arg3 );\r
-                               X.EventDispatcher._once = false;\r
-                               return this;\r
-                       },\r
-                       unlisten : function( type, arg1, arg2, 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
-                               \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
-                               \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
-                               } 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
-                               };\r
+                               x_eventdispatcher_once = false;\r
                                return this;\r
                        },\r
+                       \r
+                       off      : x_eventdispatcher_off,\r
+                       unlisten : x_eventdispatcher_off,\r
+                       \r
                        listening : function( type, arg1, arg2, arg3 ){\r
                                var list = this._listeners, unlistens, i, f, hash;\r
                                if( type === undefined ) return !!list;\r
@@ -151,7 +157,7 @@ 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
@@ -233,9 +239,9 @@ X.EventDispatcher =
                                        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
+                                                       x_eventdispatcher_once = f[ 4 ];\r
                                                        this.listen( f[ 0 ], f[ 1 ], f[ 2 ], f[ 3 ] );\r
-                                                       X.EventDispatcher._once = false;\r
+                                                       x_eventdispatcher_once = false;\r
                                                        f.length = 0;\r
                                                };\r
                                                list.length = 0;\r
@@ -260,6 +266,202 @@ X.EventDispatcher =
                }\r
        );\r
 \r
-X.EventDispatcher._once = false;\r
+\r
+// --- implements ------------------------------------------\r
+function x_eventdispatcher_on( type, arg1, arg2, arg3 ){\r
+       var list = this._listeners,\r
+               i, r, f;\r
+       \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 = type.length; i; ){\r
+                       this.listen( type[ --i ], arg1, arg2, arg3 );\r
+               };\r
+               return this;\r
+       };\r
+       \r
+       ( !list || !list[ type ] ) && X.Type.isString( type ) && x_eventdispatcher_actualAddEvent( this, type );\r
+       \r
+       if( this.listening( type, arg1, arg2, 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( arg1, arg2, arg3, this );\r
+       list[ list.length ] = f;\r
+       f.once = x_eventdispatcher_once;\r
+       \r
+       return this;\r
+};\r
+\r
+function x_eventdispatcher_off( type, arg1, arg2, 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
+       \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
+       \r
+       x_eventdispatcher_needsIndex = true;\r
+       i = this.listening( type, arg1, arg2, arg3 );\r
+       x_eventdispatcher_needsIndex = false;\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
+       } 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
+                       X.Type.isString( type ) && x_eventdispatcher_actualRemoveEvent( this, type );\r
+                       if( X.isEmptyObject( this._listeners ) ) delete this._listeners;\r
+               };\r
+       };\r
+       return this;\r
+};\r
+\r
+\r
+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 ){\r
+                       var raw = that._rawNode;\r
+                       if( !raw ) return;\r
+                       that._handleEvent = that._handleEvent || X.Callback.create( that );\r
+                       type = X.Dom.Event.Rename[ type ] || 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
+       x_eventdispatcher_temp.EVENT_W3C ?\r
+               (function( that, type ){\r
+                       that._rawNode && that._rawNode.addEventListener( X.Dom.Event.Rename[ type ] || type, that, false );\r
+               }) :\r
+       x_eventdispatcher_temp.EVENT_IE ?\r
+               (function( that, type ){\r
+                       var raw = that._rawNode;\r
+                       if( !raw ) return;\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       //if( type === 'load' && that._tag && X.Dom.Event._LOAD_FIX_TAGS[ that._tag ] ){\r
+                       //      type = 'readystatechange';\r
+                       //};\r
+                       that._handleEvent = that._handleEvent || X.Callback.create( that );\r
+                       if( raw.attachEvent ){\r
+                               raw.attachEvent( 'on' + type, that._handleEvent );\r
+                       } else {\r
+                               raw[ 'on' + type ] = that._handleEvent;\r
+                       };\r
+               }) :\r
+               (function( that, type ){\r
+                       var raw = that._rawNode || ( that._ie4getRawNode && that._ie4getRawNode() );\r
+                       if( !raw ) return;\r
+                       raw[ 'on' + ( X.Dom.Event.Rename[ type ] || type ) ] = that._handleEvent = that._handleEvent || X.Callback.create( that );\r
+               });\r
+\r
+\r
+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 ){\r
+                       var raw = that._rawNode;\r
+                       if( !raw ) return;\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       \r
+                       if( raw.addEventListener ){ // Image\r
+                               raw.removeEventListener( type, that._handleEvent, false );\r
+                       } else {\r
+                               raw[ 'on' + type ] = null;\r
+                       };\r
+                       if( !that._listeners ){\r
+                               X.Callback._correct( that._handleEvent );\r
+                               delete that._handleEvent;\r
+                       };\r
+               }) :\r
+       x_eventdispatcher_temp.EVENT_W3C ?\r
+               (function( that, type ){\r
+                       var raw = that._rawNode;\r
+                       if( !raw ) return;\r
+                       raw.removeEventListener( X.Dom.Event.Rename[ type ] || type, that, false );\r
+               }) :\r
+       x_eventdispatcher_temp.EVENT_IE ?\r
+               (function( that, type ){\r
+                       var raw = that._rawNode;\r
+                       if( !raw ) return;\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       //if( type === 'load' && that._tag && X.Dom.Event._LOAD_FIX_TAGS[ that._tag ] ){\r
+                       //      type = 'readystatechange';\r
+                       //};\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
+                       if( !that._listeners ){\r
+                               X.Callback._correct( that._handleEvent );\r
+                               delete that._handleEvent;\r
+                       };\r
+               }) :\r
+               (function( that, type ){\r
+                       var raw = that._rawNode || ( that._ie4getRawNode && that._ie4getRawNode() );\r
+                       if( !raw ) return;\r
+                       type = X.Dom.Event.Rename[ type ] || type;\r
+                       raw[ 'on' + type ] = X.emptyFunction;\r
+                       raw[ 'on' + type ] = '';\r
+                       if( !that._listeners ){\r
+                               X.Callback._correct( that._handleEvent );\r
+                               delete that._handleEvent;\r
+                       };\r
+               });\r
+\r
 \r
 console.log( 'X.Core.EventDispatcher' );\r