]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * @class Ext.Function\r | |
3 | *\r | |
4 | * A collection of useful static methods to deal with function callbacks.\r | |
5 | * @singleton\r | |
6 | */\r | |
7 | Ext.Function = (function() {\r | |
8 | // @define Ext.lang.Function\r | |
9 | // @define Ext.Function\r | |
10 | // @require Ext\r | |
11 | // @require Ext.lang.Array\r | |
12 | var lastTime = 0,\r | |
13 | animFrameId,\r | |
14 | animFrameHandlers = [],\r | |
15 | animFrameNoArgs = [],\r | |
16 | idSource = 0,\r | |
17 | animFrameMap = {},\r | |
18 | win = window,\r | |
19 | global = Ext.global,\r | |
20 | hasImmediate = !!(global.setImmediate && global.clearImmediate),\r | |
21 | requestAnimFrame = win.requestAnimationFrame || win.webkitRequestAnimationFrame ||\r | |
22 | win.mozRequestAnimationFrame || win.oRequestAnimationFrame ||\r | |
23 | function(callback) {\r | |
24 | var currTime = Ext.now(),\r | |
25 | timeToCall = Math.max(0, 16 - (currTime - lastTime)),\r | |
26 | id = win.setTimeout(function() {\r | |
27 | callback(currTime + timeToCall);\r | |
28 | }, timeToCall);\r | |
29 | lastTime = currTime + timeToCall;\r | |
30 | return id;\r | |
31 | },\r | |
32 | fireHandlers = function() {\r | |
33 | var len = animFrameHandlers.length,\r | |
34 | id, i, handler;\r | |
35 | \r | |
36 | animFrameId = null;\r | |
37 | // Fire all animation frame handlers in one go\r | |
38 | for (i = 0; i < len; i++) {\r | |
39 | handler = animFrameHandlers[i];\r | |
40 | id = handler[3];\r | |
41 | \r | |
42 | // Check if this timer has been canceled; its map entry is going to be removed\r | |
43 | if (animFrameMap[id]) {\r | |
44 | handler[0].apply(handler[1] || global, handler[2] || animFrameNoArgs);\r | |
45 | delete animFrameMap[id];\r | |
46 | }\r | |
47 | }\r | |
48 | \r | |
49 | // Clear all fired animation frame handlers, don't forget that new handlers\r | |
50 | // could have been created in user handler functions called in the loop above\r | |
51 | animFrameHandlers = animFrameHandlers.slice(len);\r | |
52 | },\r | |
53 | fireElevatedHandlers = function() {\r | |
54 | Ext.elevateFunction(fireHandlers);\r | |
55 | },\r | |
56 | \r | |
57 | ExtFunction = {\r | |
58 | /**\r | |
59 | * A very commonly used method throughout the framework. It acts as a wrapper around another method\r | |
60 | * which originally accepts 2 arguments for `name` and `value`.\r | |
61 | * The wrapped function then allows "flexible" value setting of either:\r | |
62 | *\r | |
63 | * - `name` and `value` as 2 arguments\r | |
64 | * - one single object argument with multiple key - value pairs\r | |
65 | *\r | |
66 | * For example:\r | |
67 | *\r | |
68 | * var setValue = Ext.Function.flexSetter(function(name, value) {\r | |
69 | * this[name] = value;\r | |
70 | * });\r | |
71 | *\r | |
72 | * // Afterwards\r | |
73 | * // Setting a single name - value\r | |
74 | * setValue('name1', 'value1');\r | |
75 | *\r | |
76 | * // Settings multiple name - value pairs\r | |
77 | * setValue({\r | |
78 | * name1: 'value1',\r | |
79 | * name2: 'value2',\r | |
80 | * name3: 'value3'\r | |
81 | * });\r | |
82 | *\r | |
83 | * @param {Function} setter The single value setter method.\r | |
84 | * @param {String} setter.name The name of the value being set.\r | |
85 | * @param {Object} setter.value The value being set.\r | |
86 | * @return {Function}\r | |
87 | */\r | |
88 | flexSetter: function(setter) {\r | |
89 | return function(name, value) {\r | |
90 | var k, i;\r | |
91 | \r | |
92 | if (name !== null) {\r | |
93 | if (typeof name !== 'string') {\r | |
94 | for (k in name) {\r | |
95 | if (name.hasOwnProperty(k)) {\r | |
96 | setter.call(this, k, name[k]);\r | |
97 | }\r | |
98 | }\r | |
99 | \r | |
100 | if (Ext.enumerables) {\r | |
101 | for (i = Ext.enumerables.length; i--;) {\r | |
102 | k = Ext.enumerables[i];\r | |
103 | if (name.hasOwnProperty(k)) {\r | |
104 | setter.call(this, k, name[k]);\r | |
105 | }\r | |
106 | }\r | |
107 | }\r | |
108 | } else {\r | |
109 | setter.call(this, name, value);\r | |
110 | }\r | |
111 | }\r | |
112 | \r | |
113 | return this;\r | |
114 | };\r | |
115 | },\r | |
116 | \r | |
117 | /**\r | |
118 | * Create a new function from the provided `fn`, change `this` to the provided scope,\r | |
119 | * optionally overrides arguments for the call. Defaults to the arguments passed by\r | |
120 | * the caller.\r | |
121 | *\r | |
122 | * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}\r | |
123 | * \r | |
124 | * **NOTE:** This method is deprecated. Use the standard `bind` method of JavaScript\r | |
125 | * `Function` instead:\r | |
126 | * \r | |
127 | * function foo () {\r | |
128 | * ...\r | |
129 | * }\r | |
130 | * \r | |
131 | * var fn = foo.bind(this);\r | |
132 | *\r | |
133 | * This method is unavailable natively on IE8 and IE/Quirks but Ext JS provides a\r | |
134 | * "polyfill" to emulate the important features of the standard `bind` method. In\r | |
135 | * particular, the polyfill only provides binding of "this" and optional arguments.\r | |
136 | * \r | |
137 | * @param {Function} fn The function to delegate.\r | |
138 | * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.\r | |
139 | * **If omitted, defaults to the default global environment object (usually the browser window).**\r | |
140 | * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)\r | |
141 | * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,\r | |
142 | * if a number the args are inserted at the specified position.\r | |
143 | * @return {Function} The new function.\r | |
144 | */\r | |
145 | bind: function(fn, scope, args, appendArgs) {\r | |
146 | if (arguments.length === 2) {\r | |
147 | return function() {\r | |
148 | return fn.apply(scope, arguments);\r | |
149 | };\r | |
150 | }\r | |
151 | \r | |
152 | var method = fn,\r | |
153 | slice = Array.prototype.slice;\r | |
154 | \r | |
155 | return function() {\r | |
156 | var callArgs = args || arguments;\r | |
157 | \r | |
158 | if (appendArgs === true) {\r | |
159 | callArgs = slice.call(arguments, 0);\r | |
160 | callArgs = callArgs.concat(args);\r | |
161 | }\r | |
162 | else if (typeof appendArgs === 'number') {\r | |
163 | callArgs = slice.call(arguments, 0); // copy arguments first\r | |
164 | Ext.Array.insert(callArgs, appendArgs, args);\r | |
165 | }\r | |
166 | \r | |
167 | return method.apply(scope || global, callArgs);\r | |
168 | };\r | |
169 | },\r | |
170 | \r | |
171 | /**\r | |
172 | * Captures the given parameters for a later call to `Ext.callback`. This binding is\r | |
173 | * most useful for resolving scopes for example to an `Ext.app.ViewController`.\r | |
174 | *\r | |
175 | * The arguments match that of `Ext.callback` except for the `args` which, if provided\r | |
176 | * to this method, are prepended to any arguments supplied by the eventual caller of\r | |
177 | * the returned function.\r | |
178 | *\r | |
179 | * @return {Function} A function that, when called, uses `Ext.callback` to call the\r | |
180 | * captured `callback`.\r | |
181 | * @since 5.0.0\r | |
182 | */\r | |
183 | bindCallback: function (callback, scope, args, delay, caller) {\r | |
184 | return function () {\r | |
185 | var a = Ext.Array.slice(arguments);\r | |
186 | return Ext.callback(callback, scope, args ? args.concat(a) : a, delay, caller);\r | |
187 | };\r | |
188 | },\r | |
189 | \r | |
190 | /**\r | |
191 | * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.\r | |
192 | * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.\r | |
193 | * This is especially useful when creating callbacks.\r | |
194 | *\r | |
195 | * For example:\r | |
196 | *\r | |
197 | * var originalFunction = function(){\r | |
198 | * alert(Ext.Array.from(arguments).join(' '));\r | |
199 | * };\r | |
200 | *\r | |
201 | * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);\r | |
202 | *\r | |
203 | * callback(); // alerts 'Hello World'\r | |
204 | * callback('by Me'); // alerts 'Hello World by Me'\r | |
205 | *\r | |
206 | * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}\r | |
207 | *\r | |
208 | * @param {Function} fn The original function.\r | |
209 | * @param {Array} args The arguments to pass to new callback.\r | |
210 | * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.\r | |
211 | * @return {Function} The new callback function.\r | |
212 | */\r | |
213 | pass: function(fn, args, scope) {\r | |
214 | if (!Ext.isArray(args)) {\r | |
215 | if (Ext.isIterable(args)) {\r | |
216 | args = Ext.Array.clone(args);\r | |
217 | } else {\r | |
218 | args = args !== undefined ? [args] : [];\r | |
219 | }\r | |
220 | }\r | |
221 | \r | |
222 | return function() {\r | |
223 | var fnArgs = args.slice();\r | |
224 | fnArgs.push.apply(fnArgs, arguments);\r | |
225 | return fn.apply(scope || this, fnArgs);\r | |
226 | };\r | |
227 | },\r | |
228 | \r | |
229 | /**\r | |
230 | * Create an alias to the provided method property with name `methodName` of `object`.\r | |
231 | * Note that the execution scope will still be bound to the provided `object` itself.\r | |
232 | *\r | |
233 | * @param {Object/Function} object\r | |
234 | * @param {String} methodName\r | |
235 | * @return {Function} aliasFn\r | |
236 | */\r | |
237 | alias: function(object, methodName) {\r | |
238 | return function() {\r | |
239 | return object[methodName].apply(object, arguments);\r | |
240 | };\r | |
241 | },\r | |
242 | \r | |
243 | /**\r | |
244 | * Create a "clone" of the provided method. The returned method will call the given\r | |
245 | * method passing along all arguments and the "this" pointer and return its result.\r | |
246 | *\r | |
247 | * @param {Function} method\r | |
248 | * @return {Function} cloneFn\r | |
249 | */\r | |
250 | clone: function(method) {\r | |
251 | return function() {\r | |
252 | return method.apply(this, arguments);\r | |
253 | };\r | |
254 | },\r | |
255 | \r | |
256 | /**\r | |
257 | * Creates an interceptor function. The passed function is called before the original one. If it returns false,\r | |
258 | * the original one is not called. The resulting function returns the results of the original function.\r | |
259 | * The passed function is called with the parameters of the original function. Example usage:\r | |
260 | *\r | |
261 | * var sayHi = function(name){\r | |
262 | * alert('Hi, ' + name);\r | |
263 | * };\r | |
264 | *\r | |
265 | * sayHi('Fred'); // alerts "Hi, Fred"\r | |
266 | *\r | |
267 | * // create a new function that validates input without\r | |
268 | * // directly modifying the original function:\r | |
269 | * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){\r | |
270 | * return name === 'Brian';\r | |
271 | * });\r | |
272 | *\r | |
273 | * sayHiToFriend('Fred'); // no alert\r | |
274 | * sayHiToFriend('Brian'); // alerts "Hi, Brian"\r | |
275 | *\r | |
276 | * @param {Function} origFn The original function.\r | |
277 | * @param {Function} newFn The function to call before the original.\r | |
278 | * @param {Object} [scope] The scope (`this` reference) in which the passed function is executed.\r | |
279 | * **If omitted, defaults to the scope in which the original function is called or the browser window.**\r | |
280 | * @param {Object} [returnValue=null] The value to return if the passed function return `false`.\r | |
281 | * @return {Function} The new function.\r | |
282 | */\r | |
283 | createInterceptor: function(origFn, newFn, scope, returnValue) {\r | |
284 | if (!Ext.isFunction(newFn)) {\r | |
285 | return origFn;\r | |
286 | } else {\r | |
287 | returnValue = Ext.isDefined(returnValue) ? returnValue : null;\r | |
288 | \r | |
289 | return function() {\r | |
290 | var me = this,\r | |
291 | args = arguments;\r | |
292 | \r | |
293 | return (newFn.apply(scope || me || global, args) !== false) ?\r | |
294 | origFn.apply(me || global, args) : returnValue;\r | |
295 | };\r | |
296 | }\r | |
297 | },\r | |
298 | \r | |
299 | /**\r | |
300 | * Creates a delegate (callback) which, when called, executes after a specific delay.\r | |
301 | *\r | |
302 | * @param {Function} fn The function which will be called on a delay when the returned function is called.\r | |
303 | * Optionally, a replacement (or additional) argument list may be specified.\r | |
304 | * @param {Number} delay The number of milliseconds to defer execution by whenever called.\r | |
305 | * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.\r | |
306 | * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)\r | |
307 | * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,\r | |
308 | * if a number the args are inserted at the specified position.\r | |
309 | * @return {Function} A function which, when called, executes the original function after the specified delay.\r | |
310 | */\r | |
311 | createDelayed: function(fn, delay, scope, args, appendArgs) {\r | |
312 | if (scope || args) {\r | |
313 | fn = Ext.Function.bind(fn, scope, args, appendArgs);\r | |
314 | }\r | |
315 | \r | |
316 | return function() {\r | |
317 | var me = this,\r | |
318 | args = Array.prototype.slice.call(arguments);\r | |
319 | \r | |
320 | setTimeout(function() {\r | |
321 | if (Ext.elevateFunction) {\r | |
322 | Ext.elevateFunction(fn, me, args);\r | |
323 | } else {\r | |
324 | fn.apply(me, args);\r | |
325 | }\r | |
326 | }, delay);\r | |
327 | };\r | |
328 | },\r | |
329 | \r | |
330 | /**\r | |
331 | * Calls this function after the number of milliseconds specified, optionally in a specific scope. Example usage:\r | |
332 | *\r | |
333 | * var sayHi = function(name){\r | |
334 | * alert('Hi, ' + name);\r | |
335 | * }\r | |
336 | *\r | |
337 | * // executes immediately:\r | |
338 | * sayHi('Fred');\r | |
339 | *\r | |
340 | * // executes after 2 seconds:\r | |
341 | * Ext.Function.defer(sayHi, 2000, this, ['Fred']);\r | |
342 | *\r | |
343 | * // this syntax is sometimes useful for deferring\r | |
344 | * // execution of an anonymous function:\r | |
345 | * Ext.Function.defer(function(){\r | |
346 | * alert('Anonymous');\r | |
347 | * }, 100);\r | |
348 | *\r | |
349 | * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}\r | |
350 | *\r | |
351 | * @param {Function} fn The function to defer.\r | |
352 | * @param {Number} millis The number of milliseconds for the `setTimeout` call\r | |
353 | * (if less than or equal to 0 the function is executed immediately).\r | |
354 | * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.\r | |
355 | * **If omitted, defaults to the browser window.**\r | |
356 | * @param {Array} [args] Overrides arguments for the call. Defaults to the arguments passed by the caller.\r | |
357 | * @param {Boolean/Number} [appendArgs=false] If `true` args are appended to call args instead of overriding,\r | |
358 | * or, if a number, then the args are inserted at the specified position.\r | |
359 | * @return {Number} The timeout id that can be used with `clearTimeout`.\r | |
360 | */\r | |
361 | defer: function(fn, millis, scope, args, appendArgs) {\r | |
362 | fn = Ext.Function.bind(fn, scope, args, appendArgs);\r | |
363 | if (millis > 0) {\r | |
364 | return setTimeout(function () {\r | |
365 | if (Ext.elevateFunction) {\r | |
366 | Ext.elevateFunction(fn);\r | |
367 | } else {\r | |
368 | fn();\r | |
369 | }\r | |
370 | }, millis);\r | |
371 | }\r | |
372 | fn();\r | |
373 | return 0;\r | |
374 | },\r | |
375 | \r | |
376 | /**\r | |
377 | * Calls this function repeatedly at a given interval, optionally in a specific scope.\r | |
378 | *\r | |
379 | * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}\r | |
380 | *\r | |
381 | * @param {Function} fn The function to defer.\r | |
382 | * @param {Number} millis The number of milliseconds for the `setInterval` call\r | |
383 | * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.\r | |
384 | * **If omitted, defaults to the browser window.**\r | |
385 | * @param {Array} [args] Overrides arguments for the call. Defaults to the arguments passed by the caller.\r | |
386 | * @param {Boolean/Number} [appendArgs=false] If `true` args are appended to call args instead of overriding,\r | |
387 | * or, if a number, then the args are inserted at the specified position.\r | |
388 | * @return {Number} The interval id that can be used with `clearInterval`.\r | |
389 | */\r | |
390 | interval: function(fn, millis, scope, args, appendArgs) {\r | |
391 | fn = Ext.Function.bind(fn, scope, args, appendArgs);\r | |
392 | return setInterval(function () {\r | |
393 | if (Ext.elevateFunction) {\r | |
394 | Ext.elevateFunction(fn);\r | |
395 | } else {\r | |
396 | fn();\r | |
397 | }\r | |
398 | }, millis);\r | |
399 | },\r | |
400 | \r | |
401 | /**\r | |
402 | * Create a combined function call sequence of the original function + the passed function.\r | |
403 | * The resulting function returns the results of the original function.\r | |
404 | * The passed function is called with the parameters of the original function. Example usage:\r | |
405 | *\r | |
406 | * var sayHi = function(name){\r | |
407 | * alert('Hi, ' + name);\r | |
408 | * };\r | |
409 | *\r | |
410 | * sayHi('Fred'); // alerts "Hi, Fred"\r | |
411 | *\r | |
412 | * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){\r | |
413 | * alert('Bye, ' + name);\r | |
414 | * });\r | |
415 | *\r | |
416 | * sayGoodbye('Fred'); // both alerts show\r | |
417 | *\r | |
418 | * @param {Function} originalFn The original function.\r | |
419 | * @param {Function} newFn The function to sequence.\r | |
420 | * @param {Object} [scope] The scope (`this` reference) in which the passed function is executed.\r | |
421 | * If omitted, defaults to the scope in which the original function is called or the\r | |
422 | * default global environment object (usually the browser window).\r | |
423 | * @return {Function} The new function.\r | |
424 | */\r | |
425 | createSequence: function(originalFn, newFn, scope) {\r | |
426 | if (!newFn) {\r | |
427 | return originalFn;\r | |
428 | }\r | |
429 | else {\r | |
430 | return function() {\r | |
431 | var result = originalFn.apply(this, arguments);\r | |
432 | newFn.apply(scope || this, arguments);\r | |
433 | return result;\r | |
434 | };\r | |
435 | }\r | |
436 | },\r | |
437 | \r | |
438 | /**\r | |
439 | * Creates a delegate function, optionally with a bound scope which, when called, buffers\r | |
440 | * the execution of the passed function for the configured number of milliseconds.\r | |
441 | * If called again within that period, the impending invocation will be canceled, and the\r | |
442 | * timeout period will begin again.\r | |
443 | *\r | |
444 | * @param {Function} fn The function to invoke on a buffered timer.\r | |
445 | * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the\r | |
446 | * function.\r | |
447 | * @param {Object} [scope] The scope (`this` reference) in which.\r | |
448 | * the passed function is executed. If omitted, defaults to the scope specified by the caller.\r | |
449 | * @param {Array} [args] Override arguments for the call. Defaults to the arguments\r | |
450 | * passed by the caller.\r | |
451 | * @return {Function} A function which invokes the passed function after buffering for the specified time.\r | |
452 | */\r | |
453 | createBuffered: function(fn, buffer, scope, args) {\r | |
454 | var timerId;\r | |
455 | \r | |
456 | return function() {\r | |
457 | var callArgs = args || Array.prototype.slice.call(arguments, 0),\r | |
458 | me = scope || this;\r | |
459 | \r | |
460 | if (timerId) {\r | |
461 | clearTimeout(timerId);\r | |
462 | }\r | |
463 | \r | |
464 | timerId = setTimeout(function(){\r | |
465 | if (Ext.elevateFunction) {\r | |
466 | Ext.elevateFunction(fn, me, callArgs);\r | |
467 | } else {\r | |
468 | fn.apply(me, callArgs);\r | |
469 | }\r | |
470 | }, buffer);\r | |
471 | };\r | |
472 | },\r | |
473 | \r | |
474 | /**\r | |
475 | * Creates a wrapped function that, when invoked, defers execution until the next\r | |
476 | * animation frame\r | |
477 | * @private\r | |
478 | * @param {Function} fn The function to call.\r | |
479 | * @param {Object} [scope] The scope (`this` reference) in which the function is executed. Defaults to the window object.\r | |
480 | * @param {Array} [args] The argument list to pass to the function.\r | |
481 | * @param {Number} [queueStrategy=3] A bit flag that indicates how multiple calls to\r | |
482 | * the returned function within the same animation frame should be handled.\r | |
483 | *\r | |
484 | * - 1: All calls will be queued - FIFO order\r | |
485 | * - 2: Only the first call will be queued\r | |
486 | * - 3: The last call will replace all previous calls\r | |
487 | *\r | |
488 | * @return {Function}\r | |
489 | */\r | |
490 | createAnimationFrame: function(fn, scope, args, queueStrategy) {\r | |
491 | var timerId;\r | |
492 | \r | |
493 | queueStrategy = queueStrategy || 3;\r | |
494 | \r | |
495 | return function() {\r | |
496 | var callArgs = args || Array.prototype.slice.call(arguments, 0);\r | |
497 | \r | |
498 | scope = scope || this;\r | |
499 | \r | |
500 | if (queueStrategy === 3 && timerId) {\r | |
501 | ExtFunction.cancelAnimationFrame(timerId);\r | |
502 | }\r | |
503 | \r | |
504 | if ((queueStrategy & 1) || !timerId) {\r | |
505 | timerId = ExtFunction.requestAnimationFrame(function() {\r | |
506 | timerId = null;\r | |
507 | fn.apply(scope, callArgs);\r | |
508 | });\r | |
509 | }\r | |
510 | };\r | |
511 | },\r | |
512 | \r | |
513 | /**\r | |
514 | * @private\r | |
515 | * Schedules the passed function to be called on the next animation frame.\r | |
516 | * @param {Function} fn The function to call.\r | |
517 | * @param {Object} [scope] The scope (`this` reference) in which the function is executed. Defaults to the window object.\r | |
518 | * @param {Mixed[]} [args] The argument list to pass to the function.\r | |
519 | *\r | |
520 | * @return {Number} Timer id for the new animation frame to use when canceling it.\r | |
521 | */\r | |
522 | requestAnimationFrame: function(fn, scope, args) {\r | |
523 | var id = ++idSource, // Ids start at 1\r | |
524 | handler = Array.prototype.slice.call(arguments, 0);\r | |
525 | \r | |
526 | handler[3] = id;\r | |
527 | animFrameMap[id] = 1; // A flag to indicate that the timer exists\r | |
528 | \r | |
529 | // We might be in fireHandlers at this moment but this new entry will not\r | |
530 | // be executed until the next frame\r | |
531 | animFrameHandlers.push(handler);\r | |
532 | \r | |
533 | if (!animFrameId) {\r | |
534 | animFrameId = requestAnimFrame(Ext.elevateFunction ? fireElevatedHandlers : fireHandlers);\r | |
535 | }\r | |
536 | return id;\r | |
537 | },\r | |
538 | \r | |
539 | cancelAnimationFrame: function(id) {\r | |
540 | // Don't remove any handlers from animFrameHandlers array, because\r | |
541 | // the might be in use at the moment (when cancelAnimationFrame is called).\r | |
542 | // Just remove the handler id from the map so it will not be executed\r | |
543 | delete animFrameMap[id];\r | |
544 | },\r | |
545 | \r | |
546 | /**\r | |
547 | * Creates a throttled version of the passed function which, when called repeatedly and\r | |
548 | * rapidly, invokes the passed function only after a certain interval has elapsed since the\r | |
549 | * previous invocation.\r | |
550 | *\r | |
551 | * This is useful for wrapping functions which may be called repeatedly, such as\r | |
552 | * a handler of a mouse move event when the processing is expensive.\r | |
553 | *\r | |
554 | * @param {Function} fn The function to execute at a regular time interval.\r | |
555 | * @param {Number} interval The interval in milliseconds on which the passed function is executed.\r | |
556 | * @param {Object} [scope] The scope (`this` reference) in which\r | |
557 | * the passed function is executed. If omitted, defaults to the scope specified by the caller.\r | |
558 | * @return {Function} A function which invokes the passed function at the specified interval.\r | |
559 | */\r | |
560 | createThrottled: function(fn, interval, scope) {\r | |
561 | var lastCallTime = 0,\r | |
562 | elapsed,\r | |
563 | lastArgs,\r | |
564 | timer,\r | |
565 | execute = function() {\r | |
566 | if (Ext.elevateFunction) {\r | |
567 | Ext.elevateFunction(fn, scope, lastArgs);\r | |
568 | } else {\r | |
569 | fn.apply(scope, lastArgs);\r | |
570 | }\r | |
571 | lastCallTime = Ext.now();\r | |
572 | timer = null;\r | |
573 | };\r | |
574 | \r | |
575 | return function() {\r | |
576 | // Use scope of last call unless the creator specified a scope\r | |
577 | if (!scope) {\r | |
578 | scope = this;\r | |
579 | }\r | |
580 | elapsed = Ext.now() - lastCallTime;\r | |
581 | lastArgs = arguments;\r | |
582 | \r | |
583 | // If this is the first invocation, or the throttle interval has been reached, clear any\r | |
584 | // pending invocation, and call the target function now.\r | |
585 | if (elapsed >= interval) {\r | |
586 | clearTimeout(timer);\r | |
587 | execute();\r | |
588 | }\r | |
589 | // Throttle interval has not yet been reached. Only set the timer to fire if not already set.\r | |
590 | else if (!timer) {\r | |
591 | timer = Ext.defer(execute, interval - elapsed);\r | |
592 | }\r | |
593 | };\r | |
594 | },\r | |
595 | \r | |
596 | /**\r | |
597 | * Wraps the passed function in a barrier function which will call the passed function after the passed number of invocations.\r | |
598 | * @param {Number} count The number of invocations which will result in the calling of the passed function.\r | |
599 | * @param {Function} fn The function to call after the required number of invocations.\r | |
600 | * @param {Object} scope The scope (`this` reference) in which the function will be called.\r | |
601 | */ \r | |
602 | createBarrier: function(count, fn, scope) {\r | |
603 | return function() {\r | |
604 | if (!--count) {\r | |
605 | fn.apply(scope, arguments);\r | |
606 | }\r | |
607 | };\r | |
608 | },\r | |
609 | \r | |
610 | /**\r | |
611 | * Adds behavior to an existing method that is executed before the\r | |
612 | * original behavior of the function. For example:\r | |
613 | * \r | |
614 | * var soup = {\r | |
615 | * contents: [],\r | |
616 | * add: function(ingredient) {\r | |
617 | * this.contents.push(ingredient);\r | |
618 | * }\r | |
619 | * };\r | |
620 | * Ext.Function.interceptBefore(soup, "add", function(ingredient){\r | |
621 | * if (!this.contents.length && ingredient !== "water") {\r | |
622 | * // Always add water to start with\r | |
623 | * this.contents.push("water");\r | |
624 | * }\r | |
625 | * });\r | |
626 | * soup.add("onions");\r | |
627 | * soup.add("salt");\r | |
628 | * soup.contents; // will contain: water, onions, salt\r | |
629 | * \r | |
630 | * @param {Object} object The target object\r | |
631 | * @param {String} methodName Name of the method to override\r | |
632 | * @param {Function} fn Function with the new behavior. It will\r | |
633 | * be called with the same arguments as the original method. The\r | |
634 | * return value of this function will be the return value of the\r | |
635 | * new method.\r | |
636 | * @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.\r | |
637 | * @return {Function} The new function just created.\r | |
638 | */\r | |
639 | interceptBefore: function(object, methodName, fn, scope) {\r | |
640 | var method = object[methodName] || Ext.emptyFn;\r | |
641 | \r | |
642 | return (object[methodName] = function() {\r | |
643 | var ret = fn.apply(scope || this, arguments);\r | |
644 | method.apply(this, arguments);\r | |
645 | \r | |
646 | return ret;\r | |
647 | });\r | |
648 | },\r | |
649 | \r | |
650 | /**\r | |
651 | * Adds behavior to an existing method that is executed after the\r | |
652 | * original behavior of the function. For example:\r | |
653 | * \r | |
654 | * var soup = {\r | |
655 | * contents: [],\r | |
656 | * add: function(ingredient) {\r | |
657 | * this.contents.push(ingredient);\r | |
658 | * }\r | |
659 | * };\r | |
660 | * Ext.Function.interceptAfter(soup, "add", function(ingredient){\r | |
661 | * // Always add a bit of extra salt\r | |
662 | * this.contents.push("salt");\r | |
663 | * });\r | |
664 | * soup.add("water");\r | |
665 | * soup.add("onions");\r | |
666 | * soup.contents; // will contain: water, salt, onions, salt\r | |
667 | * \r | |
668 | * @param {Object} object The target object\r | |
669 | * @param {String} methodName Name of the method to override\r | |
670 | * @param {Function} fn Function with the new behavior. It will\r | |
671 | * be called with the same arguments as the original method. The\r | |
672 | * return value of this function will be the return value of the\r | |
673 | * new method.\r | |
674 | * @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.\r | |
675 | * @return {Function} The new function just created.\r | |
676 | */\r | |
677 | interceptAfter: function(object, methodName, fn, scope) {\r | |
678 | var method = object[methodName] || Ext.emptyFn;\r | |
679 | \r | |
680 | return (object[methodName] = function() {\r | |
681 | method.apply(this, arguments);\r | |
682 | return fn.apply(scope || this, arguments);\r | |
683 | });\r | |
684 | },\r | |
685 | \r | |
686 | makeCallback: function (callback, scope) {\r | |
687 | //<debug>\r | |
688 | if (!scope[callback]) {\r | |
689 | if (scope.$className) {\r | |
690 | Ext.raise('No method "' + callback + '" on ' + scope.$className);\r | |
691 | }\r | |
692 | Ext.raise('No method "' + callback + '"');\r | |
693 | }\r | |
694 | //</debug>\r | |
695 | \r | |
696 | return function () {\r | |
697 | return scope[callback].apply(scope, arguments);\r | |
698 | };\r | |
699 | },\r | |
700 | \r | |
701 | /**\r | |
702 | * Returns a wrapper function that caches the return value for previously\r | |
703 | * processed function argument(s).\r | |
704 | *\r | |
705 | * For example:\r | |
706 | *\r | |
707 | * function factorial (value) {\r | |
708 | * var ret = value;\r | |
709 | *\r | |
710 | * while (--value > 1) {\r | |
711 | * ret *= value;\r | |
712 | * }\r | |
713 | *\r | |
714 | * return ret;\r | |
715 | * }\r | |
716 | *\r | |
717 | * Each call to `factorial` will loop and multiply to produce the answer. Using\r | |
718 | * this function we can wrap the above and cache its answers:\r | |
719 | *\r | |
720 | * factorial = Ext.Function.memoize(factorial);\r | |
721 | *\r | |
722 | * The returned function operates in the same manner as before, but results are\r | |
723 | * stored in a cache to avoid calling the wrapped function when given the same\r | |
724 | * arguments.\r | |
725 | *\r | |
726 | * var x = factorial(20); // first time; call real factorial()\r | |
727 | * var y = factorial(20); // second time; return value from first call\r | |
728 | *\r | |
729 | * To support multi-argument methods, you will need to provide a `hashFn`.\r | |
730 | *\r | |
731 | * function permutation (n, k) {\r | |
732 | * return factorial(n) / factorial(n - k);\r | |
733 | * }\r | |
734 | *\r | |
735 | * permutation = Ext.Function.memoize(permutation, null, function (n, k) {\r | |
736 | * n + '-' + k;\r | |
737 | * });\r | |
738 | *\r | |
739 | * In this case, the `memoize` of `factorial` is sufficient optimization, but the\r | |
740 | * example is simply to illustrate how to generate a unique key for an expensive,\r | |
741 | * multi-argument method.\r | |
742 | *\r | |
743 | * **IMPORTANT**: This cache is unbounded so be cautious of memory leaks if the\r | |
744 | * `memoize`d function is kept indefinitely or is given an unbounded set of\r | |
745 | * possible arguments.\r | |
746 | *\r | |
747 | * @param {Function} fn Function to wrap.\r | |
748 | * @param {Object} scope Optional scope in which to execute the wrapped function.\r | |
749 | * @param {Function} hashFn Optional function used to compute a hash key for\r | |
750 | * storing the result, based on the arguments to the original function.\r | |
751 | * @return {Function} The caching wrapper function.\r | |
752 | * @since 6.0.0\r | |
753 | */\r | |
754 | memoize: function(fn, scope, hashFn) {\r | |
755 | var memo = {},\r | |
756 | isFunc = hashFn && Ext.isFunction(hashFn);\r | |
757 | \r | |
758 | return function (value) {\r | |
759 | var key = isFunc ? hashFn.apply(scope, arguments) : value;\r | |
760 | \r | |
761 | if (!(key in memo)) {\r | |
762 | memo[key] = fn.apply(scope, arguments);\r | |
763 | }\r | |
764 | \r | |
765 | return memo[key];\r | |
766 | };\r | |
767 | }\r | |
768 | }; // ExtFunction\r | |
769 | \r | |
770 | /**\r | |
771 | * @member Ext\r | |
772 | * @method asap\r | |
773 | * Schedules the specified callback function to be executed on the next turn of the\r | |
774 | * event loop. Where available, this method uses the browser's `setImmediate` API. If\r | |
775 | * not available, this method substitutes `setTimeout(0)`. Though not a perfect\r | |
776 | * replacement for `setImmediate` it is sufficient for many use cases.\r | |
777 | *\r | |
778 | * For more details see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate).\r | |
779 | *\r | |
780 | * @param {Function} fn Callback function.\r | |
781 | * @param {Object} [scope] The scope for the callback (`this` pointer).\r | |
782 | * @param {Mixed[]} [parameters] Additional parameters to pass to `fn`.\r | |
783 | * @return {Number} A cancelation id for `{@link Ext#asapCancel}`.\r | |
784 | */\r | |
785 | Ext.asap = hasImmediate ?\r | |
786 | function (fn, scope, parameters) {\r | |
787 | if (scope != null || parameters != null) {\r | |
788 | fn = ExtFunction.bind(fn, scope, parameters);\r | |
789 | }\r | |
790 | return setImmediate(function () {\r | |
791 | if (Ext.elevateFunction) {\r | |
792 | Ext.elevateFunction(fn);\r | |
793 | } else {\r | |
794 | fn();\r | |
795 | }\r | |
796 | });\r | |
797 | } :\r | |
798 | function (fn, scope, parameters) {\r | |
799 | if (scope != null || parameters != null) {\r | |
800 | fn = ExtFunction.bind(fn, scope, parameters);\r | |
801 | }\r | |
802 | return setTimeout(function () {\r | |
803 | if (Ext.elevateFunction) {\r | |
804 | Ext.elevateFunction(fn);\r | |
805 | } else {\r | |
806 | fn();\r | |
807 | }\r | |
808 | }, 0, true);\r | |
809 | },\r | |
810 | \r | |
811 | /**\r | |
812 | * @member Ext\r | |
813 | * @method asapCancel\r | |
814 | * Cancels a previously scheduled call to `{@link Ext#asap}`.\r | |
815 | *\r | |
816 | * var asapId = Ext.asap(me.method, me);\r | |
817 | * ...\r | |
818 | *\r | |
819 | * if (nevermind) {\r | |
820 | * Ext.apasCancel(asapId);\r | |
821 | * }\r | |
822 | *\r | |
823 | * @param {Number} id The id returned by `{@link Ext#asap}`.\r | |
824 | */\r | |
825 | Ext.asapCancel = hasImmediate ?\r | |
826 | function(id) {\r | |
827 | clearImmediate(id);\r | |
828 | } : function(id) {\r | |
829 | clearTimeout(id);\r | |
830 | };\r | |
831 | \r | |
832 | /**\r | |
833 | * @method\r | |
834 | * @member Ext\r | |
835 | * @inheritdoc Ext.Function#defer\r | |
836 | */\r | |
837 | Ext.defer = ExtFunction.defer;\r | |
838 | \r | |
839 | /**\r | |
840 | * @method\r | |
841 | * @member Ext\r | |
842 | * @inheritdoc Ext.Function#interval\r | |
843 | */\r | |
844 | Ext.interval = ExtFunction.interval;\r | |
845 | \r | |
846 | /**\r | |
847 | * @method\r | |
848 | * @member Ext\r | |
849 | * @inheritdoc Ext.Function#pass\r | |
850 | */\r | |
851 | Ext.pass = ExtFunction.pass;\r | |
852 | \r | |
853 | /**\r | |
854 | * @method\r | |
855 | * @member Ext\r | |
856 | * @inheritdoc Ext.Function#bind\r | |
857 | */\r | |
858 | Ext.bind = ExtFunction.bind;\r | |
859 | \r | |
860 | Ext.deferCallback = ExtFunction.requestAnimationFrame;\r | |
861 | \r | |
862 | return ExtFunction;\r | |
863 | })();\r |