Upgrade to ExtJS 4.0.7 - Released 10/19/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         if (arguments.length === 2) {
98             return function() {
99                 return fn.apply(scope, arguments);
100             }
101         }
102
103         var method = fn,
104             slice = Array.prototype.slice;
105
106         return function() {
107             var callArgs = args || arguments;
108
109             if (appendArgs === true) {
110                 callArgs = slice.call(arguments, 0);
111                 callArgs = callArgs.concat(args);
112             }
113             else if (typeof appendArgs == 'number') {
114                 callArgs = slice.call(arguments, 0); // copy arguments first
115                 Ext.Array.insert(callArgs, appendArgs, args);
116             }
117
118             return method.apply(scope || window, callArgs);
119         };
120     },
121
122     /**
123      * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
124      * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
125      * This is especially useful when creating callbacks.
126      *
127      * For example:
128      *
129      *     var originalFunction = function(){
130      *         alert(Ext.Array.from(arguments).join(' '));
131      *     };
132      *
133      *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
134      *
135      *     callback(); // alerts 'Hello World'
136      *     callback('by Me'); // alerts 'Hello World by Me'
137      *
138      * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
139      *
140      * @param {Function} fn The original function
141      * @param {Array} args The arguments to pass to new callback
142      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
143      * @return {Function} The new callback function
144      */
145     pass: function(fn, args, scope) {
146         if (args) {
147             args = Ext.Array.from(args);
148         }
149
150         return function() {
151             return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
152         };
153     },
154
155     /**
156      * Create an alias to the provided method property with name `methodName` of `object`.
157      * Note that the execution scope will still be bound to the provided `object` itself.
158      *
159      * @param {Object/Function} object
160      * @param {String} methodName
161      * @return {Function} aliasFn
162      */
163     alias: function(object, methodName) {
164         return function() {
165             return object[methodName].apply(object, arguments);
166         };
167     },
168
169     /**
170      * Creates an interceptor function. The passed function is called before the original one. If it returns false,
171      * the original one is not called. The resulting function returns the results of the original function.
172      * The passed function is called with the parameters of the original function. Example usage:
173      *
174      *     var sayHi = function(name){
175      *         alert('Hi, ' + name);
176      *     }
177      *
178      *     sayHi('Fred'); // alerts "Hi, Fred"
179      *
180      *     // create a new function that validates input without
181      *     // directly modifying the original function:
182      *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
183      *         return name == 'Brian';
184      *     });
185      *
186      *     sayHiToFriend('Fred');  // no alert
187      *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
188      *
189      * @param {Function} origFn The original function.
190      * @param {Function} newFn The function to call before the original
191      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
192      * **If omitted, defaults to the scope in which the original function is called or the browser window.**
193      * @param {Object} returnValue (optional) The value to return if the passed function return false (defaults to null).
194      * @return {Function} The new function
195      */
196     createInterceptor: function(origFn, newFn, scope, returnValue) {
197         var method = origFn;
198         if (!Ext.isFunction(newFn)) {
199             return origFn;
200         }
201         else {
202             return function() {
203                 var me = this,
204                     args = arguments;
205                 newFn.target = me;
206                 newFn.method = origFn;
207                 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
208             };
209         }
210     },
211
212     /**
213      * Creates a delegate (callback) which, when called, executes after a specific delay.
214      *
215      * @param {Function} fn The function which will be called on a delay when the returned function is called.
216      * Optionally, a replacement (or additional) argument list may be specified.
217      * @param {Number} delay The number of milliseconds to defer execution by whenever called.
218      * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
219      * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
220      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
221      * if a number the args are inserted at the specified position.
222      * @return {Function} A function which, when called, executes the original function after the specified delay.
223      */
224     createDelayed: function(fn, delay, scope, args, appendArgs) {
225         if (scope || args) {
226             fn = Ext.Function.bind(fn, scope, args, appendArgs);
227         }
228         return function() {
229             var me = this;
230             setTimeout(function() {
231                 fn.apply(me, arguments);
232             }, delay);
233         };
234     },
235
236     /**
237      * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
238      *
239      *     var sayHi = function(name){
240      *         alert('Hi, ' + name);
241      *     }
242      *
243      *     // executes immediately:
244      *     sayHi('Fred');
245      *
246      *     // executes after 2 seconds:
247      *     Ext.Function.defer(sayHi, 2000, this, ['Fred']);
248      *
249      *     // this syntax is sometimes useful for deferring
250      *     // execution of an anonymous function:
251      *     Ext.Function.defer(function(){
252      *         alert('Anonymous');
253      *     }, 100);
254      *
255      * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
256      *
257      * @param {Function} fn The function to defer.
258      * @param {Number} millis The number of milliseconds for the setTimeout call
259      * (if less than or equal to 0 the function is executed immediately)
260      * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
261      * **If omitted, defaults to the browser window.**
262      * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
263      * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
264      * if a number the args are inserted at the specified position
265      * @return {Number} The timeout id that can be used with clearTimeout
266      */
267     defer: function(fn, millis, obj, args, appendArgs) {
268         fn = Ext.Function.bind(fn, obj, args, appendArgs);
269         if (millis > 0) {
270             return setTimeout(fn, millis);
271         }
272         fn();
273         return 0;
274     },
275
276     /**
277      * Create a combined function call sequence of the original function + the passed function.
278      * The resulting function returns the results of the original function.
279      * The passed function is called with the parameters of the original function. Example usage:
280      *
281      *     var sayHi = function(name){
282      *         alert('Hi, ' + name);
283      *     }
284      *
285      *     sayHi('Fred'); // alerts "Hi, Fred"
286      *
287      *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
288      *         alert('Bye, ' + name);
289      *     });
290      *
291      *     sayGoodbye('Fred'); // both alerts show
292      *
293      * @param {Function} origFn The original function.
294      * @param {Function} newFn The function to sequence
295      * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
296      * If omitted, defaults to the scope in which the original function is called or the browser window.
297      * @return {Function} The new function
298      */
299     createSequence: function(origFn, newFn, scope) {
300         if (!Ext.isFunction(newFn)) {
301             return origFn;
302         }
303         else {
304             return function() {
305                 var retval = origFn.apply(this || window, arguments);
306                 newFn.apply(scope || this || window, arguments);
307                 return retval;
308             };
309         }
310     },
311
312     /**
313      * Creates a delegate function, optionally with a bound scope which, when called, buffers
314      * the execution of the passed function for the configured number of milliseconds.
315      * If called again within that period, the impending invocation will be canceled, and the
316      * timeout period will begin again.
317      *
318      * @param {Function} fn The function to invoke on a buffered timer.
319      * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
320      * function.
321      * @param {Object} scope (optional) The scope (`this` reference) in which
322      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
323      * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
324      * passed by the caller.
325      * @return {Function} A function which invokes the passed function after buffering for the specified time.
326      */
327     createBuffered: function(fn, buffer, scope, args) {
328         return function(){
329             var timerId;
330             return function() {
331                 var me = this;
332                 if (timerId) {
333                     clearTimeout(timerId);
334                     timerId = null;
335                 }
336                 timerId = setTimeout(function(){
337                     fn.apply(scope || me, args || arguments);
338                 }, buffer);
339             };
340         }();
341     },
342
343     /**
344      * Creates a throttled version of the passed function which, when called repeatedly and
345      * rapidly, invokes the passed function only after a certain interval has elapsed since the
346      * previous invocation.
347      *
348      * This is useful for wrapping functions which may be called repeatedly, such as
349      * a handler of a mouse move event when the processing is expensive.
350      *
351      * @param {Function} fn The function to execute at a regular time interval.
352      * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
353      * @param {Object} scope (optional) The scope (`this` reference) in which
354      * the passed function is executed. If omitted, defaults to the scope specified by the caller.
355      * @returns {Function} A function which invokes the passed function at the specified interval.
356      */
357     createThrottled: function(fn, interval, scope) {
358         var lastCallTime, elapsed, lastArgs, timer, execute = function() {
359             fn.apply(scope || this, lastArgs);
360             lastCallTime = new Date().getTime();
361         };
362
363         return function() {
364             elapsed = new Date().getTime() - lastCallTime;
365             lastArgs = arguments;
366
367             clearTimeout(timer);
368             if (!lastCallTime || (elapsed >= interval)) {
369                 execute();
370             } else {
371                 timer = setTimeout(execute, interval - elapsed);
372             }
373         };
374     },
375
376     /**
377      * Adds behavior to an existing method that is executed before the
378      * original behavior of the function.  For example:
379      * 
380      *     var soup = {
381      *         contents: [],
382      *         add: function(ingredient) {
383      *             this.contents.push(ingredient);
384      *         }
385      *     };
386      *     Ext.Function.interceptBefore(soup, "add", function(ingredient){
387      *         if (!this.contents.length && ingredient !== "water") {
388      *             // Always add water to start with
389      *             this.contents.push("water");
390      *         }
391      *     });
392      *     soup.add("onions");
393      *     soup.add("salt");
394      *     soup.contents; // will contain: water, onions, salt
395      * 
396      * @param {Object} object The target object
397      * @param {String} methodName Name of the method to override
398      * @param {Function} fn Function with the new behavior.  It will
399      * be called with the same arguments as the original method.  The
400      * return value of this function will be the return value of the
401      * new method.
402      * @return {Function} The new function just created.
403      */
404     interceptBefore: function(object, methodName, fn) {
405         var method = object[methodName] || Ext.emptyFn;
406
407         return object[methodName] = function() {
408             var ret = fn.apply(this, arguments);
409             method.apply(this, arguments);
410
411             return ret;
412         };
413     },
414
415     /**
416      * Adds behavior to an existing method that is executed after the
417      * original behavior of the function.  For example:
418      * 
419      *     var soup = {
420      *         contents: [],
421      *         add: function(ingredient) {
422      *             this.contents.push(ingredient);
423      *         }
424      *     };
425      *     Ext.Function.interceptAfter(soup, "add", function(ingredient){
426      *         // Always add a bit of extra salt
427      *         this.contents.push("salt");
428      *     });
429      *     soup.add("water");
430      *     soup.add("onions");
431      *     soup.contents; // will contain: water, salt, onions, salt
432      * 
433      * @param {Object} object The target object
434      * @param {String} methodName Name of the method to override
435      * @param {Function} fn Function with the new behavior.  It will
436      * be called with the same arguments as the original method.  The
437      * return value of this function will be the return value of the
438      * new method.
439      * @return {Function} The new function just created.
440      */
441     interceptAfter: function(object, methodName, fn) {
442         var method = object[methodName] || Ext.emptyFn;
443
444         return object[methodName] = function() {
445             method.apply(this, arguments);
446             return fn.apply(this, arguments);
447         };
448     }
449 };
450
451 /**
452  * @method
453  * @member Ext
454  * @alias Ext.Function#defer
455  */
456 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
457
458 /**
459  * @method
460  * @member Ext
461  * @alias Ext.Function#pass
462  */
463 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
464
465 /**
466  * @method
467  * @member Ext
468  * @alias Ext.Function#bind
469  */
470 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
471