]> git.proxmox.com Git - extjs.git/blob - extjs/packages/core/src/Util.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / Util.js
1 /*
2 * This file contains miscellaneous utility methods that depends on various helper classes
3 * like `Ext.Array` and `Ext.Date`. Historically these methods were defined in Ext.js or
4 * Ext-more.js but that creates circular dependencies so they were consolidated here.
5 */
6 Ext.apply(Ext, {
7 // @define Ext.Util
8 // @require Ext
9 // @require Ext.lang.*
10
11 // shortcut for the special named scopes for listener scope resolution
12 _namedScopes: {
13 'this': { isThis: 1 },
14 controller: { isController: 1 },
15 // these two are private, used to indicate that listeners were declared on the
16 // class body with either an unspecified scope, or scope:'controller'
17 self: { isSelf: 1 },
18 'self.controller': { isSelf: 1, isController: 1 }
19 },
20
21 escapeId: (function(){
22 var validIdRe = /^[a-zA-Z_][a-zA-Z0-9_\-]*$/i,
23 escapeRx = /([\W]{1})/g,
24 leadingNumRx = /^(\d)/g,
25 escapeFn = function(match, capture){
26 return "\\" + capture;
27 },
28 numEscapeFn = function(match, capture){
29 return '\\00' + capture.charCodeAt(0).toString(16) + ' ';
30 };
31
32 return function(id) {
33 return validIdRe.test(id) ? id :
34 // replace the number portion last to keep the trailing ' '
35 // from being escaped
36 id.replace(escapeRx, escapeFn).replace(leadingNumRx, numEscapeFn);
37 };
38 }()),
39
40 /**
41 * @method callback
42 * @member Ext
43 * Execute a callback function in a particular scope. If `callback` argument is a
44 * function reference, that is called. If it is a string, the string is assumed to
45 * be the name of a method on the given `scope`. If no function is passed the call
46 * is ignored.
47 *
48 * For example, these calls are equivalent:
49 *
50 * var myFunc = this.myFunc;
51 *
52 * Ext.callback('myFunc', this, [arg1, arg2]);
53 * Ext.callback(myFunc, this, [arg1, arg2]);
54 *
55 * Ext.isFunction(myFunc) && this.myFunc(arg1, arg2);
56 *
57 * @param {Function/String} callback The callback function to execute or the name of
58 * the callback method on the provided `scope`.
59 * @param {Object} [scope] The scope in which `callback` should be invoked. If `callback`
60 * is a string this object provides the method by that name. If this is `null` then
61 * the `caller` is used to resolve the scope to a `ViewController` or the proper
62 * `defaultListenerScope`.
63 * @param {Array} [args] The arguments to pass to the function.
64 * @param {Number} [delay] Pass a number to delay the call by a number of milliseconds.
65 * @param {Object} [caller] The object calling the callback. This is used to resolve
66 * named methods when no explicit `scope` is provided.
67 * @param {Object} [defaultScope=caller] The default scope to return if none is found.
68 * @return The value returned by the callback or `undefined` (if there is a `delay`
69 * or if the `callback` is not a function).
70 */
71 callback: function (callback, scope, args, delay, caller, defaultScope) {
72 if (!callback) {
73 return;
74 }
75
76 var namedScope = (scope in Ext._namedScopes);
77
78 if (callback.charAt) { // if (isString(fn))
79 if ((!scope || namedScope) && caller) {
80 scope = caller.resolveListenerScope(namedScope ? scope : defaultScope);
81 }
82 //<debug>
83 if (!scope || !Ext.isObject(scope)) {
84 Ext.raise('Named method "' + callback + '" requires a scope object');
85 }
86 if (!Ext.isFunction(scope[callback])) {
87 Ext.raise('No method named "' + callback + '" on ' +
88 (scope.$className || 'scope object'));
89 }
90 //</debug>
91
92 callback = scope[callback];
93 } else if (namedScope) {
94 scope = defaultScope || caller;
95 } else if (!scope) {
96 scope = caller;
97 }
98
99 var ret;
100
101 if (callback && Ext.isFunction(callback)) {
102 scope = scope || Ext.global;
103 if (delay) {
104 Ext.defer(callback, delay, scope, args);
105 } else if (Ext.elevateFunction) {
106 ret = Ext.elevateFunction(callback, scope, args);
107 } else if (args) {
108 ret = callback.apply(scope, args);
109 } else {
110 ret = callback.call(scope);
111 }
112 }
113
114 return ret;
115 },
116
117 /**
118 * @method coerce
119 * @member Ext
120 * Coerces the first value if possible so that it is comparable to the second value.
121 *
122 * Coercion only works between the basic atomic data types String, Boolean, Number, Date, null and undefined.
123 *
124 * Numbers and numeric strings are coerced to Dates using the value as the millisecond era value.
125 *
126 * Strings are coerced to Dates by parsing using the {@link Ext.Date#defaultFormat defaultFormat}.
127 *
128 * For example
129 *
130 * Ext.coerce('false', true);
131 *
132 * returns the boolean value `false` because the second parameter is of type `Boolean`.
133 *
134 * @param {Mixed} from The value to coerce
135 * @param {Mixed} to The value it must be compared against
136 * @return The coerced value.
137 */
138 coerce: function(from, to) {
139 var fromType = Ext.typeOf(from),
140 toType = Ext.typeOf(to),
141 isString = typeof from === 'string';
142
143 if (fromType !== toType) {
144 switch (toType) {
145 case 'string':
146 return String(from);
147 case 'number':
148 return Number(from);
149 case 'boolean':
150 return isString && (!from || from === 'false') ? false : Boolean(from);
151 case 'null':
152 return isString && (!from || from === 'null') ? null : from;
153 case 'undefined':
154 return isString && (!from || from === 'undefined') ? undefined : from;
155 case 'date':
156 return isString && isNaN(from) ? Ext.Date.parse(from, Ext.Date.defaultFormat) : Date(Number(from));
157 }
158 }
159 return from;
160 },
161
162 /**
163 * @method copyTo
164 * @member Ext
165 * Copies a set of named properties fom the source object to the destination object.
166 *
167 * Example:
168 *
169 * var foo = { a: 1, b: 2, c: 3 };
170 *
171 * var bar = Ext.copyTo({}, foo, 'a,c');
172 * // bar = { a: 1, c: 3 };
173 *
174 * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
175 *
176 * @param {Object} dest The destination object.
177 * @param {Object} source The source object.
178 * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
179 * of property names to copy.
180 * @param {Boolean} [usePrototypeKeys=false] Pass `true` to copy keys off of the
181 * prototype as well as the instance.
182 * @return {Object} The `dest` object.
183 * @deprecated 6.0.1 Use {@link Ext#copy Ext.copy} instead. This old method
184 * would copy the named preoperties even if they did not exist in the source which
185 * could produce `undefined` values in the destination.
186 */
187 copyTo: function (dest, source, names, usePrototypeKeys) {
188 if (typeof names === 'string') {
189 names = names.split(Ext.propertyNameSplitRe);
190 }
191
192 for (var name, i = 0, n = names ? names.length : 0; i < n; i++) {
193 name = names[i];
194
195 if (usePrototypeKeys || source.hasOwnProperty(name)) {
196 dest[name] = source[name];
197 }
198 }
199
200 return dest;
201 },
202 /**
203 * @method copy
204 * @member Ext
205 * Copies a set of named properties fom the source object to the destination object.
206 *
207 * Example:
208 *
209 * var foo = { a: 1, b: 2, c: 3 };
210 *
211 * var bar = Ext.copy({}, foo, 'a,c');
212 * // bar = { a: 1, c: 3 };
213 *
214 * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
215 *
216 * @param {Object} dest The destination object.
217 * @param {Object} source The source object.
218 * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
219 * of property names to copy.
220 * @param {Boolean} [usePrototypeKeys=false] Pass `true` to copy keys off of the
221 * prototype as well as the instance.
222 * @return {Object} The `dest` object.
223 */
224 copy: function (dest, source, names, usePrototypeKeys) {
225 if (typeof names === 'string') {
226 names = names.split(Ext.propertyNameSplitRe);
227 }
228
229 for (var name, i = 0, n = names ? names.length : 0; i < n; i++) {
230 name = names[i];
231
232 // Only copy a property if the source actually *has* that property.
233 // If we are including prototype properties, then ensure that a property of
234 // that name can be found *somewhere* in the prototype chain (otherwise we'd be copying undefined in which may break things)
235 if (source.hasOwnProperty(name) || (usePrototypeKeys && name in source)) {
236 dest[name] = source[name];
237 }
238 }
239
240 return dest;
241 },
242
243 propertyNameSplitRe: /[,;\s]+/,
244
245 /**
246 * @method copyToIf
247 * @member Ext
248 * Copies a set of named properties fom the source object to the destination object
249 * if the destination object does not already have them.
250 *
251 * Example:
252 *
253 * var foo = { a: 1, b: 2, c: 3 };
254 *
255 * var bar = Ext.copyToIf({ a:42 }, foo, 'a,c');
256 * // bar = { a: 42, c: 3 };
257 *
258 * @param {Object} destination The destination object.
259 * @param {Object} source The source object.
260 * @param {String/String[]} names Either an Array of property names, or a single string
261 * with a list of property names separated by ",", ";" or spaces.
262 * @return {Object} The `dest` object.
263 * @deprecated 6.0.1 Use {@link Ext#copyIf Ext.copyIf} instead. This old method
264 * would copy the named preoperties even if they did not exist in the source which
265 * could produce `undefined` values in the destination.
266 */
267 copyToIf: function (destination, source, names) {
268 if (typeof names === 'string') {
269 names = names.split(Ext.propertyNameSplitRe);
270 }
271
272 for (var name, i = 0, n = names ? names.length : 0; i < n; i++) {
273 name = names[i];
274
275 if (destination[name] === undefined) {
276 destination[name] = source[name];
277 }
278 }
279
280 return destination;
281 },
282
283 /**
284 * @method copyIf
285 * @member Ext
286 * Copies a set of named properties fom the source object to the destination object
287 * if the destination object does not already have them.
288 *
289 * Example:
290 *
291 * var foo = { a: 1, b: 2, c: 3 };
292 *
293 * var bar = Ext.copyIf({ a:42 }, foo, 'a,c');
294 * // bar = { a: 42, c: 3 };
295 *
296 * @param {Object} destination The destination object.
297 * @param {Object} source The source object.
298 * @param {String/String[]} names Either an Array of property names, or a single string
299 * with a list of property names separated by ",", ";" or spaces.
300 * @return {Object} The `dest` object.
301 */
302 copyIf: function (destination, source, names) {
303 if (typeof names === 'string') {
304 names = names.split(Ext.propertyNameSplitRe);
305 }
306
307 for (var name, i = 0, n = names ? names.length : 0; i < n; i++) {
308 name = names[i];
309
310 // Only copy a property if the destination has no property by that name
311 if (!(name in destination) && (name in source)) {
312 destination[name] = source[name];
313 }
314 }
315
316 return destination;
317 },
318
319 /**
320 * @method extend
321 * @member Ext
322 * This method deprecated. Use {@link Ext#define Ext.define} instead.
323 * @param {Function} superclass
324 * @param {Object} overrides
325 * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
326 * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
327 */
328 extend: (function() {
329 // inline overrides
330 var objectConstructor = Object.prototype.constructor,
331 inlineOverrides = function(o) {
332 for (var m in o) {
333 if (!o.hasOwnProperty(m)) {
334 continue;
335 }
336 this[m] = o[m];
337 }
338 };
339
340 return function(subclass, superclass, overrides) {
341 // First we check if the user passed in just the superClass with overrides
342 if (Ext.isObject(superclass)) {
343 overrides = superclass;
344 superclass = subclass;
345 subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
346 superclass.apply(this, arguments);
347 };
348 }
349
350 //<debug>
351 if (!superclass) {
352 Ext.raise({
353 sourceClass: 'Ext',
354 sourceMethod: 'extend',
355 msg: 'Attempting to extend from a class which has not been loaded on the page.'
356 });
357 }
358 //</debug>
359
360 // We create a new temporary class
361 var F = function() {},
362 subclassProto, superclassProto = superclass.prototype;
363
364 F.prototype = superclassProto;
365 subclassProto = subclass.prototype = new F();
366 subclassProto.constructor = subclass;
367 subclass.superclass = superclassProto;
368
369 if (superclassProto.constructor === objectConstructor) {
370 superclassProto.constructor = superclass;
371 }
372
373 subclass.override = function(overrides) {
374 Ext.override(subclass, overrides);
375 };
376
377 subclassProto.override = inlineOverrides;
378 subclassProto.proto = subclassProto;
379
380 subclass.override(overrides);
381 subclass.extend = function(o) {
382 return Ext.extend(subclass, o);
383 };
384
385 return subclass;
386 };
387 }()),
388
389 /**
390 * @method iterate
391 * @member Ext
392 * Iterates either an array or an object. This method delegates to
393 * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
394 *
395 * @param {Object/Array} object The object or array to be iterated.
396 * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
397 * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
398 * type that is being iterated.
399 * @param {Object} [scope] The scope (`this` reference) in which the specified function is executed.
400 * Defaults to the object being iterated itself.
401 */
402 iterate: function(object, fn, scope) {
403 if (Ext.isEmpty(object)) {
404 return;
405 }
406
407 if (scope === undefined) {
408 scope = object;
409 }
410
411 if (Ext.isIterable(object)) {
412 Ext.Array.each.call(Ext.Array, object, fn, scope);
413 }
414 else {
415 Ext.Object.each.call(Ext.Object, object, fn, scope);
416 }
417 },
418
419 _resourcePoolRe: /^[<]([^<>@:]*)(?:[@]([^<>@:]+))?[>](.+)$/,
420
421 /**
422 * Resolves a resource URL that may contain a resource pool identifier token at the
423 * front. The tokens are formatted as HTML tags "&lt;poolName@packageName&gt;" followed
424 * by a normal relative path. This token is only processed if present at the first
425 * character of the given string.
426 *
427 * These tokens are parsed and the pieces are then passed to the
428 * {@link Ext#getResourcePath} method.
429 *
430 * For example:
431 *
432 * [{
433 * xtype: 'image',
434 * src: '<shared>images/foo.png'
435 * },{
436 * xtype: 'image',
437 * src: '<@package>images/foo.png'
438 * },{
439 * xtype: 'image',
440 * src: '<shared@package>images/foo.png'
441 * }]
442 *
443 * In the above example, "shared" is the name of a Sencha Cmd resource pool and
444 * "package" is the name of a Sencha Cmd package.
445 *
446 * @param {String} url The URL that may contain a resource pool token at the front.
447 * @return {String}
448 * @since 6.0.1
449 */
450 resolveResource: function (url) {
451 var ret = url,
452 m;
453
454 if (url && url.charAt(0) === '<') {
455 m = Ext._resourcePoolRe.exec(url);
456 if (m) {
457 ret = Ext.getResourcePath(m[3], m[1], m[2]);
458 }
459 }
460
461 return ret;
462 },
463
464 /**
465 *
466 * @member Ext
467 * @method urlEncode
468 * @inheritdoc Ext.Object#toQueryString
469 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
470 */
471 urlEncode: function () {
472 var args = Ext.Array.from(arguments),
473 prefix = '';
474
475 // Support for the old `pre` argument
476 if (Ext.isString(args[1])) {
477 prefix = args[1] + '&';
478 args[1] = false;
479 }
480
481 return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
482 },
483
484 /**
485 * Alias for {@link Ext.Object#fromQueryString}.
486 *
487 * @member Ext
488 * @method urlDecode
489 * @inheritdoc Ext.Object#fromQueryString
490 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
491 */
492 urlDecode: function() {
493 return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
494 },
495
496 /**
497 * @method getScrollbarSize
498 * @member Ext
499 * Returns the size of the browser scrollbars. This can differ depending on
500 * operating system settings, such as the theme or font size.
501 * @param {Boolean} [force] true to force a recalculation of the value.
502 * @return {Object} An object containing scrollbar sizes.
503 * @return {Number} return.width The width of the vertical scrollbar.
504 * @return {Number} return.height The height of the horizontal scrollbar.
505 */
506 getScrollbarSize: function (force) {
507 //<debug>
508 if (!Ext.isDomReady) {
509 Ext.raise("getScrollbarSize called before DomReady");
510 }
511 //</debug>
512
513 var scrollbarSize = Ext._scrollbarSize;
514
515 if (force || !scrollbarSize) {
516 var db = document.body,
517 div = document.createElement('div');
518
519 div.style.width = div.style.height = '100px';
520 div.style.overflow = 'scroll';
521 div.style.position = 'absolute';
522
523 db.appendChild(div); // now we can measure the div...
524
525 // at least in iE9 the div is not 100px - the scrollbar size is removed!
526 Ext._scrollbarSize = scrollbarSize = {
527 width: div.offsetWidth - div.clientWidth,
528 height: div.offsetHeight - div.clientHeight
529 };
530
531 db.removeChild(div);
532 }
533
534 return scrollbarSize;
535 },
536
537 /**
538 * @method typeOf
539 * @member Ext
540 * Returns the type of the given variable in string format. List of possible values are:
541 *
542 * - `undefined`: If the given value is `undefined`
543 * - `null`: If the given value is `null`
544 * - `string`: If the given value is a string
545 * - `number`: If the given value is a number
546 * - `boolean`: If the given value is a boolean value
547 * - `date`: If the given value is a `Date` object
548 * - `function`: If the given value is a function reference
549 * - `object`: If the given value is an object
550 * - `array`: If the given value is an array
551 * - `regexp`: If the given value is a regular expression
552 * - `element`: If the given value is a DOM Element
553 * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
554 * - `whitespace`: If the given value is a DOM text node and contains only whitespace
555 *
556 * @param {Object} value
557 * @return {String}
558 */
559 typeOf: (function () {
560 var nonWhitespaceRe = /\S/,
561 toString = Object.prototype.toString,
562 typeofTypes = {
563 number: 1,
564 string: 1,
565 'boolean': 1,
566 'undefined': 1
567 },
568 toStringTypes = {
569 '[object Array]' : 'array',
570 '[object Date]' : 'date',
571 '[object Boolean]': 'boolean',
572 '[object Number]' : 'number',
573 '[object RegExp]' : 'regexp'
574 };
575
576 return function(value) {
577 if (value === null) {
578 return 'null';
579 }
580
581 var type = typeof value,
582 ret, typeToString;
583
584 if (typeofTypes[type]) {
585 return type;
586 }
587
588 ret = toStringTypes[typeToString = toString.call(value)];
589 if (ret) {
590 return ret;
591 }
592
593 if (type === 'function') {
594 return 'function';
595 }
596
597 if (type === 'object') {
598 if (value.nodeType !== undefined) {
599 if (value.nodeType === 3) {
600 return nonWhitespaceRe.test(value.nodeValue) ? 'textnode' : 'whitespace';
601 }
602 else {
603 return 'element';
604 }
605 }
606
607 return 'object';
608 }
609
610 //<debug>
611 Ext.raise({
612 sourceClass: 'Ext',
613 sourceMethod: 'typeOf',
614 msg: 'Failed to determine the type of "' + value + '".'
615 });
616 //</debug>
617
618 return typeToString;
619 };
620 }()),
621
622 /**
623 * A global factory method to instantiate a class from a config object. For example,
624 * these two calls are equivalent:
625 *
626 * Ext.factory({ text: 'My Button' }, 'Ext.Button');
627 * Ext.create('Ext.Button', { text: 'My Button' });
628 *
629 * If an existing instance is also specified, it will be updated with the supplied config object. This is useful
630 * if you need to either create or update an object, depending on if an instance already exists. For example:
631 *
632 * var button;
633 * button = Ext.factory({ text: 'New Button' }, 'Ext.Button', button); // Button created
634 * button = Ext.factory({ text: 'Updated Button' }, 'Ext.Button', button); // Button updated
635 *
636 * @param {Object} config The config object to instantiate or update an instance with.
637 * @param {String} [classReference] The class to instantiate from (if there is a default).
638 * @param {Object} [instance] The instance to update.
639 * @param [aliasNamespace]
640 * @member Ext
641 */
642 factory: function(config, classReference, instance, aliasNamespace) {
643 var manager = Ext.ClassManager,
644 newInstance;
645
646 // If config is falsy or a valid instance, destroy the current instance
647 // (if it exists) and replace with the new one
648 if (!config || config.isInstance) {
649 if (instance && instance !== config) {
650 instance.destroy();
651 }
652
653 return config;
654 }
655
656 if (aliasNamespace) {
657 // If config is a string value, treat it as an alias
658 if (typeof config === 'string') {
659 return manager.instantiateByAlias(aliasNamespace + '.' + config);
660 }
661 // Same if 'type' is given in config
662 else if (Ext.isObject(config) && 'type' in config) {
663 return manager.instantiateByAlias(aliasNamespace + '.' + config.type, config);
664 }
665 }
666
667 if (config === true) {
668 //<debug>
669 if (!instance && !classReference) {
670 Ext.raise('[Ext.factory] Cannot determine type of class to create');
671 }
672 //</debug>
673 return instance || Ext.create(classReference);
674 }
675
676 //<debug>
677 if (!Ext.isObject(config)) {
678 Ext.raise("Invalid config, must be a valid config object");
679 }
680 //</debug>
681
682 if ('xtype' in config) {
683 newInstance = manager.instantiateByAlias('widget.' + config.xtype, config);
684 }
685 else if ('xclass' in config) {
686 newInstance = Ext.create(config.xclass, config);
687 }
688
689 if (newInstance) {
690 if (instance) {
691 instance.destroy();
692 }
693
694 return newInstance;
695 }
696
697 if (instance) {
698 return instance.setConfig(config);
699 }
700
701 return Ext.create(classReference, config);
702 },
703
704 /**
705 * @method log
706 * @member Ext
707 * Logs a message. If a console is present it will be used. On Opera, the method
708 * "opera.postError" is called. In other cases, the message is logged to an array
709 * "Ext.log.out". An attached debugger can watch this array and view the log. The
710 * log buffer is limited to a maximum of "Ext.log.max" entries (defaults to 250).
711 *
712 * If additional parameters are passed, they are joined and appended to the message.
713 * A technique for tracing entry and exit of a function is this:
714 *
715 * function foo () {
716 * Ext.log({ indent: 1 }, '>> foo');
717 *
718 * // log statements in here or methods called from here will be indented
719 * // by one step
720 *
721 * Ext.log({ outdent: 1 }, '<< foo');
722 * }
723 *
724 * This method does nothing in a release build.
725 *
726 * @param {String/Object} [options] The message to log or an options object with any
727 * of the following properties:
728 *
729 * - `msg`: The message to log (required).
730 * - `level`: One of: "error", "warn", "info" or "log" (the default is "log").
731 * - `dump`: An object to dump to the log as part of the message.
732 * - `stack`: True to include a stack trace in the log.
733 * - `indent`: Cause subsequent log statements to be indented one step.
734 * - `outdent`: Cause this and following statements to be one step less indented.
735 *
736 * @param {String...} [message] The message to log (required unless specified in
737 * options object).
738 */
739 log:
740 //<debug>
741 (function () {
742 /*
743 * Iterate through an object to dump its content into a string.
744 * For example:
745 * {
746 * style: {
747 * lineWidth: 1
748 * },
749 * label: {},
750 * marker: {
751 * strokeStyle: "#555",
752 * radius: 3,
753 * size: 3
754 * },
755 * subStyle: {
756 * fillStyle: [
757 * 0: "#133987",
758 * 1: "#1c55ca",
759 * 2: "#4d7fe6"
760 * ]
761 * },
762 * markerSubStyle: {}
763 * }
764 *
765 * @param {Object} object The object to iterate
766 * @param {Number} [level] Current level of identation (and recursion). Default is 0.
767 * @param {Number} [maxLevel] Maximum level of recursion. Default is 3.
768 * @param {Boolean} [withFunctions] Include functions in the output.
769 * @return {String} The string with the contents of the object
770 */
771 var primitiveRe = /string|number|boolean/;
772 function dumpObject (object, level, maxLevel, withFunctions) {
773 var member, type, value, name, prefix, suffix,
774 members = [];
775
776 if (Ext.isArray(object)) {
777 prefix = '[';
778 suffix = ']';
779 } else if (Ext.isObject(object)) {
780 prefix = '{';
781 suffix = '}';
782 }
783 if (!maxLevel) {
784 maxLevel = 3;
785 }
786 if (level > maxLevel) {
787 return prefix+'...'+suffix;
788 }
789
790 level = level || 1;
791 var spacer = (new Array(level)).join(' ');
792
793 // Cannot use Ext.encode since it can recurse endlessly
794 for (name in object) {
795 if (object.hasOwnProperty(name)) {
796 value = object[name];
797
798 type = typeof value;
799 if (type === 'function') {
800 if (!withFunctions) {
801 continue;
802 }
803 member = type;
804 } else if (type === 'undefined') {
805 member = type;
806 } else if (value === null || primitiveRe.test(type) || Ext.isDate(value)) {
807 member = Ext.encode(value);
808 } else if (Ext.isArray(value)) {
809 member = dumpObject(value, level+1, maxLevel, withFunctions);
810 } else if (Ext.isObject(value)) {
811 member = dumpObject(value, level+1, maxLevel, withFunctions);
812 } else {
813 member = type;
814 }
815 members.push(spacer + name + ': ' + member); // or Ext.encode(name)
816 }
817 }
818 if (members.length) {
819 return prefix + '\n '+ members.join(',\n ') + '\n'+spacer+suffix;
820 }
821 return prefix+suffix;
822 }
823
824 function log (message) {
825 var options, dump,
826 con = Ext.global.console,
827 level = 'log',
828 indent = log.indent || 0,
829 prefix, stack, fn, out, max;
830
831 log.indent = indent;
832
833 if (typeof message !== 'string') {
834 options = message;
835 message = options.msg || '';
836 level = options.level || level;
837 dump = options.dump;
838 stack = options.stack;
839 prefix = options.prefix;
840 fn = options.fn;
841
842 if (options.indent) {
843 ++log.indent;
844 } else if (options.outdent) {
845 log.indent = indent = Math.max(indent - 1, 0);
846 }
847
848 if (dump && !(con && con.dir)) {
849 message += dumpObject(dump);
850 dump = null;
851 }
852 }
853
854 if (arguments.length > 1) {
855 message += Array.prototype.slice.call(arguments, 1).join('');
856 }
857
858 if (prefix) {
859 message = prefix + ' - ' + message;
860 }
861
862 message = indent ? Ext.String.repeat(' ', log.indentSize * indent) + message : message;
863 // w/o console, all messages are equal, so munge the level into the message:
864 if (level !== 'log') {
865 message = '[' + level.charAt(0).toUpperCase() + '] ' + message;
866 }
867
868 if (fn) {
869 message += '\nCaller: ' + fn.toString();
870 }
871
872 // Not obvious, but 'console' comes and goes when Firebug is turned on/off, so
873 // an early test may fail either direction if Firebug is toggled.
874 //
875 if (con) { // if (Firebug-like console)
876 if (con[level]) {
877 con[level](message);
878 } else {
879 con.log(message);
880 }
881
882 if (dump) {
883 con.dir(dump);
884 }
885
886 if (stack && con.trace) {
887 // Firebug's console.error() includes a trace already...
888 if (!con.firebug || level !== 'error') {
889 con.trace();
890 }
891 }
892 } else if (Ext.isOpera) {
893 opera.postError(message); // jshint ignore:line
894 } else {
895 out = log.out;
896 max = log.max;
897
898 if (out.length >= max) {
899 // this formula allows out.max to change (via debugger), where the
900 // more obvious "max/4" would not quite be the same
901 Ext.Array.erase(out, 0, out.length - 3 * Math.floor(max / 4)); // keep newest 75%
902 }
903
904 out.push(message);
905 }
906
907 // Mostly informational, but the Ext.Error notifier uses them:
908 ++log.count;
909 ++log.counters[level];
910 }
911
912 function logx (level, args) {
913 if (typeof args[0] === 'string') {
914 args.unshift({});
915 }
916 args[0].level = level;
917 log.apply(this, args);
918 }
919
920 log.error = function () {
921 logx('error', Array.prototype.slice.call(arguments));
922 };
923 log.info = function () {
924 logx('info', Array.prototype.slice.call(arguments));
925 };
926 log.warn = function () {
927 logx('warn', Array.prototype.slice.call(arguments));
928 };
929
930 log.count = 0;
931 log.counters = { error: 0, warn: 0, info: 0, log: 0 };
932 log.indentSize = 2;
933 log.out = [];
934 log.max = 750;
935
936 return log;
937 }()) ||
938 //</debug>
939 (function () {
940 var nullLog = function () {};
941 nullLog.info = nullLog.warn = nullLog.error = Ext.emptyFn;
942 return nullLog;
943 }())
944 });