Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / core / src / lang / Function.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.Function
17  *
18  * A collection of useful static methods to deal with function callbacks
19  * @singleton
20  */
21 Ext.Function = {
22
23     /**
24      * A very commonly used method throughout the framework. It acts as a wrapper around another method
25      * which originally accepts 2 arguments for `name` and `value`.
26      * The wrapped function then allows "flexible" value setting of either:
27      *
28      * - `name` and `value` as 2 arguments
29      * - one single object argument with multiple key - value pairs
30      *
31      * For example:
32      *
33      *     var setValue = Ext.Function.flexSetter(function(name, value) {
34      *         this[name] = value;
35      *     });
36      *
37      *     // Afterwards
38      *     // Setting a single name - value
39      *     setValue('name1', 'value1');
40      *
41      *     // Settings multiple name - value pairs
42      *     setValue({
43      *         name1: 'value1',
44      *         name2: 'value2',
45      *         name3: 'value3'
46      *     });
47      *
48      * @param {Function} setter
49      * @returns {Function} flexSetter
50      */
51     flexSetter: function(fn) {
52         return function(a, b) {
53             var k, i;
54
55             if (a === null) {
56                 return this;
57             }
58
59             if (typeof a !== 'string') {
60                 for (k in a) {
61                     if (a.hasOwnProperty(k)) {
62                         fn.call(this, k, a[k]);
63                     }
64                 }
65
66                 if (Ext.enumerables) {
67                     for (i = Ext.enumerables.length; i--;) {
68                         k = Ext.enumerables[i];
69                         if (a.hasOwnProperty(k)) {
70                             fn.call(this, k, a[k]);
71                         }
72                     }
73                 }
74             } else {
75                 fn.call(this, a, b);
76             }
77
78             return this;
79         };
80     },
81
82     /**
83      * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
84      * overrides arguments for the call. (Defaults to the arguments passed by the caller)
85      *
86      * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
87      *
88      * @param {Function} fn The function to delegate.
89      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
90      * **If omitted, defaults to the browser window.**
91      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
92      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
93      * if a number the args are inserted at the specified position
94      * @return {Function} The new function
95      */
96     bind: function(fn, scope, args, appendArgs) {
97         var method = fn,
98             slice = Array.prototype.slice;
99
100         return function() {
101             var callArgs = args || arguments;
102
103             if (appendArgs === true) {
104                 callArgs = slice.call(arguments, 0);
105                 callArgs = callArgs.concat(args);
106             }
107             else if (Ext.isNumber(appendArgs)) {
108                 callArgs = slice.call(arguments, 0); // copy arguments first
109                 Ext.Array.insert(callArgs, appendArgs, args);
110             }
111
112             return method.apply(scope || window, callArgs);
113         };
114     },
115
116     /**
117      * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
118      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
119      * This is especially useful when creating callbacks.
120      *
121      * For example:
122      *
123      *     var originalFunction = function(){
124      *         alert(Ext.Array.from(arguments).join(' '));
125      *     };
126      *
127      *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
128      *
129      *     callback(); // alerts 'Hello World'
130      *     callback('by Me'); // alerts 'Hello World by Me'
131      *
132      * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
133      *
134      * @param {Function} fn The original function
135      * @param {Array} args The arguments to pass to new callback
136      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
137      * @return {Function} The new callback function
138      */
139     pass: function(fn, args, scope) {
140         if (args) {
141             args = Ext.Array.from(args);
142         }
143
144         return function() {
145             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
146         };
147     },
148
149     /**
150      * Create an alias to the provided method property with name `methodName` of `object`.
151      * Note that the execution scope will still be bound to the provided `object` itself.
152      *
153      * @param {Object/Function} object
154      * @param {String} methodName
155      * @return {Function} aliasFn
156      */
157     alias: function(object, methodName) {
158         return function() {
159             return object[methodName].apply(object, arguments);
160         };
161     },
162
163     /**
164      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
165      * the original one is not called. The resulting function returns the results of the original function.
166      * The passed function is called with the parameters of the original function. Example usage:
167      *
168      *     var sayHi = function(name){
169      *         alert('Hi, ' + name);
170      *     }
171      *
172      *     sayHi('Fred'); // alerts "Hi, Fred"
173      *
174      *     // create a new function that validates input without
175      *     // directly modifying the original function:
176      *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
177      *         return name == 'Brian';
178      *     });
179      *
180      *     sayHiToFriend('Fred');  // no alert
181      *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
182      *
183      * @param {Function} origFn The original function.
184      * @param {Function} newFn The function to call before the original
185      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
186      * **If omitted, defaults to the scope in which the original function is called or the browser window.**
187      * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
188      * @return {Function} The new function
189      */
190     createInterceptor: function(origFn, newFn, scope, returnValue) {
191         var method = origFn;
192         if (!Ext.isFunction(newFn)) {
193             return origFn;
194         }
195         else {
196             return function() {
197                 var me = this,
198                     args = arguments;
199                 newFn.target = me;
200                 newFn.method = origFn;
201                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
202             };
203         }
204     },
205
206     /**
207      * Creates a delegate (callback) which, when called, executes after a specific delay.
208      *
209      * @param {Function} fn The function which will be called on a delay when the returned function is called.
210      * Optionally, a replacement (or additional) argument list may be specified.
211      * @param {Number} delay The number of milliseconds to defer execution by whenever called.
212      * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
213      * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
214      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
215      * if a number the args are inserted at the specified position.
216      * @return {Function} A function which, when called, executes the original function after the specified delay.
217      */
218     createDelayed: function(fn, delay, scope, args, appendArgs) {
219         if (scope || args) {
220             fn = Ext.Function.bind(fn, scope, args, appendArgs);
221         }
222         return function() {
223             var me = this;
224             setTimeout(function() {
225                 fn.apply(me, arguments);
226             }, delay);
227         };
228     },
229
230     /**
231      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
232      *
233      *     var sayHi = function(name){
234      *         alert('Hi, ' + name);
235      *     }
236      *
237      *     // executes immediately:
238      *     sayHi('Fred');
239      *
240      *     // executes after 2 seconds:
241      *     Ext.Function.defer(sayHi, 2000, this, ['Fred']);
242      *
243      *     // this syntax is sometimes useful for deferring
244      *     // execution of an anonymous function:
245      *     Ext.Function.defer(function(){
246      *         alert('Anonymous');
247      *     }, 100);
248      *
249      * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
250      *
251      * @param {Function} fn The function to defer.
252      * @param {Number} millis The number of milliseconds for the setTimeout call
253      * (if less than or equal to 0 the function is executed immediately)
254      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
255      * **If omitted, defaults to the browser window.**
256      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
257      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
258      * if a number the args are inserted at the specified position
259      * @return {Number} The timeout id that can be used with clearTimeout
260      */
261     defer: function(fn, millis, obj, args, appendArgs) {
262         fn = Ext.Function.bind(fn, obj, args, appendArgs);
263         if (millis > 0) {
264             return setTimeout(fn, millis);
265         }
266         fn();
267         return 0;
268     },
269
270     /**
271      * Create a combined function call sequence of the original function + the passed function.
272      * The resulting function returns the results of the original function.
273      * The passed function is called with the parameters of the original function. Example usage:
274      *
275      *     var sayHi = function(name){
276      *         alert('Hi, ' + name);
277      *     }
278      *
279      *     sayHi('Fred'); // alerts "Hi, Fred"
280      *
281      *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
282      *         alert('Bye, ' + name);
283      *     });
284      *
285      *     sayGoodbye('Fred'); // both alerts show
286      *
287      * @param {Function} origFn The original function.
288      * @param {Function} newFn The function to sequence
289      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
290      * If omitted, defaults to the scope in which the original function is called or the browser window.
291      * @return {Function} The new function
292      */
293     createSequence: function(origFn, newFn, scope) {
294         if (!Ext.isFunction(newFn)) {
295             return origFn;
296         }
297         else {
298             return function() {
299                 var retval = origFn.apply(this || window, arguments);
300                 newFn.apply(scope || this || window, arguments);
301                 return retval;
302             };
303         }
304     },
305
306     /**
307      * Creates a delegate function, optionally with a bound scope which, when called, buffers
308      * the execution of the passed function for the configured number of milliseconds.
309      * If called again within that period, the impending invocation will be canceled, and the
310      * timeout period will begin again.
311      *
312      * @param {Function} fn The function to invoke on a buffered timer.
313      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
314      * function.
315      * @param {Object} scope (optional) The scope (`this` reference) in which
316      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
317      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
318      * passed by the caller.
319      * @return {Function} A function which invokes the passed function after buffering for the specified time.
320      */
321     createBuffered: function(fn, buffer, scope, args) {
322         return function(){
323             var timerId;
324             return function() {
325                 var me = this;
326                 if (timerId) {
327                     clearInterval(timerId);
328                     timerId = null;
329                 }
330                 timerId = setTimeout(function(){
331                     fn.apply(scope || me, args || arguments);
332                 }, buffer);
333             };
334         }();
335     },
336
337     /**
338      * Creates a throttled version of the passed function which, when called repeatedly and
339      * rapidly, invokes the passed function only after a certain interval has elapsed since the
340      * previous invocation.
341      *
342      * This is useful for wrapping functions which may be called repeatedly, such as
343      * a handler of a mouse move event when the processing is expensive.
344      *
345      * @param {Function} fn The function to execute at a regular time interval.
346      * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
347      * @param {Object} scope (optional) The scope (`this` reference) in which
348      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
349      * @returns {Function} A function which invokes the passed function at the specified interval.
350      */
351     createThrottled: function(fn, interval, scope) {
352         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
353             fn.apply(scope || this, lastArgs);
354             lastCallTime = new Date().getTime();
355         };
356
357         return function() {
358             elapsed = new Date().getTime() - lastCallTime;
359             lastArgs = arguments;
360
361             clearTimeout(timer);
362             if (!lastCallTime || (elapsed >= interval)) {
363                 execute();
364             } else {
365                 timer = setTimeout(execute, interval - elapsed);
366             }
367         };
368     }
369 };
370
371 /**
372  * @method
373  * @member Ext
374  * @alias Ext.Function#defer
375  */
376 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
377
378 /**
379  * @method
380  * @member Ext
381  * @alias Ext.Function#pass
382  */
383 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
384
385 /**
386  * @method
387  * @member Ext
388  * @alias Ext.Function#bind
389  */
390 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
391