/** * @class Ext.Function * * A collection of useful static methods to deal with function callbacks. * @singleton */ /* eslint-disable indent */Ext.Function = (function() {// @define Ext.lang.Function// @define Ext.Function// @require Ext// @require Ext.lang.Array var lastTime = 0, animFrameId, animFrameHandlers = [], animFrameNoArgs = [], idSource = 0, animFrameMap = {}, slice = Array.prototype.slice, win = window, global = Ext.global, // We disable setImmediate in unit tests because it derails internal Jasmine queue hasImmediate = !Ext.disableImmediate && !!(global.setImmediate && global.clearImmediate), requestAnimFrame = win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.oRequestAnimationFrame || function(callback) { var currTime = Ext.now(), timeToCall = Math.max(0, 16 - (currTime - lastTime)), timerFn = function() { callback(currTime + timeToCall); }, id; //<debug> timerFn.$origFn = callback.$origFn || callback; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; //</debug> id = win.setTimeout(timerFn, timeToCall); lastTime = currTime + timeToCall; return id; }, fireHandlers = function() { var len = animFrameHandlers.length, id, i, handler; animFrameId = null; //<debug> var timer; // eslint-disable-line vars-on-top //</debug> // Fire all animation frame handlers in one go for (i = 0; i < len; i++) { handler = animFrameHandlers[i]; id = handler[3]; // Check if this timer has been canceled; its map entry is going to be removed if (animFrameMap[id]) { delete animFrameMap[id]; //<debug> timer = Ext.Timer.get(id, 'raf'); if (timer) { timer.tick(); } //</debug> handler[0].apply(handler[1] || global, handler[2] || animFrameNoArgs); //<debug> if (timer) { timer.tock(); } //</debug> } } // Clear all fired animation frame handlers, don't forget that new handlers // could have been created in user handler functions called in the loop above animFrameHandlers = animFrameHandlers.slice(len); }, fireElevatedHandlers = function() { Ext.elevate(fireHandlers); }, ExtFunction = { /** * A very commonly used method throughout the framework. It acts as a wrapper around * another method which originally accepts 2 arguments for `name` and `value`. * The wrapped function then allows "flexible" value setting of either: * * - `name` and `value` as 2 arguments * - one single object argument with multiple key - value pairs * * For example: * * var setValue = Ext.Function.flexSetter(function(name, value) { * this[name] = value; * }); * * // Afterwards * // Setting a single name - value * setValue('name1', 'value1'); * * // Settings multiple name - value pairs * setValue({ * name1: 'value1', * name2: 'value2', * name3: 'value3' * }); * * @param {Function} setter The single value setter method. * @param {String} setter.name The name of the value being set. * @param {Object} setter.value The value being set. * @return {Function} */ flexSetter: function(setter) { return function(name, value) { var k, i; if (name !== null) { if (typeof name !== 'string') { for (k in name) { if (name.hasOwnProperty(k)) { setter.call(this, k, name[k]); } } if (Ext.enumerables) { for (i = Ext.enumerables.length; i--;) { k = Ext.enumerables[i]; if (name.hasOwnProperty(k)) { setter.call(this, k, name[k]); } } } } else { setter.call(this, name, value); } } return this; }; }, /** * Create a new function from the provided `fn`, change `this` to the provided scope, * optionally overrides arguments for the call. Defaults to the arguments passed by * the caller. * * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind} * * **NOTE:** This method is similar to the native `bind()` method. The major difference * is in the way the parameters are passed. This method expects an array of parameters, * and if supplied, it does not automatically pass forward parameters from the bound * function: * * function foo (a, b, c) { * console.log(a, b, c); * } * * var nativeFn = foo.bind(this, 1, 2); * var extFn = Ext.Function.bind(foo, this, [1, 2]); * * nativeFn(3); // 1, 2, 3 * extFn(3); // 1, 2, undefined * * This method is unavailable natively on IE8 and IE/Quirks but Ext JS provides a * "polyfill" to emulate the important features of the standard `bind` method. In * particular, the polyfill only provides binding of "this" and optional arguments. * * @param {Function} fn The function to delegate. * @param {Object} [scope] The scope (`this` reference) in which the function * is executed. * **If omitted, defaults to the global environment object (usually the browser `window`).** * @param {Array} [args] Overrides arguments for the call. (Defaults to * the arguments passed by the caller). * @param {Boolean/Number} [appendArgs] if `true` the `args` are appended to the * arguments passed to the returned wrapper (by default these arguments are ignored). * If a number then the `args` are inserted at the specified position. * @return {Function} The bound wrapper function. */ bind: function(fn, scope, args, appendArgs) { // Function.prototype.bind is polyfilled in IE8, otherwise native if (arguments.length <= 2) { return fn.bind(scope); } var method = fn; // eslint-disable-line vars-on-top return function() { var callArgs = args || arguments; if (appendArgs === true) { callArgs = slice.call(arguments, 0); callArgs = callArgs.concat(args); } else if (typeof appendArgs === 'number') { callArgs = slice.call(arguments, 0); // copy arguments first Ext.Array.insert(callArgs, appendArgs, args); } return method.apply(scope || global, callArgs); }; }, /** * Captures the given parameters for a later call to `Ext.callback`. This binding is * most useful for resolving scopes for example to an `Ext.app.ViewController`. * * The arguments match that of `Ext.callback` except for the `args` which, if provided * to this method, are prepended to any arguments supplied by the eventual caller of * the returned function. * * @return {Function} A function that, when called, uses `Ext.callback` to call the * captured `callback`. * @since 5.0.0 */ bindCallback: function(callback, scope, args, delay, caller) { return function() { var a = slice.call(arguments); return Ext.callback(callback, scope, args ? args.concat(a) : a, delay, caller); }; }, /** * Create a new function from the provided `fn`, the arguments of which are pre-set * to `args`. New arguments passed to the newly created callback when it's invoked * are appended after the pre-set ones. * This is especially useful when creating callbacks. * * For example: * * var originalFunction = function(){ * alert(Ext.Array.from(arguments).join(' ')); * }; * * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']); * * callback(); // alerts 'Hello World' * callback('by Me'); // alerts 'Hello World by Me' * * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass} * * @param {Function} fn The original function. * @param {Array} args The arguments to pass to new callback. * @param {Object} scope (optional) The scope (`this` reference) in which the function * is executed. * @return {Function} The new callback function. */ pass: function(fn, args, scope) { if (!Ext.isArray(args)) { if (Ext.isIterable(args)) { args = Ext.Array.clone(args); } else { args = args !== undefined ? [args] : []; } } return function() { var fnArgs = args.slice(); fnArgs.push.apply(fnArgs, arguments); return fn.apply(scope || this, fnArgs); }; }, /** * Create an alias to the provided method property with name `methodName` of `object`. * Note that the execution scope will still be bound to the provided `object` itself. * * @param {Object/Function} object * @param {String} methodName * @return {Function} aliasFn */ alias: function(object, methodName) { return function() { return object[methodName].apply(object, arguments); }; }, /** * Create a "clone" of the provided method. The returned method will call the given * method passing along all arguments and the "this" pointer and return its result. * * @param {Function} method * @return {Function} cloneFn */ clone: function(method) { var newMethod, prop; newMethod = function() { return method.apply(this, arguments); }; for (prop in method) { if (method.hasOwnProperty(prop)) { newMethod[prop] = method[prop]; } } return newMethod; }, /** * Creates an interceptor function. The passed function is called before the original one. * If it returns false, the original one is not called. The resulting function returns * the results of the original function. The passed function is called with the parameters * of the original function. Example usage: * * var sayHi = function(name){ * alert('Hi, ' + name); * }; * * sayHi('Fred'); // alerts "Hi, Fred" * * // create a new function that validates input without * // directly modifying the original function: * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){ * return name === 'Brian'; * }); * * sayHiToFriend('Fred'); // no alert * sayHiToFriend('Brian'); // alerts "Hi, Brian" * * @param {Function} origFn The original function. * @param {Function} newFn The function to call before the original. * @param {Object} [scope] The scope (`this` reference) in which the passed function * is executed. **If omitted, defaults to the scope in which the original function * is called or the browser window.** * @param {Object} [returnValue=null] The value to return if the passed function return * `false`. * @return {Function} The new function. */ createInterceptor: function(origFn, newFn, scope, returnValue) { if (!Ext.isFunction(newFn)) { return origFn; } else { returnValue = Ext.isDefined(returnValue) ? returnValue : null; return function() { var me = this, args = arguments; return (newFn.apply(scope || me || global, args) !== false) ? origFn.apply(me || global, args) : returnValue; }; } }, /** * Creates a delegate (callback) which, when called, executes after a specific delay. * * @param {Function} fn The function which will be called on a delay when the returned * function is called. Optionally, a replacement (or additional) argument list * may be specified. * @param {Number} delay The number of milliseconds to defer execution by whenever called. * @param {Object} scope (optional) The scope (`this` reference) used by the function * at execution time. * @param {Array} args (optional) Override arguments for the call. * (Defaults to the arguments passed by the caller) * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args * instead of overriding, if a number the args are inserted at the specified position. * @return {Function} A function which, when called, executes the original function * after the specified delay. */ createDelayed: function(fn, delay, scope, args, appendArgs) { var boundFn = fn; if (scope || args) { boundFn = Ext.Function.bind(fn, scope, args, appendArgs); } return function() { var me = this, args = slice.call(arguments), timerFn, timerId; //<debug> var timer; // eslint-disable-line vars-on-top, one-var //</debug> timerFn = function() { Ext.elevate(boundFn, me, args //<debug> , timer // eslint-disable-line comma-style //</debug> ); }; timerId = setTimeout(timerFn, delay); //<debug> timerFn.$origFn = fn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; timer = Ext.Timer.created('timeout', timerId, { type: 'createDelayed', fn: fn, timerFn: timerFn }); //</debug> }; }, /** * Calls function `fn` after the number of milliseconds specified, optionally with * a specific `scope` (`this` pointer). * * Example usage: * * var sayHi = function(name) { * alert('Hi, ' + name); * } * * // executes immediately: * sayHi('Fred'); * * // executes after 2 seconds: * Ext.defer(sayHi, 2000, this, ['Fred']); * * The following syntax is useful for scheduling anonymous functions: * * Ext.defer(function() { * alert('Anonymous'); * }, 100); * * NOTE: The `Ext.Function.defer()` method is an alias for `Ext.defer()`. * * @param {Function} fn The function to defer. * @param {Number} millis The number of milliseconds for the `setTimeout` call * (if less than or equal to 0 the function is executed immediately). * @param {Object} scope (optional) The scope (`this` reference) in which the function * is executed. **If omitted, defaults to the browser window.** * @param {Array} [args] Overrides arguments for the call. Defaults to the arguments passed * by the caller. * @param {Boolean/Number} [appendArgs=false] If `true` args are appended to call args * instead of overriding, or, if a number, then the args are inserted at the specified * position. * @return {Number} The timeout id that can be used with `Ext.undefer`. */ defer: function(fn, millis, scope, args, appendArgs) { var timerId = 0, timerFn, boundFn; //<debug> var timer; // eslint-disable-line vars-on-top, one-var //</debug> if (!scope && !args && !appendArgs) { boundFn = fn; } else { boundFn = Ext.Function.bind(fn, scope, args, appendArgs); } if (millis > 0) { timerFn = function() { Ext.elevate(boundFn //<debug> , null, null, timer // eslint-disable-line comma-style //</debug> ); }; timerId = setTimeout(timerFn, millis); //<debug> timerFn.$origFn = fn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; timer = Ext.Timer.created('timeout', timerId, { type: 'defer', fn: fn, timerFn: timerFn }); //</debug> } else { boundFn(); } return timerId; }, /** * Calls the function `fn` repeatedly at a given interval, optionally with a * specific `scope` (`this` pointer). * * var sayHi = function(name) { * console.log('Hi, ' + name); * } * * // executes every 2 seconds: * var timerId = Ext.interval(sayHi, 2000, this, ['Fred']); * * The timer is stopped by: * * Ext.uninterval(timerId); * * NOTE: The `Ext.Function.interval()` method is an alias for `Ext.interval()`. * * @param {Function} fn The function to defer. * @param {Number} millis The number of milliseconds for the `setInterval` call * @param {Object} scope (optional) The scope (`this` reference) in which the function * is executed. **If omitted, defaults to the browser window.** * @param {Array} [args] Overrides arguments for the call. Defaults to the arguments * passed by the caller. * @param {Boolean/Number} [appendArgs=false] If `true` args are appended to call args * instead of overriding, or, if a number, then the args are inserted at the specified * position. * @return {Number} The interval id that can be used with `Ext.uninterval`. */ interval: function(fn, millis, scope, args, appendArgs) { var timerFn, timerId, boundFn; //<debug> var timer; // eslint-disable-line vars-on-top, one-var //</debug> boundFn = Ext.Function.bind(fn, scope, args, appendArgs); timerFn = function() { Ext.elevate(boundFn //<debug> , null, null, timer // eslint-disable-line comma-style //</debug> ); }; timerId = setInterval(timerFn, millis); //<debug> timerFn.$origFn = boundFn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; timer = Ext.Timer.created('interval', timerId, { type: 'interval', fn: fn, timerFn: timerFn }); //</debug> return timerId; }, /** * Create a combined function call sequence of the original function + the passed function. * The resulting function returns the results of the original function. * The passed function is called with the parameters of the original function. * Example usage: * * var sayHi = function(name){ * alert('Hi, ' + name); * }; * * sayHi('Fred'); // alerts "Hi, Fred" * * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){ * alert('Bye, ' + name); * }); * * sayGoodbye('Fred'); // both alerts show * * @param {Function} originalFn The original function. * @param {Function} newFn The function to sequence. * @param {Object} [scope] The scope (`this` reference) in which the passed function * is executed. If omitted, defaults to the scope in which the original function is called * or the default global environment object (usually the browser window). * @return {Function} The new function. */ createSequence: function(originalFn, newFn, scope) { if (!newFn) { return originalFn; } else { return function() { var result = originalFn.apply(this, arguments); newFn.apply(scope || this, arguments); return result; }; } }, /** * Creates a delegate function, optionally with a bound scope which, when called, buffers * the execution of the passed function for the configured number of milliseconds. * If called again within that period, the impending invocation will be canceled, and the * timeout period will begin again. * * @param {Function} fn The function to invoke on a buffered timer. * @param {Number} buffer The number of milliseconds by which to buffer the invocation * of the function. * @param {Object} [scope] The scope (`this` reference) in which. * the passed function is executed. If omitted, defaults to the scope specified * by the caller. * @param {Array} [args] Override arguments for the call. Defaults to the arguments * passed by the caller. * @return {Function} A function which invokes the passed function after buffering * for the specified time. */ createBuffered: function(fn, buffer, scope, args) { var timerId, result = function() { var callArgs = args || slice.call(arguments, 0), me = scope || this, timerFn; //<debug> var timer; // eslint-disable-line vars-on-top, one-var //</debug> if (timerId) { Ext.undefer(timerId); } timerFn = function() { Ext.elevate(fn, me, callArgs //<debug> , timer // eslint-disable-line comma-style //</debug> ); }; result.timer = timerId = setTimeout(timerFn, buffer); //<debug> timerFn.$origFn = fn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; timer = Ext.Timer.created('timeout', timerId, { type: 'createBuffered', fn: fn, timerFn: timerFn }); //</debug> }; return result; }, /** * Creates a wrapped function that, when invoked, defers execution until the next * animation frame * @private * @param {Function} fn The function to call. * @param {Object} [scope] The scope (`this` reference) in which the function is executed. * Defaults to the window object. * @param {Array} [args] The argument list to pass to the function. * @param {Number} [queueStrategy=3] A bit flag that indicates how multiple calls to * the returned function within the same animation frame should be handled. * * - 1: All calls will be queued - FIFO order * - 2: Only the first call will be queued * - 3: The last call will replace all previous calls * * @return {Function} */ createAnimationFrame: function(fn, scope, args, queueStrategy) { var boundFn, timerId; queueStrategy = queueStrategy || 3; boundFn = function() { var timerFn, callArgs = args || slice.call(arguments, 0); scope = scope || this; if (queueStrategy === 3 && timerId) { ExtFunction.cancelAnimationFrame(timerId); } if ((queueStrategy & 1) || !timerId) { timerFn = function() { timerId = boundFn.timerId = null; fn.apply(scope, callArgs); }; //<debug> timerFn.$origFn = fn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; //</debug> timerId = boundFn.timerId = ExtFunction.requestAnimationFrame(timerFn); } }; return boundFn; }, /** * @private * Schedules the passed function to be called on the next animation frame. * @param {Function} fn The function to call. * @param {Object} [scope] The scope (`this` reference) in which the function is executed. * Defaults to the window object. * @param {Mixed[]} [args] The argument list to pass to the function. * * @return {Number} Timer id for the new animation frame to use when canceling it. */ requestAnimationFrame: function(fn, scope, args) { var id = ++idSource, // Ids start at 1 handler = slice.call(arguments, 0); handler[3] = id; animFrameMap[id] = 1; // A flag to indicate that the timer exists //<debug> Ext.Timer.created('raf', id, { type: 'raf', fn: fn }); //</debug> // We might be in fireHandlers at this moment but this new entry will not // be executed until the next frame animFrameHandlers.push(handler); if (!animFrameId) { animFrameId = requestAnimFrame(fireElevatedHandlers); } return id; }, cancelAnimationFrame: function(id) { // Don't remove any handlers from animFrameHandlers array, because // the might be in use at the moment (when cancelAnimationFrame is called). // Just remove the handler id from the map so it will not be executed delete animFrameMap[id]; //<debug> Ext.Timer.cancel('raf', id); //</debug> }, /** * Creates a throttled version of the passed function which, when called repeatedly and * rapidly, invokes the passed function only after a certain interval has elapsed since the * previous invocation. * * This is useful for wrapping functions which may be called repeatedly, such as * a handler of a mouse move event when the processing is expensive. * * @param {Function} fn The function to execute at a regular time interval. * @param {Number} interval The interval in milliseconds on which the passed function * is executed. * @param {Object} [scope] The scope (`this` reference) in which * the passed function is executed. If omitted, defaults to the scope specified * by the caller. * @return {Function} A function which invokes the passed function at the specified * interval. */ createThrottled: function(fn, interval, scope) { var lastCallTime = 0, elapsed, lastArgs, timerId, execute = function() { fn.apply(scope, lastArgs); lastCallTime = Ext.now(); lastArgs = timerId = null; }; //<debug> execute.$origFn = fn.$origFn || fn; execute.$skipTimerCheck = execute.$origFn.$skipTimerCheck; //</debug> return function() { // Use scope of last call unless the creator specified a scope if (!scope) { scope = this; } elapsed = Ext.now() - lastCallTime; lastArgs = Ext.Array.slice(arguments); // If this is the first invocation, or the throttle interval has been reached, // clear any pending invocation, and call the target function now. if (elapsed >= interval) { Ext.undefer(timerId); execute(); } // Throttle interval has not yet been reached. Only set the timer to fire // if not already set. else if (!timerId) { timerId = Ext.defer(execute, interval - elapsed); } }; }, /** * Wraps the passed function in a barrier function which will call the passed function * after the passed number of invocations. * @param {Number} count The number of invocations which will result in the calling * of the passed function. * @param {Function} fn The function to call after the required number of invocations. * @param {Object} scope The scope (`this` reference) in which the function will be called. */ createBarrier: function(count, fn, scope) { var barrierFn = function() { if (!--count) { fn.apply(scope, arguments); } }; //<debug> barrierFn.$origFn = fn.$origFn || fn; barrierFn.$skipTimerCheck = barrierFn.$origFn.$skipTimerCheck; //</debug> return barrierFn; }, /** * Adds behavior to an existing method that is executed before the * original behavior of the function. For example: * * var soup = { * contents: [], * add: function(ingredient) { * this.contents.push(ingredient); * } * }; * Ext.Function.interceptBefore(soup, "add", function(ingredient){ * if (!this.contents.length && ingredient !== "water") { * // Always add water to start with * this.contents.push("water"); * } * }); * soup.add("onions"); * soup.add("salt"); * soup.contents; // will contain: water, onions, salt * * @param {Object} object The target object * @param {String} methodName Name of the method to override * @param {Function} fn Function with the new behavior. It will * be called with the same arguments as the original method. The * return value of this function will be the return value of the * new method. * @param {Object} [scope] The scope to execute the interceptor function. * Defaults to the object. * @return {Function} The new function just created. */ interceptBefore: function(object, methodName, fn, scope) { var method = object[methodName] || Ext.emptyFn; return (object[methodName] = function() { var ret = fn.apply(scope || this, arguments); method.apply(this, arguments); return ret; }); }, /** * Adds behavior to an existing method that is executed after the * original behavior of the function. For example: * * var soup = { * contents: [], * add: function(ingredient) { * this.contents.push(ingredient); * } * }; * Ext.Function.interceptAfter(soup, "add", function(ingredient){ * // Always add a bit of extra salt * this.contents.push("salt"); * }); * soup.add("water"); * soup.add("onions"); * soup.contents; // will contain: water, salt, onions, salt * * @param {Object} object The target object * @param {String} methodName Name of the method to override * @param {Function} fn Function with the new behavior. It will * be called with the same arguments as the original method. The * return value of this function will be the return value of the * new method. * @param {Object} [scope] The scope to execute the interceptor function. * Defaults to the object. * @return {Function} The new function just created. */ interceptAfter: function(object, methodName, fn, scope) { var method = object[methodName] || Ext.emptyFn; return (object[methodName] = function() { method.apply(this, arguments); return fn.apply(scope || this, arguments); }); }, interceptAfterOnce: function(object, methodName, fn, scope) { var origMethod = object[methodName], newMethod; newMethod = function() { var ret; if (origMethod) { origMethod.apply(this, arguments); } ret = fn.apply(scope || this, arguments); object[methodName] = origMethod; object = methodName = fn = scope = origMethod = newMethod = null; return ret; }; object[methodName] = newMethod; return newMethod; }, makeCallback: function(callback, scope) { //<debug> if (!scope[callback]) { if (scope.$className) { Ext.raise('No method "' + callback + '" on ' + scope.$className); } Ext.raise('No method "' + callback + '"'); } //</debug> return function() { return scope[callback].apply(scope, arguments); }; }, /** * Returns a wrapper function that caches the return value for previously * processed function argument(s). * * For example: * * function factorial (value) { * var ret = value; * * while (--value > 1) { * ret *= value; * } * * return ret; * } * * Each call to `factorial` will loop and multiply to produce the answer. Using * this function we can wrap the above and cache its answers: * * factorial = Ext.Function.memoize(factorial); * * The returned function operates in the same manner as before, but results are * stored in a cache to avoid calling the wrapped function when given the same * arguments. * * var x = factorial(20); // first time; call real factorial() * var y = factorial(20); // second time; return value from first call * * To support multi-argument methods, you will need to provide a `hashFn`. * * function permutation (n, k) { * return factorial(n) / factorial(n - k); * } * * permutation = Ext.Function.memoize(permutation, null, function(n, k) { * n + '-' + k; * }); * * In this case, the `memoize` of `factorial` is sufficient optimization, but the * example is simply to illustrate how to generate a unique key for an expensive, * multi-argument method. * * **IMPORTANT**: This cache is unbounded so be cautious of memory leaks if the * `memoize`d function is kept indefinitely or is given an unbounded set of * possible arguments. * * @param {Function} fn Function to wrap. * @param {Object} scope Optional scope in which to execute the wrapped function. * @param {Function} hashFn Optional function used to compute a hash key for * storing the result, based on the arguments to the original function. * @return {Function} The caching wrapper function. * @since 6.0.0 */ memoize: function(fn, scope, hashFn) { var memo = {}, isFunc = hashFn && Ext.isFunction(hashFn); return function(value) { var key = isFunc ? hashFn.apply(scope, arguments) : value; if (!(key in memo)) { memo[key] = fn.apply(scope, arguments); } return memo[key]; }; }, //<debug> _stripCommentRe: /(\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\/)|(\/\/.*)/g, //</debug> toCode: function(fn) { var s = fn ? fn.toString() : ''; //<debug> s = s.replace(ExtFunction._stripCommentRe, ''); //</debug> return s; } //<debug> // This is useful for unit testing so we can force handlers which have been deferred // to the next animation frame to run immediately , fireElevatedHandlers: function() { // eslint-disable-line comma-style fireElevatedHandlers(); } //</debug> }; // ExtFunction /** * @member Ext * @method asap * Schedules the specified callback function to be executed on the next turn of the * event loop. Where available, this method uses the browser's `setImmediate` API. If * not available, this method substitutes `setTimeout(0)`. Though not a perfect * replacement for `setImmediate` it is sufficient for many use cases. * * For more details see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate). * * @param {Function} fn Callback function. * @param {Object} [scope] The scope for the callback (`this` pointer). * @param {Mixed[]} [parameters] Additional parameters to pass to `fn`. * @return {Number} A cancellation id for `{@link Ext#unasap}`. */ Ext.asap = hasImmediate ? function(fn, scope, parameters) { var boundFn = fn, timerFn, timerId; //<debug> var timer; // eslint-disable-line vars-on-top, one-var //</debug> if (scope != null || parameters != null) { boundFn = ExtFunction.bind(fn, scope, parameters); } timerFn = function() { Ext.elevate(boundFn //<debug> , null, null, timer // eslint-disable-line comma-style //</debug> ); }; timerId = setImmediate(timerFn); //<debug> timerFn.$origFn = fn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; timer = Ext.Timer.created('asap', timerId, { type: 'asap', fn: fn, timerFn: timerFn }); //</debug> return timerId; } : function(fn, scope, parameters) { var boundFn = fn, timerFn, timerId; //<debug> var timer; // eslint-disable-line vars-on-top, one-var //</debug> if (scope != null || parameters != null) { boundFn = ExtFunction.bind(fn, scope, parameters); } timerFn = function() { Ext.elevate(boundFn //<debug> , null, null, timer // eslint-disable-line comma-style //</debug> ); }; timerId = setTimeout(timerFn, 0, true); //<debug> timerFn.$origFn = fn.$origFn || fn; timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck; timer = Ext.Timer.created('timeout', timerId, { type: 'asap', fn: fn, timerFn: timerFn }); //</debug> return timerId; }; /** * @member Ext * @method unasap * Cancels a previously scheduled call to `{@link Ext#asap}`. * * var timerId = Ext.asap(me.method, me); * ... * * if (nevermind) { * Ext.unasap(timerId); * } * * This method always returns `null` to enable simple cleanup: * * timerId = Ext.unasap(timerId); // safe even if !timerId * * @param {Number} id The id returned by `{@link Ext#asap}`. * @return {Object} Always returns `null`. */ Ext.unasap = hasImmediate ? function(id) { if (id) { clearImmediate(id); //<debug> Ext.Timer.cancel('asap', id); //</debug> } return null; } : function(id) { return Ext.undefer(id); }; /** * @member Ext * @method asapCancel * Cancels a previously scheduled call to `{@link Ext#asap}`. * @param {Number} id The id returned by `{@link Ext#asap}`. * @deprecated 6.5.1 Use `Ext.unasap` instead. */ Ext.asapCancel = function(id) { return Ext.unasap(id); }; /** * @method defer * @member Ext * @inheritdoc Ext.Function#defer */ Ext.defer = ExtFunction.defer; /** * @member Ext * @method undefer * Cancels a previously scheduled call to `{@link Ext#defer}`. * * var timerId = Ext.defer(me.method, me); * ... * * if (nevermind) { * Ext.undefer(timerId); * } * * This method always returns `null` to enable simple cleanup: * * timerId = Ext.undefer(timerId); // safe even if !timerId * * @param {Number} id The id returned by `{@link Ext#defer}`. */ Ext.undefer = function(id) { if (id) { clearTimeout(id); //<debug> Ext.Timer.cancel('timeout', id); //</debug> } return null; }; /** * @method interval * @member Ext * @inheritdoc Ext.Function#interval */ Ext.interval = ExtFunction.interval; /** * @member Ext * @method uninterval * Cancels a previously scheduled call to `{@link Ext#interval}`. * * var timerId = Ext.interval(me.method, me); * ... * * if (nevermind) { * Ext.uninterval(timerId); * } * * This method always returns `null` to enable simple cleanup: * * timerId = Ext.uninterval(timerId); // safe even if !timerId * * @param {Number} id The id returned by `{@link Ext#interval}`. */ Ext.uninterval = function(id) { if (id) { clearInterval(id); //<debug> Ext.Timer.cancel('interval', id); //</debug> } return null; }; /** * @method pass * @member Ext * @inheritdoc Ext.Function#pass */ Ext.pass = ExtFunction.pass; /** * @method bind * @member Ext * @inheritdoc Ext.Function#bind */ Ext.bind = ExtFunction.bind; Ext.raf = function() { return ExtFunction.requestAnimationFrame.apply(ExtFunction, arguments); }; Ext.unraf = function(id) { ExtFunction.cancelAnimationFrame(id); }; return ExtFunction;})();