Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / Ext.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
17  * @singleton
18  */
19 (function() {
20     var global = this,
21         objectPrototype = Object.prototype,
22         toString = objectPrototype.toString,
23         enumerables = true,
24         enumerablesTest = { toString: 1 },
25         i;
26
27     if (typeof Ext === 'undefined') {
28         global.Ext = {};
29     }
30
31     Ext.global = global;
32
33     for (i in enumerablesTest) {
34         enumerables = null;
35     }
36
37     if (enumerables) {
38         enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
39                        'toLocaleString', 'toString', 'constructor'];
40     }
41
42     /**
43      * An array containing extra enumerables for old browsers
44      * @property {String[]}
45      */
46     Ext.enumerables = enumerables;
47
48     /**
49      * Copies all the properties of config to the specified object.
50      * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
51      * {@link Ext.Object#merge} instead.
52      * @param {Object} object The receiver of the properties
53      * @param {Object} config The source of the properties
54      * @param {Object} defaults A different object that will also be applied for default values
55      * @return {Object} returns obj
56      */
57     Ext.apply = function(object, config, defaults) {
58         if (defaults) {
59             Ext.apply(object, defaults);
60         }
61
62         if (object && config && typeof config === 'object') {
63             var i, j, k;
64
65             for (i in config) {
66                 object[i] = config[i];
67             }
68
69             if (enumerables) {
70                 for (j = enumerables.length; j--;) {
71                     k = enumerables[j];
72                     if (config.hasOwnProperty(k)) {
73                         object[k] = config[k];
74                     }
75                 }
76             }
77         }
78
79         return object;
80     };
81
82     Ext.buildSettings = Ext.apply({
83         baseCSSPrefix: 'x-',
84         scopeResetCSS: false
85     }, Ext.buildSettings || {});
86
87     Ext.apply(Ext, {
88         /**
89          * A reusable empty function
90          */
91         emptyFn: function() {},
92
93         baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
94
95         /**
96          * Copies all the properties of config to object if they don't already exist.
97          * @param {Object} object The receiver of the properties
98          * @param {Object} config The source of the properties
99          * @return {Object} returns obj
100          */
101         applyIf: function(object, config) {
102             var property;
103
104             if (object) {
105                 for (property in config) {
106                     if (object[property] === undefined) {
107                         object[property] = config[property];
108                     }
109                 }
110             }
111
112             return object;
113         },
114
115         /**
116          * Iterates either an array or an object. This method delegates to
117          * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
118          *
119          * @param {Object/Array} object The object or array to be iterated.
120          * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
121          * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
122          * type that is being iterated.
123          * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
124          * Defaults to the object being iterated itself.
125          * @markdown
126          */
127         iterate: function(object, fn, scope) {
128             if (Ext.isEmpty(object)) {
129                 return;
130             }
131
132             if (scope === undefined) {
133                 scope = object;
134             }
135
136             if (Ext.isIterable(object)) {
137                 Ext.Array.each.call(Ext.Array, object, fn, scope);
138             }
139             else {
140                 Ext.Object.each.call(Ext.Object, object, fn, scope);
141             }
142         }
143     });
144
145     Ext.apply(Ext, {
146
147         /**
148          * This method deprecated. Use {@link Ext#define Ext.define} instead.
149          * @method
150          * @param {Function} superclass
151          * @param {Object} overrides
152          * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
153          * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
154          */
155         extend: function() {
156             // inline overrides
157             var objectConstructor = objectPrototype.constructor,
158                 inlineOverrides = function(o) {
159                 for (var m in o) {
160                     if (!o.hasOwnProperty(m)) {
161                         continue;
162                     }
163                     this[m] = o[m];
164                 }
165             };
166
167             return function(subclass, superclass, overrides) {
168                 // First we check if the user passed in just the superClass with overrides
169                 if (Ext.isObject(superclass)) {
170                     overrides = superclass;
171                     superclass = subclass;
172                     subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
173                         superclass.apply(this, arguments);
174                     };
175                 }
176
177                 //<debug>
178                 if (!superclass) {
179                     Ext.Error.raise({
180                         sourceClass: 'Ext',
181                         sourceMethod: 'extend',
182                         msg: 'Attempting to extend from a class which has not been loaded on the page.'
183                     });
184                 }
185                 //</debug>
186
187                 // We create a new temporary class
188                 var F = function() {},
189                     subclassProto, superclassProto = superclass.prototype;
190
191                 F.prototype = superclassProto;
192                 subclassProto = subclass.prototype = new F();
193                 subclassProto.constructor = subclass;
194                 subclass.superclass = superclassProto;
195
196                 if (superclassProto.constructor === objectConstructor) {
197                     superclassProto.constructor = superclass;
198                 }
199
200                 subclass.override = function(overrides) {
201                     Ext.override(subclass, overrides);
202                 };
203
204                 subclassProto.override = inlineOverrides;
205                 subclassProto.proto = subclassProto;
206
207                 subclass.override(overrides);
208                 subclass.extend = function(o) {
209                     return Ext.extend(subclass, o);
210                 };
211
212                 return subclass;
213             };
214         }(),
215
216         /**
217          * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
218
219     Ext.define('My.cool.Class', {
220         sayHi: function() {
221             alert('Hi!');
222         }
223     }
224
225     Ext.override(My.cool.Class, {
226         sayHi: function() {
227             alert('About to say...');
228
229             this.callOverridden();
230         }
231     });
232
233     var cool = new My.cool.Class();
234     cool.sayHi(); // alerts 'About to say...'
235                   // alerts 'Hi!'
236
237          * Please note that `this.callOverridden()` only works if the class was previously
238          * created with {@link Ext#define)
239          *
240          * @param {Object} cls The class to override
241          * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
242          * containing one or more methods.
243          * @method override
244          * @markdown
245          */
246         override: function(cls, overrides) {
247             if (cls.prototype.$className) {
248                 return cls.override(overrides);
249             }
250             else {
251                 Ext.apply(cls.prototype, overrides);
252             }
253         }
254     });
255
256     // A full set of static methods to do type checking
257     Ext.apply(Ext, {
258
259         /**
260          * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
261          * value (second argument) otherwise.
262          *
263          * @param {Object} value The value to test
264          * @param {Object} defaultValue The value to return if the original value is empty
265          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
266          * @return {Object} value, if non-empty, else defaultValue
267          */
268         valueFrom: function(value, defaultValue, allowBlank){
269             return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
270         },
271
272         /**
273          * Returns the type of the given variable in string format. List of possible values are:
274          *
275          * - `undefined`: If the given value is `undefined`
276          * - `null`: If the given value is `null`
277          * - `string`: If the given value is a string
278          * - `number`: If the given value is a number
279          * - `boolean`: If the given value is a boolean value
280          * - `date`: If the given value is a `Date` object
281          * - `function`: If the given value is a function reference
282          * - `object`: If the given value is an object
283          * - `array`: If the given value is an array
284          * - `regexp`: If the given value is a regular expression
285          * - `element`: If the given value is a DOM Element
286          * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
287          * - `whitespace`: If the given value is a DOM text node and contains only whitespace
288          *
289          * @param {Object} value
290          * @return {String}
291          * @markdown
292          */
293         typeOf: function(value) {
294             if (value === null) {
295                 return 'null';
296             }
297
298             var type = typeof value;
299
300             if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
301                 return type;
302             }
303
304             var typeToString = toString.call(value);
305
306             switch(typeToString) {
307                 case '[object Array]':
308                     return 'array';
309                 case '[object Date]':
310                     return 'date';
311                 case '[object Boolean]':
312                     return 'boolean';
313                 case '[object Number]':
314                     return 'number';
315                 case '[object RegExp]':
316                     return 'regexp';
317             }
318
319             if (type === 'function') {
320                 return 'function';
321             }
322
323             if (type === 'object') {
324                 if (value.nodeType !== undefined) {
325                     if (value.nodeType === 3) {
326                         return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
327                     }
328                     else {
329                         return 'element';
330                     }
331                 }
332
333                 return 'object';
334             }
335
336             //<debug error>
337             Ext.Error.raise({
338                 sourceClass: 'Ext',
339                 sourceMethod: 'typeOf',
340                 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
341             });
342             //</debug>
343         },
344
345         /**
346          * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
347          *
348          * - `null`
349          * - `undefined`
350          * - a zero-length array
351          * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
352          *
353          * @param {Object} value The value to test
354          * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
355          * @return {Boolean}
356          * @markdown
357          */
358         isEmpty: function(value, allowEmptyString) {
359             return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
360         },
361
362         /**
363          * Returns true if the passed value is a JavaScript Array, false otherwise.
364          *
365          * @param {Object} target The target to test
366          * @return {Boolean}
367          * @method
368          */
369         isArray: ('isArray' in Array) ? Array.isArray : function(value) {
370             return toString.call(value) === '[object Array]';
371         },
372
373         /**
374          * Returns true if the passed value is a JavaScript Date object, false otherwise.
375          * @param {Object} object The object to test
376          * @return {Boolean}
377          */
378         isDate: function(value) {
379             return toString.call(value) === '[object Date]';
380         },
381
382         /**
383          * Returns true if the passed value is a JavaScript Object, false otherwise.
384          * @param {Object} value The value to test
385          * @return {Boolean}
386          * @method
387          */
388         isObject: (toString.call(null) === '[object Object]') ?
389         function(value) {
390             // check ownerDocument here as well to exclude DOM nodes
391             return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
392         } :
393         function(value) {
394             return toString.call(value) === '[object Object]';
395         },
396
397         /**
398          * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
399          * @param {Object} value The value to test
400          * @return {Boolean}
401          */
402         isPrimitive: function(value) {
403             var type = typeof value;
404
405             return type === 'string' || type === 'number' || type === 'boolean';
406         },
407
408         /**
409          * Returns true if the passed value is a JavaScript Function, false otherwise.
410          * @param {Object} value The value to test
411          * @return {Boolean}
412          * @method
413          */
414         isFunction:
415         // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
416         // Object.prorotype.toString (slower)
417         (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
418             return toString.call(value) === '[object Function]';
419         } : function(value) {
420             return typeof value === 'function';
421         },
422
423         /**
424          * Returns true if the passed value is a number. Returns false for non-finite numbers.
425          * @param {Object} value The value to test
426          * @return {Boolean}
427          */
428         isNumber: function(value) {
429             return typeof value === 'number' && isFinite(value);
430         },
431
432         /**
433          * Validates that a value is numeric.
434          * @param {Object} value Examples: 1, '1', '2.34'
435          * @return {Boolean} True if numeric, false otherwise
436          */
437         isNumeric: function(value) {
438             return !isNaN(parseFloat(value)) && isFinite(value);
439         },
440
441         /**
442          * Returns true if the passed value is a string.
443          * @param {Object} value The value to test
444          * @return {Boolean}
445          */
446         isString: function(value) {
447             return typeof value === 'string';
448         },
449
450         /**
451          * Returns true if the passed value is a boolean.
452          *
453          * @param {Object} value The value to test
454          * @return {Boolean}
455          */
456         isBoolean: function(value) {
457             return typeof value === 'boolean';
458         },
459
460         /**
461          * Returns true if the passed value is an HTMLElement
462          * @param {Object} value The value to test
463          * @return {Boolean}
464          */
465         isElement: function(value) {
466             return value ? value.nodeType === 1 : false;
467         },
468
469         /**
470          * Returns true if the passed value is a TextNode
471          * @param {Object} value The value to test
472          * @return {Boolean}
473          */
474         isTextNode: function(value) {
475             return value ? value.nodeName === "#text" : false;
476         },
477
478         /**
479          * Returns true if the passed value is defined.
480          * @param {Object} value The value to test
481          * @return {Boolean}
482          */
483         isDefined: function(value) {
484             return typeof value !== 'undefined';
485         },
486
487         /**
488          * Returns true if the passed value is iterable, false otherwise
489          * @param {Object} value The value to test
490          * @return {Boolean}
491          */
492         isIterable: function(value) {
493             return (value && typeof value !== 'string') ? value.length !== undefined : false;
494         }
495     });
496
497     Ext.apply(Ext, {
498
499         /**
500          * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
501          * @param {Object} item The variable to clone
502          * @return {Object} clone
503          */
504         clone: function(item) {
505             if (item === null || item === undefined) {
506                 return item;
507             }
508
509             // DOM nodes
510             // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
511             // recursively
512             if (item.nodeType && item.cloneNode) {
513                 return item.cloneNode(true);
514             }
515
516             var type = toString.call(item);
517
518             // Date
519             if (type === '[object Date]') {
520                 return new Date(item.getTime());
521             }
522
523             var i, j, k, clone, key;
524
525             // Array
526             if (type === '[object Array]') {
527                 i = item.length;
528
529                 clone = [];
530
531                 while (i--) {
532                     clone[i] = Ext.clone(item[i]);
533                 }
534             }
535             // Object
536             else if (type === '[object Object]' && item.constructor === Object) {
537                 clone = {};
538
539                 for (key in item) {
540                     clone[key] = Ext.clone(item[key]);
541                 }
542
543                 if (enumerables) {
544                     for (j = enumerables.length; j--;) {
545                         k = enumerables[j];
546                         clone[k] = item[k];
547                     }
548                 }
549             }
550
551             return clone || item;
552         },
553
554         /**
555          * @private
556          * Generate a unique reference of Ext in the global scope, useful for sandboxing
557          */
558         getUniqueGlobalNamespace: function() {
559             var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
560
561             if (uniqueGlobalNamespace === undefined) {
562                 var i = 0;
563
564                 do {
565                     uniqueGlobalNamespace = 'ExtBox' + (++i);
566                 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
567
568                 Ext.global[uniqueGlobalNamespace] = Ext;
569                 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
570             }
571
572             return uniqueGlobalNamespace;
573         },
574
575         /**
576          * @private
577          */
578         functionFactory: function() {
579             var args = Array.prototype.slice.call(arguments);
580
581             if (args.length > 0) {
582                 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
583                     args[args.length - 1];
584             }
585
586             return Function.prototype.constructor.apply(Function.prototype, args);
587         }
588     });
589
590     /**
591      * Old alias to {@link Ext#typeOf}
592      * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
593      * @method
594      * @alias Ext#typeOf
595      */
596     Ext.type = Ext.typeOf;
597
598 })();
599