//@tag foundation,core //@define Ext.Function //@require Ext.Object /** * @class Ext.Function * * A collection of useful static methods to deal with function callbacks. * @singleton * @alternateClassName Ext.util.Functions */ Ext.Function = { /** * 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} fn * @return {Function} flexSetter */ flexSetter: function(fn) { return function(a, b) { var k, i; if (a === null) { return this; } if (typeof a !== 'string') { for (k in a) { if (a.hasOwnProperty(k)) { fn.call(this, k, a[k]); } } if (Ext.enumerables) { for (i = Ext.enumerables.length; i--;) { k = Ext.enumerables[i]; if (a.hasOwnProperty(k)) { fn.call(this, k, a[k]); } } } } else { fn.call(this, a, b); } 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} * * @param {Function} fn The function to delegate. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. * **If omitted, defaults to the browser window.** * @param {Array} args (optional) Overrides 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} The new function. */ bind: function(fn, scope, args, appendArgs) { if (arguments.length === 2) { return function() { return fn.apply(scope, arguments); } } var method = fn, slice = Array.prototype.slice; 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 || window, callArgs); }; }, /** * 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)) { args = Ext.Array.clone(args); } return function() { args.push.apply(args, arguments); return fn.apply(scope || this, args); }; }, /** * 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) { return function() { return method.apply(this, arguments); }; }, /** * 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 (optional) 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] (optional) The value to return if the passed function return `false`. * @return {Function} The new function. */ createInterceptor: function(origFn, newFn, scope, returnValue) { var method = origFn; if (!Ext.isFunction(newFn)) { return origFn; } else { return function() { var me = this, args = arguments; newFn.target = me; newFn.method = origFn; return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null; }; } }, /** * 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) { if (scope || args) { fn = Ext.Function.bind(fn, scope, args, appendArgs); } return function() { var me = this, args = Array.prototype.slice.call(arguments); setTimeout(function() { fn.apply(me, args); }, delay); } }, /** * Calls this function after the number of milliseconds specified, optionally in a specific scope. Example usage: * * var sayHi = function(name){ * alert('Hi, ' + name); * }; * * // executes immediately: * sayHi('Fred'); * * // executes after 2 seconds: * Ext.Function.defer(sayHi, 2000, this, ['Fred']); * * // this syntax is sometimes useful for deferring * // execution of an anonymous function: * Ext.Function.defer(function(){ * alert('Anonymous'); * }, 100); * * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.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 (optional) Overrides 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 {Number} The timeout id that can be used with `clearTimeout()`. */ defer: function(fn, millis, scope, args, appendArgs) { fn = Ext.Function.bind(fn, scope, args, appendArgs); if (millis > 0) { return setTimeout(fn, millis); } fn(); return 0; }, /** * 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 (optional) 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. * @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 (optional) The scope (`this` reference) in which * the passed function is executed. If omitted, defaults to the scope specified by the caller. * @param {Array} args (optional) 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; return function() { var callArgs = args || Array.prototype.slice.call(arguments, 0), me = scope || this; if (timerId) { clearTimeout(timerId); } timerId = setTimeout(function(){ fn.apply(me, callArgs); }, buffer); }; }, /** * 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 (optional) 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, elapsed, lastArgs, timer, execute = function() { fn.apply(scope || this, lastArgs); lastCallTime = new Date().getTime(); }; return function() { elapsed = new Date().getTime() - lastCallTime; lastArgs = arguments; clearTimeout(timer); if (!lastCallTime || (elapsed >= interval)) { execute(); } else { timer = setTimeout(execute, interval - elapsed); } }; }, /** * 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); }); } }; /** * @method * @member Ext * @alias Ext.Function#defer */ Ext.defer = Ext.Function.alias(Ext.Function, 'defer'); /** * @method * @member Ext * @alias Ext.Function#pass */ Ext.pass = Ext.Function.alias(Ext.Function, 'pass'); /** * @method * @member Ext * @alias Ext.Function#bind */ Ext.bind = Ext.Function.alias(Ext.Function, 'bind');