]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/lang/Function.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / lang / Function.js
CommitLineData
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
7Ext.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