OSDN Git Service

25c2d11477034a160a6fcb231065e418636740b8
[pettanr/clientJs.git] / 0.6.x / js / 01_core / 13_XEventDispatcher.js
1 /**\r
2  * \r
3  */\r
4 \r
5 /**\r
6  * X.EventDispatcher\r
7  * \r
8  *  1. as3 の EventDispatcher ライクなクラス。そのまま使ったり、継承したり。コールバック中にイベントを追加したら?削除したら?にも対処している。\r
9  *  2. _rawObject メンバがいる場合、addEventListener, attachEvent, on 等で生のブラウザオブジェクトにリスナを登録する。\r
10  *     window, document, HTMLElement, Image, XHR などが _rawObject\r
11  * \r
12  * use X.Callback\r
13  * \r
14  * https://developer.mozilla.org/ja/docs/Web/API/EventTarget.addEventListener\r
15  * イベント発送中のリスナーの追加\r
16  * EventListener がイベント処理中に EventTarget に追加された場合、それが現在のアクションによって実行されることはありませんが、浮上フェーズのように、後の段階のイベントフローで実行されるかもしれません。\r
17  * \r
18  * https://developer.mozilla.org/ja/docs/Web/API/EventTarget.removeEventListener\r
19  * イベントリスナーが イベントを処理中であるイベントターゲットから削除された場合、現在のアクションによってそのイベントリスナーが実行されることはありません。\r
20  * イベントリスナーは、決して削除された後に実行されることはありません。\r
21  * イベントターゲット上にある現在のどのイベントリスナーも指定していない引数付きの removeEventListener は、何の効果もありません。\r
22  */\r
23 \r
24 // ------------------------------------------------------------------------- //\r
25 // ------------ local variables -------------------------------------------- //\r
26 // ------------------------------------------------------------------------- //\r
27 var X_EventDispatcher_once       = false,\r
28         X_EventDispatcher_lock       = false,\r
29         X_EventDispatcher_unlock     = false,\r
30         X_EventDispatcher_needsIndex = false,\r
31         \r
32         X_EventDispatcher_safariPreventDefault = false; // Safari3-\r
33 \r
34 // ------------------------------------------------------------------------- //\r
35 // --- interface ----------------------------------------------------------- //\r
36 // ------------------------------------------------------------------------- //\r
37 \r
38 /**\r
39  * <p>イベントターゲット(widnow, document, Image, XHR, Silverlight 等)をラップする場合、通常は new 時に渡します。\r
40  * <p>参照:コンストラクタ実体 {@link X.EventDispatcher.Constructor}\r
41  * <p>アプリケーション独自のイベントをやり取りしたい、という場合、イベントターゲットは不要です。\r
42  * \r
43  * @class\r
44  * @classdesc EventTarget オブジェクトをラップしたり、アプリケーションで独自に定義したイベントを発信するためのクラスです。\r
45  *     <p>listen, unlisten, dispatch という addEventListener, removeEventListener, dispatchEvent に対応する関数を持ちます。\r
46  *     <p>また listening という ActionScript3 の hasEventListener に相当する関数を持ちます。\r
47  *     <p>イベントターゲットオブジェクト(widnow, document, HTMLElement, XHR 等)が this._rawObject に設定されていた場合に、それらへ実際のイベント登録・解除も行います。\r
48  *     <p>このイベントの登録・解除はクロスブラウザで、IE5~8 の独自イベントの差異を吸収し、DOM0 に対しても複数のコールバックを登録することができます。\r
49  *     <p>またコールバックに対して、this コンテキストや、追加の引数を指定もできます。 this コンテキストを指定しなかった場合、EventDispatcher インスタンスがコールバックの this になります。 \r
50  *     <p>unlisten() は、引数を指定しなかった場合、全てのイベントを解除します。ただし、systemListen 経由で登録されたハンドラは解除されません。\r
51  *     systemListen, systemUnlisten は、ライブラリ内のコードからしかアクセスできません。\r
52  * \r
53  * @augments X_Class_CommonProps\r
54  * \r
55  * @param {object=} opt_rawObject イベントターゲット(EventTarget)\r
56  */\r
57 X.EventDispatcher =\r
58         X.Class.create(\r
59                 'EventDispatcher',\r
60                 \r
61             /** @lends X.EventDispatcher.prototype */\r
62                 {\r
63 \r
64 \r
65 \r
66                 // TODO _rawObjectType EventTarget, XHR, Silverlight, ...\r
67                 /**\r
68                  * イベントリスナをイベント名(string)や数値(1~,フレームワーク内で定義)をキーとするArrayで記憶します。\r
69                  * Arrayには、{k:種類,x:コンテキスト(thisObject),f:関数,s:サプリメントする引数の配列} というハッシュ、または関数が蓄えられています。\r
70                  * @private\r
71                  * @type {Object.<(number|string), Array.<(callbackHash|function)>>}\r
72                  */\r
73                         '_listeners'    : null,\r
74 \r
75                 /**\r
76                  * _rawObject には HTMLElement, window, document, XHR といったイベントターゲットオブジェクトを設定します。\r
77                  * _rawObject が設定されていると listen(), unlisten() 時に addEventListener(DOM Level2) や detachEvent(ie5~8), on~(DOM0) 等を操作します。\r
78                  * _rawObject は最初の listen() 前に設定しておかないと addEventListener 等が意図したように行われません。\r
79                  * X.Node では非同期に HTMLElement を生成していて、要素生成以前に listen, unlisten を呼び出すことができます。これは適宜に X_EventDispatcher_toggleAllEvents を呼んで解決しているためです。\r
80                  * @private\r
81                  * @type {Object}\r
82                  */\r
83                         '_rawObject'    : null,\r
84                 \r
85                 /**\r
86                  * _rawObject がある場合、イベントターゲットオブジェクトに対して addEventListener, attachEvent, onXX 時に実際に渡される関数。\r
87                  * イベントタイプが変わっても、単一の オブジェクトを使いまわす。(但し例外は、IE8以下の XHR|MSXML に対して。)最初の listen で生成されて、最後の unlisten で削除される。\r
88                  * @private\r
89                  * @type {X.Callback}\r
90                  */\r
91                         '_handleEvent'  : null,\r
92                         \r
93                 /**\r
94                  * dispatch 中に dispatch が呼ばれた際に、そのネストの深さを保存する。\r
95                  * dispatch() 終了時に _dispatching が 0 の場合に、現在のインスタンスの dispatch がすべて終わったことになる。\r
96                  * @private\r
97                  * @type {number}\r
98                  */\r
99                         '_dispatching'  : 0,     // dispatch 中の unlisten で使用\r
100 \r
101                 /**\r
102                  * dispatch 中に listen が呼ばれた場合に、配列のindexがずれることを避けるため、一旦保持する。\r
103                  * _dispatching が 0 のときに _reserves を使って listen() を呼び出す。\r
104                  * @private\r
105                  * @type {Object.<(number|string), Array.<(X.Callback|function)>>}\r
106                  */\r
107                         '_reserves'     : null,\r
108                 \r
109                 /**\r
110                  * dispatch 中に unlisten が呼ばれた場合に、配列のindexがずれることを避けるため、一旦保持する。\r
111                  * _dispatching が 0 のときに _unlistens を使って unlisten() を呼び出す。\r
112                  * @private\r
113                  * @type {Object.<(number|string), Array.<(X.Callback|function)>>}\r
114                  */\r
115                         '_unlistens'    : null,\r
116                         \r
117                 /**\r
118                  * dispatch 中に kill が呼ばれた場合に、X.EventDispatcher インスタンスの削除を dispatch 後にずらすために立てるフラグ。\r
119                  * @private\r
120                  * @type {boolean}\r
121                  */\r
122                         '_killReserved' : false,\r
123 \r
124                 /**\r
125                  * asyncDispatch 中に kill が呼ばれた場合に、X.EventDispatcher インスタンスの削除を最後のタイマーの発火後にずらすために立てるフラグ。\r
126                  * TODO asyncDispatch の解除はどうする?\r
127                  * @private\r
128                  * @type {boolean}\r
129                  */\r
130                         '_lastLazyID' : false,\r
131                         \r
132             /**\r
133              * X.EventDispatcher のコンストラクタの実体。\r
134              * @@@constructs\r
135              * @this {X.EventDispatcher}\r
136              * @param {object=} opt_rawObject\r
137              */\r
138                         Constructor : function( opt_rawObject ){\r
139                                 if( opt_rawObject ){\r
140                                         this._rawObject = opt_rawObject;\r
141                                 };\r
142                         },\r
143 \r
144                 /**\r
145                  * 登録されたイベントリスナを呼び出す。イベントリスナの返り値(数値)を OR したものを返す。\r
146                  * @this {X.EventDispatcher}\r
147                  * @return {number}\r
148                  * @param {(eventHash|string|number)} e\r
149                  */     \r
150                         dispatch : X_EventDispatcher_dispatch,\r
151                         \r
152                 /**\r
153                  * \r
154                  * @this {X.EventDispatcher}\r
155                  * @param {(string|number|Array.<(string,number)>)} type\r
156                  * @param {(listener|function|Array)=} opt_arg1\r
157                  * @param {(function|Array=} opt_arg2\r
158                  * @param {Array=} opt_arg3\r
159                  * @return {X.EventDispatcher}\r
160                  */\r
161                         listen : X_EventDispatcher_listen,\r
162                 \r
163                 /**\r
164                  * 一度 dispatch された時に自動で unlisten されるフラグを立てて listen する。\r
165                  * @this {X.EventDispatcher}\r
166                  * @return {X.EventDispatcher}\r
167                  * @param {(string|number|Array.<(string,number)>)} type\r
168                  * @param {(listener|function|Array)=} opt_arg1\r
169                  * @param {(function|Array=} opt_arg2\r
170                  * @param {Array=} opt_arg3\r
171                  */\r
172                         listenOnce : function( type, opt_arg1, opt_arg2, opt_arg3 ){\r
173                                 X_EventDispatcher_once = true;\r
174                                 this.listen( type, opt_arg1, opt_arg2, opt_arg3 );\r
175                                 X_EventDispatcher_once = false;\r
176                                 return this;\r
177                         },\r
178 \r
179                         unlisten : X_EventDispatcher_unlisten,\r
180 \r
181                 /**\r
182                  * <p>イベントリスナの登録状況を真偽値で返す。戻り値が数値(index)の場合もあるが、これは内部のみで使用。\r
183                  * <p>this.listening(); のように type を省略した場合、一つでも登録があれば true を返す。\r
184                  * <p>this.listening( 'myevent' ); と type だけを与えた場合、その type に登録があれば true を返す。\r
185                  * <p>type と イベントリスナの組み合わせが登録されているかを調べる場合は、listen 時の thisObject や args(Array) も一致させて渡す必要がある。\r
186                  * \r
187                  * @example \r
188                  *  this.listen( [ 'myevent', 'yourevent' ], this, onMyEvent, args = [ 1, 'a' ] );\r
189                  *  this.listening( 'myevent', this, onMyEvent, args ) === true;\r
190                  * \r
191                  * @this {X.EventDispatcher}\r
192                  * @return {(number|boolean)}\r
193                  * @param {(string|number)=} opt_type\r
194                  * @param {(listener|function|Array|callbackHash)=} opt_arg1\r
195                  * @param {(function|Array=} opt_arg2\r
196                  * @param {Array=} opt_arg3\r
197                  */                     \r
198                         listening : function( opt_type, opt_arg1, opt_arg2, opt_arg3 ){\r
199                                 var list = this._listeners,\r
200                                         unlistens, i, f, hash;\r
201                                 \r
202                                 if( opt_type === undefined ) return !!list;\r
203                                 if( !list || !( list = list[ opt_type ] ) ) return false;\r
204                                 if( opt_arg1 === undefined ) return true;\r
205                                 \r
206                                 if( opt_arg1.k ){\r
207                                         hash = opt_arg1;\r
208                                 } else {\r
209                                         hash = X_Callback_classifyCallbackArgs( opt_arg1, opt_arg2, opt_arg3, this );\r
210                                 };\r
211                                 \r
212                                 if( ( unlistens = this._unlistens ) && ( unlistens = unlistens[ opt_type ] ) ){\r
213                                         for( i = unlistens.length; i; ){\r
214                                                 f = unlistens[ --i ];\r
215                                                 if( f === hash || ( f.x === hash.x && f.f === hash.f && f.s === hash.s ) ) return false;\r
216                                         };\r
217                                 };\r
218                                 for( i = list.length; i; ){\r
219                                         f = list[ --i ];\r
220                                         if( f === hash || ( f.x === hash.x && f.f === hash.f && f.s === hash.s ) ){\r
221                                                 // index を要求された場合、lock されていない、または unlock なら index を返す\r
222                                                 return X_EventDispatcher_needsIndex ? ( X_EventDispatcher_unlock || !f.lock ? i : false ) : true;\r
223                                         };\r
224                                 };\r
225                                 return false;\r
226                         },\r
227 \r
228                 /**\r
229                  * delay(ミリ秒)後にイベントを dispatch する。戻り値は uid = X.Timer.add() のタイマーID(数値)。X.Timer.remove(uid) でタイマーを解除して dispatch を中止できる。\r
230                  * @this {X.EventDispatcher}\r
231                  * @param {(number|eventHash|string)=} delay ms 省略した場合は 0 として扱う asyncDispatch( 'myevent' ) -> asyncDispatch( 0, 'myevent' )\r
232                  * @param {(eventHash|string|number)=} e イベントを表す数値、文字列、{ type : XXX, ... } なオブジェクト\r
233                  * @return {number} X.Timer.add() の戻り値\r
234                  */                     \r
235                         asyncDispatch : function( delay, e ){\r
236                                 if( delay && e === undefined ){\r
237                                         e = delay;\r
238                                         delay = 0;\r
239                                 };\r
240                                 return this[ '_lastLazyID' ] = X.Timer.add( delay, 1, this, X_EventDispatcher_dispatch, [ e ] );\r
241                         }\r
242                 }\r
243         );\r
244 \r
245 // ------------------------------------------------------------------------- //\r
246 // --- implements ---------------------------------------------------------- //\r
247 // ------------------------------------------------------------------------- //\r
248 \r
249 /**\r
250  * 登録されたイベントリスナを呼び出す。イベントリスナの返り値(数値)を OR したものを返す。イベントハッシュでなく、string|number を渡すと内部でイベントハッシュを作る\r
251  * @this {X.EventDispatcher}\r
252  * @return {number} X.Callback で定義された数値\r
253  * @param {(eventHash|string|number)} e\r
254  */\r
255 function X_EventDispatcher_dispatch( e ){\r
256         var list  = this[ '_listeners' ],\r
257                 ret   = X_Callback_NONE,\r
258                 type  = e[ 'type' ],\r
259                 unlistens, i, l, args, f, r, sysOnly;\r
260         \r
261         if( !list || !( list = list[ type || e ] ) ) return ret;\r
262         \r
263         // 数値, 文字が渡された場合\r
264         if( !type ){\r
265                 e = { type : type = e };\r
266         };\r
267         e.target        = e.target || this;\r
268         e.currentTarget = e.currentTarget || this;\r
269         \r
270         ++this._dispatching;\r
271         \r
272         // todo:\r
273         // type も保存\r
274         this._unlistens = this._unlistens || {};\r
275         unlistens = this._unlistens[ type ];\r
276         \r
277         for( i = 0; i < list.length; ++i ){\r
278                 f = list[ i ];\r
279                 if( !unlistens ){\r
280                         unlistens = this._unlistens[ type ];\r
281                 };\r
282                 if( unlistens && unlistens.indexOf( f ) !== -1 ) continue;\r
283                 \r
284                 //if( f !== X.emptyFunction ){\r
285                 //      if( f.k ){\r
286                                 r = X_Callback_proxyCallback( f, args || ( args = [ e ] ) );\r
287                 //      } else {\r
288                 //              r = f.call( this, e );\r
289                 //      };\r
290                 //};\r
291                 \r
292                 if( f.once || r & X_Callback_UN_LISTEN ){\r
293                         // dispatch 中に unlisten が作られることがある\r
294                         if( !unlistens ){\r
295                                 unlistens = this._unlistens || ( this._unlistens = {} );\r
296                                 unlistens = unlistens[ type ] || ( unlistens[ type ] = [] );\r
297                         };\r
298                         unlistens.indexOf( f ) === -1 && ( unlistens[ unlistens.length ] = f );\r
299                 };\r
300 \r
301                 if( r & X.Callback.STOP_NOW ){\r
302                         sysOnly = true;\r
303                 };\r
304                 ret |= X.Type.isFinite( r ) ? r : 0;\r
305         };\r
306         \r
307         if( ( --this._dispatching ) === 0 ){\r
308                 \r
309                 if( this[ '_lastLazyID' ] && X_Timer_currentUID === this[ '_lastLazyID' ] ){\r
310                         delete this[ '_lastLazyID' ];\r
311                 };\r
312                 \r
313                 // dispatch 中に unlisten された要素の削除\r
314                 unlistens = this._unlistens;\r
315                 delete this._dispatching;\r
316                 delete this._unlistens;                                 \r
317                 // _unlistens に入っている callbackHash は、lock をクリアしている\r
318                 X_EventDispatcher_unlock = true;\r
319                 for( type in unlistens ){\r
320                         //if( X_EMPTY_OBJECT[ type ] ) continue;\r
321                         list = unlistens[ type ];\r
322                         for( i = list.length; i; ){\r
323                                 this.unlisten( type, list[ --i ] );\r
324                         };\r
325                         list.length = 0;\r
326                         delete unlistens[ type ];\r
327                 };\r
328                 X_EventDispatcher_unlock = false;\r
329                 \r
330                 if( this._killReserved ){\r
331                         this.kill();\r
332                 } else\r
333                 if( list = this._reserves ){\r
334                         for( i = 0, l = list.length; i < l; ++i ){\r
335                                 f = list[ i ];\r
336                                 X_EventDispatcher_once = f[ 4 ];\r
337                                 X_EventDispatcher_lock = f[ 5 ];\r
338                                 this.listen( f[ 0 ], f[ 1 ], f[ 2 ], f[ 3 ] );\r
339                                 X_EventDispatcher_once = false;\r
340                                 X_EventDispatcher_lock = false;\r
341                                 f.length = 0;\r
342                         };\r
343                         list.length = 0;\r
344                         delete this._reserves;\r
345                 };\r
346         };\r
347         \r
348         return ret;\r
349 };\r
350 \r
351 /**\r
352  * \r
353  * @this {X.EventDispatcher}\r
354  * @memberOf X.EventDispatcher.prototype\r
355  * @param {(string|number|Array.<(string,number)>)} type\r
356  * @param {(listener|function|Array)=} opt_arg1\r
357  * @param {(function|Array=} opt_arg2\r
358  * @param {Array=} opt_arg3\r
359  * @return {X.EventDispatcher}\r
360  */\r
361 function X_EventDispatcher_listen( type, opt_arg1, opt_arg2, opt_arg3 ){\r
362         var list = this._listeners,\r
363                 i, raw, add, f;\r
364 \r
365         if( !type ) return this;\r
366         \r
367         if( this._dispatching ){\r
368                 if( !this._reserves ) this._reserves = [];\r
369                 this._reserves[ this._reserves.length ] = [ type, opt_arg1, opt_arg2, opt_arg3, X_EventDispatcher_once, X_EventDispatcher_lock ];\r
370                 return this;\r
371         };\r
372         \r
373         if( X.Type.isArray( type ) ){\r
374                 for( i = type.length; i; ){\r
375                         this.listen( type[ --i ], opt_arg1, opt_arg2, opt_arg3 );\r
376                 };\r
377                 return this;\r
378         };\r
379         \r
380         raw = this._rawObject || X_UA_DOM.IE4 && X_Node__ie4getRawNode( this );\r
381         add = raw && ( !list || !list[ type ] ) && X.Type.isString( type );\r
382         \r
383         if( this.listening( type, opt_arg1 || this, opt_arg2, opt_arg3 ) ) return this;\r
384 \r
385         if( !list ) list = this._listeners = {};\r
386         if( !( list = list[ type ] ) ) list = this._listeners[ type ] = [];\r
387         \r
388         add && X_EventDispatcher_addEvent( this, type, raw, list );\r
389         \r
390         f = X_Callback_classifyCallbackArgs( opt_arg1, opt_arg2, opt_arg3, this );\r
391         list[ list.length ] = f;\r
392         f.once = X_EventDispatcher_once;\r
393         f.lock = X_EventDispatcher_lock;\r
394         \r
395         return this;\r
396 };\r
397 \r
398 /*\r
399  * X_EventDispatcher_systemUnlisten 経由でないと解除できないリスナの登録\r
400  */\r
401 function X_EventDispatcher_systemListen( that, type, opt_arg1, opt_arg2, opt_arg3 ){\r
402         X_EventDispatcher_lock = true;\r
403         that.listen( type, opt_arg1, opt_arg2, opt_arg3 );\r
404         X_EventDispatcher_lock = false;\r
405 };\r
406 \r
407 // TODO this.listen(type) は this リスナの登録なのに、this.unlisten(type)は全てのtypeの削除、と不一致\r
408 \r
409 /**\r
410  * \r
411  * @this {X.EventDispatcher}\r
412  * @return {X.EventDispatcher}\r
413  * @param {(string|number|Array.<(string,number)>)=} opt_type\r
414  * @param {(listener|function|Array)=} opt_arg1\r
415  * @param {(function|Array=} opt_arg2\r
416  * @param {Array=} opt_arg3\r
417  */\r
418 function X_EventDispatcher_unlisten( opt_type, opt_arg1, opt_arg2, opt_arg3 ){\r
419         var list = this._listeners,\r
420                 _list, reserves, unlistens, i, f, raw, empty;\r
421         if( !list ) return this;\r
422         \r
423         if( X.Type.isArray( opt_type ) ){\r
424                 for( i = opt_type.length; i; ){\r
425                         this.unlisten( opt_type[ --i ], opt_arg1, opt_arg2, opt_arg3 );\r
426                         if( !opt_type[ i ] ){\r
427                                 alert( '不正な unlisten Array' );\r
428                         };\r
429                 };\r
430                 return this;\r
431         };\r
432         \r
433         if( opt_type === undefined ){\r
434                 // 全て削除\r
435                 for( opt_type in list ){\r
436                         //if( X_EMPTY_OBJECT[ opt_type ] ) continue;\r
437                         _list = list[ opt_type ];\r
438                         for( i = _list.length; i; ){\r
439                                 this.unlisten( opt_type, _list[ --i ] ); // override されていることがあるので、必ず unlisten を使用\r
440                         };\r
441                         // this.unlisten( opt_type ); これは無茶!\r
442                 };\r
443                 return this;\r
444         } else\r
445         if( opt_arg1 === undefined ){\r
446                 // 同一タイプを全て削除\r
447                 if( _list = list[ opt_type ] ){\r
448                         for( i = _list.length; i; ){\r
449                                 this.unlisten( opt_type, _list[ --i ] ); // override されていることがあるので、必ず unlisten を使用\r
450                         };\r
451                 };\r
452                 return this;\r
453         } else\r
454         if( reserves = this._reserves ){\r
455                 for( i = reserves.length; i; ){\r
456                         f = reserves[ --i ];\r
457                         if( f[ 0 ] === opt_type && f[ 1 ] === opt_arg1 && f[ 2 ] === opt_arg2 && f[ 3 ] === opt_arg3 ){\r
458                                 reserves.splice( i, 1 );\r
459                                 if( !reserves.legth ) delete this._reserves;\r
460                                 return this;\r
461                         };\r
462                 };\r
463         };\r
464         \r
465         X_EventDispatcher_needsIndex = true;\r
466         i = this.listening( opt_type, opt_arg1, opt_arg2, opt_arg3 );\r
467         X_EventDispatcher_needsIndex = false;\r
468         if( i === false ) return this;\r
469 \r
470         f = ( _list = list[ opt_type ] )[ i ];\r
471         // _unlistens に入っている callbackHash は、lock をクリアしている\r
472         if( unlistens = this._unlistens ){\r
473                 ( unlistens = unlistens[ opt_type ] ) ?\r
474                         ( unlistens[ unlistens.length ] = f ) :\r
475                         ( this._unlistens[ opt_type ] = [ f ] );\r
476         } else {\r
477                 delete f.once;\r
478                 _list.splice( i, 1 );\r
479                 if( !_list.length ){\r
480                         raw  = this._rawObject || X_UA_DOM.IE4 && X_Node__ie4getRawNode( this );\r
481                         delete list[ opt_type ];\r
482                         if( empty = X_Object_isEmpty( list ) ) delete this._listeners;\r
483                         if( raw && '' + parseFloat( opt_type ) !== '' + opt_type ){ // 数字イベントの除外\r
484                                 X_EventDispatcher_removeEvent( this, opt_type, raw, _list, !empty );\r
485                         };\r
486                 };\r
487         };\r
488         return this;\r
489 };\r
490 \r
491 /*\r
492  * X_EventDispatcher_systemListen から登録したイベントの解除\r
493  */\r
494 function X_EventDispatcher_systemUnlisten( that, type, opt_arg1, opt_arg2, opt_arg3 ){\r
495         X_EventDispatcher_unlock = true;\r
496         that.unlisten( type, opt_arg1, opt_arg2, opt_arg3 );\r
497         X_EventDispatcher_unlock = false;\r
498 };\r
499 \r
500 function X_EventDispatcher_addEvent( that, type, raw, list ){\r
501         var i;\r
502         X_EventDispatcher_lock || ( type = X_Event_Rename[ type ] || type );\r
503         \r
504         if( X.Type.isArray( type ) ){\r
505                 for( i = type.length; i; ){\r
506                         X_EventDispatcher_systemListen( that, type[ --i ], X.emptyFunction );\r
507                         console.log( 'events fix > ' + type[ i ] );\r
508                 };\r
509         } else {\r
510                 X_EventDispatcher_actualAddEvent( that, type, raw, list );\r
511         };\r
512 };\r
513 \r
514 var X_EventDispatcher_actualAddEvent =\r
515         // Days on the Moon DOM Events とブラウザの実装 \r
516         // http://nanto.asablo.jp/blog/2007/03/23/1339502\r
517         // Safari 2 では関数オブジェクトしか EventListener として使えませんが、Safari のナイトリービルドでは handleEvent メソッドを持つオブジェクトも EventListener として使えるようです。\r
518         X_UA_EVENT.W3C /* && ( X_UA.WebKit < 525.13 || X_UA.Opera7 || X_UA.NetFront < 4 ) */ ? // Safari3-\r
519                 (function( that, type, raw, list ){\r
520                         if( that._isXHR && X_UA.Opera < 12 ){\r
521                                 // Opera11- の XHR は event オブジェクトが返らないため, eventType 毎に callback を指定する addEventListener もない\r
522                                 raw[ 'on' + type ] = X_Callback_create( that, X_EventDispatcher_dispatch, [ type ] );\r
523                         } else\r
524                         if( that._isSilverlight ){\r
525                                 list.slcallback = X_Callback_create( that, X_EventDispatcher_sliverLightDispatch, [ type ] );\r
526                                 list.sltoken    = raw.AddEventListener( type, list.slcallback );\r
527                         } else\r
528                         // iOS と MacOSX Iron36 で発生。連続してアニメーションが起こると、クロージャの束縛された obj へのアクセスに失敗する。Win では起きない?\r
529                         // むしろ、MacOSX のブラウザ全般で起こる??\r
530                         if( ( X_UA.WebKit || X_UA.Blink ) &&\r
531                                 ( type === 'webkitTransitionEnd' || type === 'transitionend' ||\r
532                                   type === 'animationend'        || type === 'webkitAnimationEnd' ||\r
533                                   type === 'animationstart'      || type === 'webkitAnimationStart' ||\r
534                                   type === 'animationiteration'  || type === 'webkitAnimationIteration' ) ){\r
535                                 raw.addEventListener( type, X_EventDispatcher_iOSTransitionEndDispatch, false );\r
536                         } else {\r
537                                 console.log( 'event > ' + type );\r
538                                 that._handleEvent || ( that._handleEvent = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) );\r
539 \r
540                                 if( raw.addEventListener ){\r
541                                         raw.addEventListener( type, that._handleEvent, false );\r
542                                 } else {\r
543                                         // Safari は Image, Opera7 は window\r
544                                         raw[ 'on' + type ] = that._handleEvent;\r
545                                 };\r
546                         };\r
547                 }) :\r
548         X_UA_EVENT.IE ?\r
549                 (function( that, type, raw, list ){\r
550                         if( that._isXHR ){\r
551                                 // ie8- の XHR は window.event が更新されないため, eventType 毎に callback を指定する\r
552                                 raw[ 'on' + type ] = X_Callback_create( that, X_EventDispatcher_dispatch, [ type ] );\r
553                         } else                                  \r
554                         if( that._isSilverlight ){\r
555                                 list.slcallback = X_Callback_create( that, X_EventDispatcher_sliverLightDispatch, [ type ] );\r
556                                 list.sltoken    = raw.AddEventListener( type, list.slcallback );\r
557                         } else {                                \r
558                                 that._handleEvent || ( that._handleEvent = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) );\r
559                                 \r
560                                 if( raw.attachEvent ){\r
561                                         raw.attachEvent( 'on' + type, that._handleEvent );\r
562                                 } else {\r
563                                         raw[ 'on' + type ] = that._handleEvent;\r
564                                 };\r
565                         };\r
566                 }) :\r
567                 (function( that, type, raw, list ){\r
568                         if( that._isXHR ){\r
569                                 // ie4 mobile は XHR をサポート!\r
570                                 raw[ 'on' + type ] = X_Callback_create( that, X_EventDispatcher_dispatch, [ type ] );\r
571                         } else\r
572                         if( that._isSilverlight ){\r
573                                 // DOM0 で Silverlight ってあるの?\r
574                                 list.slcallback = X_Callback_create( that, X_EventDispatcher_sliverLightDispatch, [ type ] );\r
575                                 list.sltoken    = raw.AddEventListener( type, list.slcallback );\r
576                         } else {\r
577                                 raw[ 'on' + type ] = that._handleEvent || ( that._handleEvent = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) );\r
578                         };\r
579                 });\r
580 \r
581 /*\r
582  * iOS の webkitTransitionEnd が連続して起こる場合、\r
583  * コールバックの(that._handleEvent)クロージャ内の実際のコールバック(X_Callback_actualClosure:obj._)が\r
584  * 参照できていない問題に遭遇、、、iOS3.1.3 & iOS6.1.5 で確認\r
585  * animation も怪しい、、、\r
586  */\r
587 function X_EventDispatcher_iOSTransitionEndDispatch( e ){\r
588         return X_Node_getXNode( this ).dispatch( X_Event_RenameTo[ e.type ] || e.type );\r
589 };\r
590 \r
591 /*\r
592  * Silverlight のイベントの概要\r
593  * http://msdn.microsoft.com/ja-jp/library/cc189018%28v=vs.95%29.aspx#the_sender_parameter_and_event_data\r
594  */\r
595 function X_EventDispatcher_sliverLightDispatch( sender, e, type ){\r
596         return this.dispatch( type );\r
597 };\r
598 \r
599 function X_EventDispatcher_removeEvent( that, type, raw, list, skip ){\r
600         var i;\r
601         X_EventDispatcher_unlock || ( type = X_Event_Rename[ type ] || type );\r
602         \r
603         if( X.Type.isArray( type ) ){\r
604                 for( i = type.length; i; ){\r
605                         X_EventDispatcher_systemUnlisten( that, type[ --i ], X.emptyFunction );\r
606                 };\r
607         } else {\r
608                 X_EventDispatcher_actualRemoveEvent( that, type, raw, list, skip );\r
609         };\r
610 };\r
611 \r
612 var X_EventDispatcher_actualRemoveEvent =\r
613         X_UA_EVENT.W3C /*&& ( X_UA.WebKit < 525.13 || X_UA.Opera7 || X_UA.NetFront < 4 )*/ ? // Safari3-\r
614                 (function( that, type, raw, list, skip ){\r
615                         if( that._isXHR && X_UA.Opera < 12 ){\r
616                                 X_Callback_correct( raw[ 'on' + type ] );\r
617                                 raw[ 'on' + type ] = '';\r
618                         } else  \r
619                         if( that._isSilverlight ){\r
620                                 raw.RemoveEventListener( type, list.sltoken ); // token\r
621                                 X_Callback_correct( list.slcallback );\r
622                                 delete list.sltoken;\r
623                                 delete list.slcallback;\r
624                         } else\r
625                         if( ( X_UA.WebKit || X_UA.Blink ) &&\r
626                                 ( type === 'webkitTransitionEnd' || type === 'transitionend' ||\r
627                                   type === 'animationend'        || type === 'webkitAnimationEnd' ||\r
628                                   type === 'animationstart'      || type === 'webkitAnimationStart' ||\r
629                                   type === 'animationiteration'  || type === 'webkitAnimationIteration' ) ){\r
630                                 raw.removeEventListener( type, X_EventDispatcher_iOSTransitionEndDispatch, false );\r
631                         } else {\r
632                                                                 \r
633                                 if( raw.addEventListener ){\r
634                                         raw.removeEventListener( type, that._handleEvent, false );\r
635                                 } else {\r
636                                         raw[ 'on' + type ] = null;\r
637                                 };\r
638                                 if( !skip ){\r
639                                         X_Callback_correct( that._handleEvent );\r
640                                         delete that._handleEvent;\r
641                                 };\r
642                         };\r
643                 }) :\r
644         X_UA_EVENT.IE ?\r
645                 (function( that, type, raw, list, skip ){\r
646                         if( that._isXHR ){\r
647                                 X_Callback_correct( raw[ 'on' + type ] );\r
648                                 raw[ 'on' + type ] = X.emptyFunction;\r
649                                 raw[ 'on' + type ] = '';\r
650                         } else  \r
651                         if( that._isSilverlight ){\r
652                                 raw.RemoveEventListener( type, list.sltoken ); // token\r
653                                 X_Callback_correct( list.slcallback );\r
654                                 delete list.sltoken;\r
655                                 delete list.slcallback;\r
656                         } else {\r
657                                 if( raw.attachEvent ){\r
658                                         raw.detachEvent( 'on' + type, that._handleEvent );\r
659                                 } else {\r
660                                         raw[ 'on' + type ] = X.emptyFunction;\r
661                                         raw[ 'on' + type ] = '';\r
662                                 };\r
663                                 \r
664                                 if( !skip ){\r
665                                         X_Callback_correct( that._handleEvent );\r
666                                         delete that._handleEvent;\r
667                                 };\r
668                         };\r
669                 }) :\r
670                 (function( that, type, raw, list, skip ){\r
671                         if( that._isXHR ){\r
672                                 X_Callback_correct( raw[ 'on' + type ] );\r
673                                 raw[ 'on' + type ] = X.emptyFunction;\r
674                                 raw[ 'on' + type ] = '';\r
675                         } else\r
676                         if( that._isSilverlight ){\r
677                                 raw.RemoveEventListener( type, list.sltoken ); // token\r
678                                 X_Callback_correct( list.slcallback );\r
679                                 delete list.sltoken;\r
680                                 delete list.slcallback;\r
681                         } else {\r
682                                 raw[ 'on' + type ] = X.emptyFunction;\r
683                                 raw[ 'on' + type ] = '';\r
684                                 \r
685                                 if( !skip ){\r
686                                         X_Callback_correct( that._handleEvent );\r
687                                         delete that._handleEvent;\r
688                                 };\r
689                         };\r
690                 });\r
691 \r
692 \r
693 // handleEvent を拡張可能にするために、クロージャに移動した\r
694 // Is this in regard to the Safari 1.x preventDefault bug on click/dblclick?\r
695 // https://groups.google.com/forum/#!msg/comp.lang.javascript/uYEuCHjHxnw/yKoHtZJPa1QJ\r
696 var X_EventDispatcher_actualHandleEvent =\r
697         X_UA_EVENT.IE4 || X_UA_EVENT.IE ? // ie45678 EVENT_IE & EVENT_DOM0 for ie4\r
698                 (function(){\r
699                         var ret;\r
700                         \r
701                         //if( event.type === 'readystatechange' && this._tag && X.Dom.Event._LOAD_FIX_TAGS[ this._tag ] ){\r
702                                 //type = 'readystatechange';\r
703                         //};\r
704                         \r
705                         ret = this.dispatch( new X.Dom.Event( event, this, this._rawObject ) );\r
706 \r
707                         if( ret & X.Callback.STOP_PROPAGATION ){\r
708                                 event.cancelBubble = true;\r
709                         };\r
710                         if( ret & X.Callback.PREVENT_DEFAULT ){\r
711                                 this._tag === 'A' && this._rawObject.blur();\r
712                                 return event.returnValue = false;\r
713                         };\r
714                 }) :\r
715         //X_UA_EVENT.W3C & X_UA_EVENT.DOM0\r
716                 (function( e ){\r
717                         var ev  = new X.Dom.Event( e, this ),\r
718                                 ret = X_Callback_NONE,\r
719                                 i, l;\r
720                         //console.log( '>>>>>>>>>> ' + e.type );\r
721                         // touch event -> pointer\r
722                         if( X.Type.isArray( ev ) ){\r
723                                 if( ev.length === 0 ){\r
724                                         // TouchEvent の後に発生した MouseEvent のキャンセル\r
725                                         ret = X.Callback.STOP_PROPAGATION | X.Callback.PREVENT_DEFAULT;\r
726                                 } else {\r
727                                         for( i = 0, l = ev.length; i < l; ++i ){\r
728                                                 console.log( 'handleEvent ' + ev[ i ].type );\r
729                                                 ret |= this.dispatch( ev[ i ] ) || 0;\r
730                                         };                              \r
731                                 };\r
732                         } else {\r
733                                 ret = this.dispatch( ev );\r
734                         };\r
735                         \r
736                         if( ret & X.Callback.STOP_PROPAGATION ){\r
737                                 e.stopPropagation();\r
738                         };\r
739                         if( ret & X.Callback.PREVENT_DEFAULT ){\r
740                                 this._tag === 'A' && this._rawObject.blur();\r
741                                 e.preventDefault();\r
742                                 if( X_UA.WebKit < 525.13 ){ // Safari3-\r
743                                         if( e.type === 'click' || e.type === 'dbclick' ){\r
744                                                 X_EventDispatcher_safariPreventDefault = true;\r
745                                         };\r
746                                 };\r
747                                 return false;\r
748                         };\r
749                 });\r
750 \r
751 if( X_UA.WebKit < 525.13 ){ // Safari3-\r
752         document.documentElement.onclick =\r
753         document.documentElement.ondbclick = function( e ){\r
754                         if( X_EventDispatcher_safariPreventDefault ){\r
755                                 X_EventDispatcher_safariPreventDefault = false;\r
756                                 e.preventDefault();\r
757                                 return false;\r
758                         };\r
759                 };\r
760 };\r
761 \r
762 // イベントの退避、dom が画面から抜かれる場合に実施しておく\r
763 // 退避したイベントの復帰\r
764 function X_EventDispatcher_toggleAllEvents( that, add ){\r
765         var list = that._listeners,\r
766                 raw  = that._rawObject || X_UA_DOM.IE4 && X_Node__ie4getRawNode( that ),\r
767                 f    = add ? X_EventDispatcher_addEvent : X_EventDispatcher_removeEvent,\r
768                 type;\r
769         if( !list || !raw ) return;\r
770         for( type in list ){\r
771                 //if( X_EMPTY_OBJECT[ type ] ) continue;\r
772                 // 数字イベントの除外\r
773                 if( '' + parseFloat( type ) !== type ){\r
774                         // TODO type rename はここ\r
775                         f( that, type, raw, list[ type ], true );\r
776                 };\r
777         };\r
778 };\r
779 \r
780 \r
781 console.log( 'X.Core.EventDispatcher' );\r