OSDN Git Service

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