OSDN Git Service

31c8a3cae5cf2ae968b87b489b668c0d69678d9e
[pettanr/clientJs.git] / 0.6.x / js / 01_core / 10_XCallback.js
1 \r
2 // ------------------------------------------------------------------------- //\r
3 // ------------ local variables -------------------------------------------- //\r
4 // ------------------------------------------------------------------------- //\r
5 \r
6 var \r
7         X_Callback_LIVE_LIST        = [],\r
8 \r
9         X_Callback_POOL_LIST        = [],\r
10 \r
11         X_Closure_COMMAND_BACK      = X_Callback_LIVE_LIST,\r
12 \r
13         X_Closure_COMMAND_DROP      = X_Callback_POOL_LIST,\r
14         \r
15         /** @const */\r
16         X_Callback_THIS_FUNC        = 1,\r
17         /** @const */\r
18         X_Callback_HANDLEEVENT      = 2,\r
19         /** @const */\r
20         X_Callback_FUNC_ONLY        = 3,\r
21         /** @const */\r
22         X_Callback_THIS_FUNCNAME    = 4,\r
23 \r
24         /** @const */\r
25         X_Callback_NONE             =  0,\r
26         /** @const */\r
27         X_Callback_UN_LISTEN        =  1,\r
28         /** @const */\r
29         X_Callback_STOP_PROPAGATION =  2,\r
30         /** @const */\r
31         X_Callback_STOP_NOW         =  4 | 2,  // 同一階層のリスナーのキャンセル(上位へもキャンセル)\r
32         /** @const */\r
33         X_Callback_PREVENT_DEFAULT  =  8,  // 結果動作のキャンセル,\r
34         /** @const */\r
35         X_Callback_CAPTURE_POINTER  = 16,\r
36         /** @const */\r
37         X_Callback_RELEASE_POINTER  = 32,\r
38         \r
39         /** @const */\r
40         X_Callback_SYS_CANCEL       = 64 | 4 | 2;\r
41 \r
42 \r
43 \r
44 /*\r
45  * handleEvent という関数のメンバーを持つオブジェクト\r
46  * @typedef {{ handleEvent : function }}\r
47  */\r
48 var listener;\r
49 \r
50 /**\r
51  * <h4>クロージャについて</h4>\r
52  * javascript 開発で多用されるクロージャですが、次の理由によりその使用は慎重になるべきです。\r
53  * <ol>\r
54  * <li>正しく参照を切ってガベージコレクトされる条件を満たしたか?プログラマが見落としやすく、結果メモリリークを起こしやすい。特にコードがコールバック地獄になると目も当てられない。\r
55  * </ol>\r
56  * \r
57  * とくに次のようなページでは、クロージャの使用を極力避けるべきです。破棄が行われることが確実で即時関数によって新しい名前を追加したくない場合と、次項で紹介する絶対に必要なケース以外で使わないようにします。\r
58  * <ol>\r
59  * <li>ajax によりデータ取得がたびたび起こり、合わせて不要なデータの破棄も行う。\r
60  * <li>シングルページアプリケーションで画面の生成と破棄が繰り返される。\r
61  * <li>Web アプリケーションで、一度開いたら一日中操作し続けるユーザーも想定される。\r
62  * </ol>\r
63  * \r
64  * <h4>クロージャが絶対に必要な場合</h4>\r
65  * IE5 ~ IE8 の独自イベントモデルに於いて、イベントオブジェクトに event.currentTarget に相当するものがなく、現在どの HTML 要素でイベントが起こっているか分かりません。<br>\r
66  * そのため HTML 要素とコールバック関数を束縛するクロージャを使う必要があります。<br>\r
67  * 参照:『Javascript 第5版』 オライリー p430 17.3.6 attachEvent() と this キーワード<br>\r
68  * \r
69  * このほかに IE8 以下と Opera11 以下の XHR ではイベントオブジェクトが用意されないため、eventType が分からない問題があります。このために eventType とコールバック関数を束縛するクロージャが必要です。<br>\r
70  * \r
71  * このように、Web ブラウザと javascript の接点では、どうしてもクロージャが必要なケースがあります。<br>\r
72  * さて、クロージャの使用を最小限に留め、残った僅かなクロージャのメモリリークをチェックし、といくら慎重に開発を行っても、そもそもクロージャが破棄されるのか?ガベージコレクションの怪しいブラウザもあり問題はまだ続きます。\r
73  * \r
74  * <h4>再利用可能なクロージャ</h4>\r
75  * クロージャの使用を最小限にしたうえで、なおかつクロージャがガベージコレクションされない可能性を考慮して、pettanR フレームワークでは再利用可能なクロージャを用意します。<br>\r
76  * 再利用可能クロージャはフレームワーク内で生成・破棄されるため、ユーザーが直接触ることはありません。しかし debug ツールのコールスタックには登場するため、知識を持っておくことは有益です。<br>\r
77  * \r
78  * <h5>再利用可能クロージャの作成</h5>\r
79  * X_Callback_create() で再利用可能なクロージャの作成。次のパターンで呼び出します。<br>\r
80  * 最大で三つの引数を並べる一連のパターンは、 EventDispatcher.listen unlisten, listening や X.Timer.add, once でも使われますので、ここでよく目を通しておきます。\r
81  * \r
82  * <table>\r
83  * <tr><th>this コンテキスト+関数<td>X_Callback_create( thisObject, func )<td>func.call( thisObject );\r
84  * <tr><th>this コンテキスト+関数+追加引数<td>X_Callback_create( thisObject, func, [ arg1, ...args ] )<td>func.apply( thisObject, [ arg1, ...args ] );\r
85  * <tr><th>listener オブジェクト<td>X_Callback_create( listener )<td>listener.handleEvent(); コールバックに関数でなく handleEvent 関数をメンバに持つオブジェクトを渡すのは NN4 からある javascript のお約束です。\r
86  * <tr><th>listener オブジェクト+追加引数<td>X_Callback_create( listener, [ arg1, ...args ] )<td>listener.handleEvent.apply( listener, [ arg1, ...args ] );\r
87  * <tr><th>関数<td>X_Callback_create( func )<td>特別な操作は不要なので再利用可能クロージャは作られません。func をそのまま利用します。\r
88  * <tr><th>関数+引数<td>X_Callback_create( func, [ arg1, ...args ] )<td>func.apply( ?, [ arg1, ...args ] );\r
89  * </table>\r
90  * \r
91  * <h5>再利用可能クロージャの破棄と再利用</h5>\r
92  * X_Callback_correct() によってクロージャは回収され再利用に備えます。<br>\r
93  * 実は、クロージャが束縛するのは、this コンテキストやコールバック関数といった、<strong>そのもの</strong>ではなく、それらを一定のルールで格納したハッシュです。<br>\r
94  * このハッシュはクロージャに与えた後も、適宜に取得が可能です。このハッシュのメンバーを書き換えることで、クロージャの this コンテキストやコールバック関数を書き換えています。\r
95  * \r
96  * @class __CallbackHash__\r
97  * @classdesc コールバック関数に this コンテキストや、追加の引数を設定するための情報を収めたハッシュです。<br>\r
98  * フレームワークユーザは直接触ることにはないが、重要な情報なので書いておきます。\r
99  * @private\r
100  */\r
101 var __CallbackHash__ =\r
102 /** @lends __CallbackHash__.prototype */\r
103 {\r
104         /**\r
105          * コールバックの種類を表す数値。 this + function, this.handleEvent, function only がある。\r
106          * @type {number} \r
107          */\r
108         kind : X_Callback_THIS_FUNC,\r
109         /**\r
110          * コールバック。\r
111          * @type {funciton|undefined} \r
112          */\r
113         func : undefined,\r
114         /**\r
115          * コールバック名。コールバック作成時に関数が無い、関数が入れ替わっていても動作する。\r
116          * @type {string|undefined} \r
117          */\r
118         name : undefined,\r
119         /**\r
120          * コールバックの this コンテキスト。 \r
121          * @type {listener|object|undefined}\r
122          */\r
123         context : undefined,\r
124         /**\r
125          * コールバックに追加する引数。イベントのコールバックでは event オブジェクトのあとに追加されるため supplement[0] が第一引数にならない点に注意。\r
126          * @type {Array|undefined}\r
127          */\r
128         supplement : undefined,\r
129         /**\r
130          * __CallbackHash__ の情報を元に、コールバックを実施するプロキシ。\r
131          * @type {Function}\r
132          */\r
133         proxy : X_Callback_proxyCallback\r
134 };\r
135 \r
136 /**\r
137  * X.Timer と X.EventDispatcher からのコールバックの返り値を定義。\r
138  * @namespace X.Callback\r
139  */\r
140 X[ 'Callback' ] = {\r
141         /**\r
142          * このコールバックでは返り値による操作は無い。\r
143          * @alias X.Callback.NONE\r
144          */\r
145         'NONE'             : X_Callback_NONE,\r
146         /**\r
147          * X.Timer, X.EventDispatcher のコールバックでタイマーやイベントリスナの解除に使用。\r
148          * @alias X.Callback.UN_LISTEN\r
149          */\r
150         'UN_LISTEN'        : X_Callback_UN_LISTEN,\r
151         /**\r
152          * 上位階層へのイベント伝播のキャンセル。DOM イベントのコールバックの戻り値に指定すると e.stopPropagation() が呼ばれる。\r
153          * @alias X.Callback.STOP_PROPAGATION\r
154          */\r
155         'STOP_PROPAGATION' : X_Callback_STOP_PROPAGATION,\r
156         /**\r
157          * 以降のイベントのディスパッチを中断する。STOP_PROPAGATION との違いは、次に控えているコールバックもキャンセルされる点。但し system によって追加されたイベントはキャンセルされない。\r
158          * @alias X.Callback.STOP_NOW\r
159          */\r
160         'STOP_NOW'         : X_Callback_STOP_NOW,\r
161         /**\r
162          * DOM イベントのコールバックの戻り値に指定すると e.preventDefault() が呼ばれる。\r
163          * またフレームワーク内で定義されたデフォルト動作の回避にも使用される。\r
164          * @alias X.Callback.PREVENT_DEFAULT\r
165          */\r
166         'PREVENT_DEFAULT'  : X_Callback_PREVENT_DEFAULT,\r
167         /**\r
168          * X.UI の uinode でポインターイベントの戻り値に指定すると、以降のポインターベントを独占する。\r
169          * @alias X.Callback.CAPTURE_POINTER\r
170          */\r
171         'CAPTURE_POINTER'  : X_Callback_CAPTURE_POINTER,\r
172         /**\r
173          * X.UI の uinode でポインターイベントの戻り値に指定すると、以降のポインターベントを独占を解除する。\r
174          * @alias X.Callback.RELEASE_POINTER\r
175          */\r
176         'RELEASE_POINTER'  : X_Callback_RELEASE_POINTER\r
177 };\r
178 \r
179 // ------------------------------------------------------------------------- //\r
180 // --- implements ---------------------------------------------------------- //\r
181 // ------------------------------------------------------------------------- //\r
182 \r
183 function X_Callback_create( thisObject, opt_callback, opt_args /* [ listener || ( context + function ) || function ][ args... ] */ ){\r
184         var obj = X_Callback_classifyCallbackArgs( thisObject, opt_callback, opt_args ),\r
185                 l, ret, _obj;\r
186         \r
187         if( !obj.kind ) return obj;\r
188         \r
189         if( l = X_Callback_POOL_LIST.length ){\r
190                 ret  = X_Callback_POOL_LIST[ l - 1 ]; --X_Callback_POOL_LIST.length; // ret = X_Callback_POOL_LIST.pop();\r
191                 _obj = ret( X_Closure_COMMAND_BACK );\r
192                 \r
193                 _obj.kind       = obj.kind;\r
194                 _obj.name       = obj.name;\r
195                 _obj.func       = obj.func;\r
196                 _obj.context    = obj.context;\r
197                 _obj.supplement = obj.supplement;\r
198                 _obj.proxy      = X_Callback_proxyCallback;\r
199         } else {\r
200                 ret             = X_Callback_actualClosure( obj );\r
201                 obj.proxy       = X_Callback_proxyCallback;\r
202         };\r
203         X_Callback_LIVE_LIST[ X_Callback_LIVE_LIST.length ] = ret;\r
204         return ret;\r
205 };\r
206 \r
207 \r
208 function X_Callback_classifyCallbackArgs( arg1, arg2, arg3, alt_context ){\r
209         var obj;\r
210         \r
211         if( X_Type_isObject( arg1 ) && X_Type_isFunction( arg2 ) ){\r
212                 obj  = { context : arg1, func : arg2, kind : X_Callback_THIS_FUNC };\r
213         } else\r
214         if( X_Type_isObject( arg1 ) ){\r
215                 if( arg2 && X_Type_isString( arg2 ) ){\r
216                         obj  = { context : arg1, name : arg2, kind : X_Callback_THIS_FUNCNAME };\r
217                 } else {\r
218                         obj  = { context : arg1, kind : X_Callback_HANDLEEVENT };\r
219                         arg3 = arg2;                    \r
220                 };\r
221         } else\r
222         if( X_Type_isFunction( arg1 ) ){\r
223                 arg3 = arg2;\r
224                 if( alt_context ){\r
225                         obj  = { context : alt_context, func : arg1, kind : X_Callback_THIS_FUNC };\r
226                 } else {\r
227                         obj  = { func : arg1, kind : X_Callback_FUNC_ONLY };\r
228                 };\r
229         } else\r
230         if( X_Type_isFunction( arg2 ) ){\r
231                 //console.log( 'X_Callback_classifyCallbackArgs : arg1 が ' + arg1 + 'です' ); ie4 で error\r
232                 if( alt_context ){\r
233                         obj  = { context : alt_context, func : arg2, kind : X_Callback_THIS_FUNC };\r
234                 } else {\r
235                         obj  = { func : arg2, kind : X_Callback_FUNC_ONLY };\r
236                 };\r
237         } else\r
238         if( alt_context && X_Type_isString( arg1 ) ){\r
239                 arg3 = arg2;\r
240                 obj  = { context : alt_context, name : arg1, kind : X_Callback_THIS_FUNCNAME };\r
241         } else\r
242         if( alt_context ){\r
243                 obj  = { context : alt_context, kind : X_Callback_HANDLEEVENT };\r
244                 arg3 = arg1;\r
245         } else {\r
246                 console.log( '不正 ' + arg1 );\r
247                 console.dir( arg1 );\r
248                 return;\r
249         };\r
250         \r
251         if( X_Type_isArray( arg3 )){\r
252                 obj.supplement = arg3;\r
253         };\r
254         return ( obj.context || obj.supplement ) ? obj : arg1;\r
255 };\r
256 \r
257 function X_Callback_actualClosure( obj ){\r
258         return function(){\r
259                 if( arguments[ 0 ] === X_Closure_COMMAND_BACK ) return obj;\r
260                 if( arguments[ 0 ] !== X_Closure_COMMAND_DROP ) return obj.proxy( obj, arguments );\r
261         };\r
262 };\r
263 \r
264 function X_Callback_proxyCallback( xfunc, _args ){\r
265         var args    = _args || [],\r
266                 thisObj = xfunc.context,\r
267                 func    = xfunc.func,\r
268                 supp    = xfunc.supplement,\r
269                 temp, ret, funcName;    \r
270         \r
271         if( supp && supp.length ){\r
272                 temp = [];\r
273                 args.length &&\r
274                         (\r
275                                 args.length === 1 ?\r
276                                         ( temp[ 0 ] = args[ 0 ] ) :\r
277                                         temp.push.apply( temp, args )\r
278                         );\r
279                 supp.length === 1 ?\r
280                         ( temp[ temp.length ] = supp[ 0 ] ) :\r
281                         temp.push.apply( temp, supp );\r
282                 args = temp;\r
283         };\r
284         \r
285         switch( xfunc.kind ){\r
286 \r
287                 case X_Callback_THIS_FUNC :\r
288                         return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );\r
289                 \r
290                 case X_Callback_THIS_FUNCNAME :\r
291                         funcName = xfunc.name;\r
292                 case X_Callback_HANDLEEVENT :\r
293                         funcName = funcName || 'handleEvent';\r
294                         temp = thisObj[ funcName ];\r
295                         if( X_Type_isFunction( temp ) ){\r
296                                 return args.length === 0 ? thisObj[ 'handleEvent' ]() :\r
297                                            args.length === 1 ? thisObj[ 'handleEvent' ]( args[ 0 ] ) : temp.apply( thisObj, args );\r
298                         };\r
299                         break;\r
300                         /*\r
301                         if( temp !== func && X_Type_isFunction( temp ) ){\r
302                                 return args.length === 0 ? thisObj[ 'handleEvent' ]() : temp.apply( thisObj, args );\r
303                         } else\r
304                         if( X_Type_isFunction( thisObj ) ){\r
305                                 return args.length === 0 ? thisObj.call( thisObj ) : thisObj.apply( thisObj, args );\r
306                         };\r
307                         return args.length === 0 ? func.call( thisObj ) : func.apply( thisObj, args );*/\r
308                                                 \r
309                 case X_Callback_FUNC_ONLY :\r
310                         return args.length === 0 ?\r
311                                         func() :\r
312                                 args.length === 1 ?\r
313                                         func( args[ 0 ] ) :\r
314                                         func.apply( null, args );\r
315         };\r
316         return X_Callback_NONE;\r
317 };\r
318 \r
319 function X_Callback_correct( f ){\r
320         var i = X_Callback_LIVE_LIST.indexOf( f ),\r
321                 obj;\r
322         if( i !== -1 ){\r
323                 X_Callback_LIVE_LIST.splice( i, 1 );\r
324                 X_Callback_POOL_LIST[ X_Callback_POOL_LIST.length ] = f;\r
325                 obj = f( X_Closure_COMMAND_BACK );\r
326                 delete obj.kind;\r
327                 if( obj.name ) delete obj.name;\r
328                 if( obj.func ) delete obj.func;\r
329                 if( obj.context ) delete obj.context;\r
330                 if( obj.supplement ) delete obj.supplement;\r
331                 delete obj.proxy;\r
332                 return true;\r
333         };\r
334         return false;\r
335 };\r
336 \r
337 function X_Callback_monitor(){\r
338         return {\r
339                 'Callback:Live' : X_Callback_LIVE_LIST.length,\r
340                 'Callback:Pool' : X_Callback_POOL_LIST.length\r
341         };\r
342 };\r
343 function X_Callback_gc(){\r
344         X_Callback_POOL_LIST.length = 0; // ?\r
345 };\r
346 \r
347 X_TEMP.onSystemReady.push( function( sys ){\r
348         sys.monitor( X_Callback_monitor );\r
349         sys.gc( X_Callback_gc );\r
350 });\r
351 \r
352 \r
353 console.log( 'X.Core.Callback' );\r
354 \r