]>
git.proxmox.com Git - extjs.git/blob - extjs/packages/core/src/Util.js
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.
11 // shortcut for the special named scopes for listener scope resolution
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'
18 'self.controller': { isSelf
: 1, isController
: 1 }
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
;
28 numEscapeFn = function(match
, capture
){
29 return '\\00' + capture
.charCodeAt(0).toString(16) + ' ';
33 return validIdRe
.test(id
) ? id
:
34 // replace the number portion last to keep the trailing ' '
36 id
.replace(escapeRx
, escapeFn
).replace(leadingNumRx
, numEscapeFn
);
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
48 * For example, these calls are equivalent:
50 * var myFunc = this.myFunc;
52 * Ext.callback('myFunc', this, [arg1, arg2]);
53 * Ext.callback(myFunc, this, [arg1, arg2]);
55 * Ext.isFunction(myFunc) && this.myFunc(arg1, arg2);
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).
71 callback: function (callback
, scope
, args
, delay
, caller
, defaultScope
) {
76 var namedScope
= (scope
in Ext
._namedScopes
);
78 if (callback
.charAt
) { // if (isString(fn))
79 if ((!scope
|| namedScope
) && caller
) {
80 scope
= caller
.resolveListenerScope(namedScope
? scope
: defaultScope
);
83 if (!scope
|| !Ext
.isObject(scope
)) {
84 Ext
.raise('Named method "' + callback
+ '" requires a scope object');
86 if (!Ext
.isFunction(scope
[callback
])) {
87 Ext
.raise('No method named "' + callback
+ '" on ' +
88 (scope
.$className
|| 'scope object'));
92 callback
= scope
[callback
];
93 } else if (namedScope
) {
94 scope
= defaultScope
|| caller
;
101 if (callback
&& Ext
.isFunction(callback
)) {
102 scope
= scope
|| Ext
.global
;
104 Ext
.defer(callback
, delay
, scope
, args
);
105 } else if (Ext
.elevateFunction
) {
106 ret
= Ext
.elevateFunction(callback
, scope
, args
);
108 ret
= callback
.apply(scope
, args
);
110 ret
= callback
.call(scope
);
120 * Coerces the first value if possible so that it is comparable to the second value.
122 * Coercion only works between the basic atomic data types String, Boolean, Number, Date, null and undefined.
124 * Numbers and numeric strings are coerced to Dates using the value as the millisecond era value.
126 * Strings are coerced to Dates by parsing using the {@link Ext.Date#defaultFormat defaultFormat}.
130 * Ext.coerce('false', true);
132 * returns the boolean value `false` because the second parameter is of type `Boolean`.
134 * @param {Mixed} from The value to coerce
135 * @param {Mixed} to The value it must be compared against
136 * @return The coerced value.
138 coerce: function(from, to
) {
139 var fromType
= Ext
.typeOf(from),
140 toType
= Ext
.typeOf(to
),
141 isString
= typeof from === 'string';
143 if (fromType
!== toType
) {
150 return isString
&& (!from || from === 'false') ? false : Boolean(from);
152 return isString
&& (!from || from === 'null') ? null : from;
154 return isString
&& (!from || from === 'undefined') ? undefined : from;
156 return isString
&& isNaN(from) ? Ext
.Date
.parse(from, Ext
.Date
.defaultFormat
) : Date(Number(from));
165 * Copies a set of named properties fom the source object to the destination object.
169 * var foo = { a: 1, b: 2, c: 3 };
171 * var bar = Ext.copyTo({}, foo, 'a,c');
172 * // bar = { a: 1, c: 3 };
174 * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
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.
187 copyTo: function (dest
, source
, names
, usePrototypeKeys
) {
188 if (typeof names
=== 'string') {
189 names
= names
.split(Ext
.propertyNameSplitRe
);
192 for (var name
, i
= 0, n
= names
? names
.length
: 0; i
< n
; i
++) {
195 if (usePrototypeKeys
|| source
.hasOwnProperty(name
)) {
196 dest
[name
] = source
[name
];
205 * Copies a set of named properties fom the source object to the destination object.
209 * var foo = { a: 1, b: 2, c: 3 };
211 * var bar = Ext.copy({}, foo, 'a,c');
212 * // bar = { a: 1, c: 3 };
214 * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
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.
224 copy: function (dest
, source
, names
, usePrototypeKeys
) {
225 if (typeof names
=== 'string') {
226 names
= names
.split(Ext
.propertyNameSplitRe
);
229 for (var name
, i
= 0, n
= names
? names
.length
: 0; i
< n
; i
++) {
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
];
243 propertyNameSplitRe
: /[,;\s]+/,
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.
253 * var foo = { a: 1, b: 2, c: 3 };
255 * var bar = Ext.copyToIf({ a:42 }, foo, 'a,c');
256 * // bar = { a: 42, c: 3 };
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.
267 copyToIf: function (destination
, source
, names
) {
268 if (typeof names
=== 'string') {
269 names
= names
.split(Ext
.propertyNameSplitRe
);
272 for (var name
, i
= 0, n
= names
? names
.length
: 0; i
< n
; i
++) {
275 if (destination
[name
] === undefined) {
276 destination
[name
] = source
[name
];
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.
291 * var foo = { a: 1, b: 2, c: 3 };
293 * var bar = Ext.copyIf({ a:42 }, foo, 'a,c');
294 * // bar = { a: 42, c: 3 };
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.
302 copyIf: function (destination
, source
, names
) {
303 if (typeof names
=== 'string') {
304 names
= names
.split(Ext
.propertyNameSplitRe
);
307 for (var name
, i
= 0, n
= names
? names
.length
: 0; i
< n
; i
++) {
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
];
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
328 extend
: (function() {
330 var objectConstructor
= Object
.prototype.constructor,
331 inlineOverrides = function(o
) {
333 if (!o
.hasOwnProperty(m
)) {
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
);
354 sourceMethod
: 'extend',
355 msg
: 'Attempting to extend from a class which has not been loaded on the page.'
360 // We create a new temporary class
361 var F = function() {},
362 subclassProto
, superclassProto
= superclass
.prototype;
364 F
.prototype = superclassProto
;
365 subclassProto
= subclass
.prototype = new F();
366 subclassProto
.constructor = subclass
;
367 subclass
.superclass
= superclassProto
;
369 if (superclassProto
.constructor === objectConstructor
) {
370 superclassProto
.constructor = superclass
;
373 subclass
.override = function(overrides
) {
374 Ext
.override(subclass
, overrides
);
377 subclassProto
.override
= inlineOverrides
;
378 subclassProto
.proto
= subclassProto
;
380 subclass
.override(overrides
);
381 subclass
.extend = function(o
) {
382 return Ext
.extend(subclass
, o
);
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.
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.
402 iterate: function(object
, fn
, scope
) {
403 if (Ext
.isEmpty(object
)) {
407 if (scope
=== undefined) {
411 if (Ext
.isIterable(object
)) {
412 Ext
.Array
.each
.call(Ext
.Array
, object
, fn
, scope
);
415 Ext
.Object
.each
.call(Ext
.Object
, object
, fn
, scope
);
419 _resourcePoolRe
: /^[<]([^<>@:]*)(?:[@]([^<>@:]+))?[>](.+)$/,
422 * Resolves a resource URL that may contain a resource pool identifier token at the
423 * front. The tokens are formatted as HTML tags "<poolName@packageName>" followed
424 * by a normal relative path. This token is only processed if present at the first
425 * character of the given string.
427 * These tokens are parsed and the pieces are then passed to the
428 * {@link Ext#getResourcePath} method.
434 * src: '<shared>images/foo.png'
437 * src: '<@package>images/foo.png'
440 * src: '<shared@package>images/foo.png'
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.
446 * @param {String} url The URL that may contain a resource pool token at the front.
450 resolveResource: function (url
) {
454 if (url
&& url
.charAt(0) === '<') {
455 m
= Ext
._resourcePoolRe
.exec(url
);
457 ret
= Ext
.getResourcePath(m
[3], m
[1], m
[2]);
468 * @inheritdoc Ext.Object#toQueryString
469 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
471 urlEncode: function () {
472 var args
= Ext
.Array
.from(arguments
),
475 // Support for the old `pre` argument
476 if (Ext
.isString(args
[1])) {
477 prefix
= args
[1] + '&';
481 return prefix
+ Ext
.Object
.toQueryString
.apply(Ext
.Object
, args
);
485 * Alias for {@link Ext.Object#fromQueryString}.
489 * @inheritdoc Ext.Object#fromQueryString
490 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
492 urlDecode: function() {
493 return Ext
.Object
.fromQueryString
.apply(Ext
.Object
, arguments
);
497 * @method getScrollbarSize
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.
506 getScrollbarSize: function (force
) {
508 if (!Ext
.isDomReady
) {
509 Ext
.raise("getScrollbarSize called before DomReady");
513 var scrollbarSize
= Ext
._scrollbarSize
;
515 if (force
|| !scrollbarSize
) {
516 var db
= document
.body
,
517 div
= document
.createElement('div');
519 div
.style
.width
= div
.style
.height
= '100px';
520 div
.style
.overflow
= 'scroll';
521 div
.style
.position
= 'absolute';
523 db
.appendChild(div
); // now we can measure the div...
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
534 return scrollbarSize
;
540 * Returns the type of the given variable in string format. List of possible values are:
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
556 * @param {Object} value
559 typeOf
: (function () {
560 var nonWhitespaceRe
= /\S/,
561 toString
= Object
.prototype.toString
,
569 '[object Array]' : 'array',
570 '[object Date]' : 'date',
571 '[object Boolean]': 'boolean',
572 '[object Number]' : 'number',
573 '[object RegExp]' : 'regexp'
576 return function(value
) {
577 if (value
=== null) {
581 var type
= typeof value
,
584 if (typeofTypes
[type
]) {
588 ret
= toStringTypes
[typeToString
= toString
.call(value
)];
593 if (type
=== 'function') {
597 if (type
=== 'object') {
598 if (value
.nodeType
!== undefined) {
599 if (value
.nodeType
=== 3) {
600 return nonWhitespaceRe
.test(value
.nodeValue
) ? 'textnode' : 'whitespace';
613 sourceMethod
: 'typeOf',
614 msg
: 'Failed to determine the type of "' + value
+ '".'
623 * A global factory method to instantiate a class from a config object. For example,
624 * these two calls are equivalent:
626 * Ext.factory({ text: 'My Button' }, 'Ext.Button');
627 * Ext.create('Ext.Button', { text: 'My Button' });
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:
633 * button = Ext.factory({ text: 'New Button' }, 'Ext.Button', button); // Button created
634 * button = Ext.factory({ text: 'Updated Button' }, 'Ext.Button', button); // Button updated
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]
642 factory: function(config
, classReference
, instance
, aliasNamespace
) {
643 var manager
= Ext
.ClassManager
,
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
) {
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
);
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
);
667 if (config
=== true) {
669 if (!instance
&& !classReference
) {
670 Ext
.raise('[Ext.factory] Cannot determine type of class to create');
673 return instance
|| Ext
.create(classReference
);
677 if (!Ext
.isObject(config
)) {
678 Ext
.raise("Invalid config, must be a valid config object");
682 if ('xtype' in config
) {
683 newInstance
= manager
.instantiateByAlias('widget.' + config
.xtype
, config
);
685 else if ('xclass' in config
) {
686 newInstance
= Ext
.create(config
.xclass
, config
);
698 return instance
.setConfig(config
);
701 return Ext
.create(classReference
, config
);
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).
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:
716 * Ext.log({ indent: 1 }, '>> foo');
718 * // log statements in here or methods called from here will be indented
721 * Ext.log({ outdent: 1 }, '<< foo');
724 * This method does nothing in a release build.
726 * @param {String/Object} [options] The message to log or an options object with any
727 * of the following properties:
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.
736 * @param {String...} [message] The message to log (required unless specified in
743 * Iterate through an object to dump its content into a string.
751 * strokeStyle: "#555",
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
771 var primitiveRe
= /string|number|boolean/;
772 function dumpObject (object
, level
, maxLevel
, withFunctions
) {
773 var member
, type
, value
, name
, prefix
, suffix
,
776 if (Ext
.isArray(object
)) {
779 } else if (Ext
.isObject(object
)) {
786 if (level
> maxLevel
) {
787 return prefix
+'...'+suffix
;
791 var spacer
= (new Array(level
)).join(' ');
793 // Cannot use Ext.encode since it can recurse endlessly
794 for (name
in object
) {
795 if (object
.hasOwnProperty(name
)) {
796 value
= object
[name
];
799 if (type
=== 'function') {
800 if (!withFunctions
) {
804 } else if (type
=== 'undefined') {
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
);
815 members
.push(spacer
+ name
+ ': ' + member
); // or Ext.encode(name)
818 if (members
.length
) {
819 return prefix
+ '\n '+ members
.join(',\n ') + '\n'+spacer
+suffix
;
821 return prefix
+suffix
;
824 function log (message
) {
826 con
= Ext
.global
.console
,
828 indent
= log
.indent
|| 0,
829 prefix
, stack
, fn
, out
, max
;
833 if (typeof message
!== 'string') {
835 message
= options
.msg
|| '';
836 level
= options
.level
|| level
;
838 stack
= options
.stack
;
839 prefix
= options
.prefix
;
842 if (options
.indent
) {
844 } else if (options
.outdent
) {
845 log
.indent
= indent
= Math
.max(indent
- 1, 0);
848 if (dump
&& !(con
&& con
.dir
)) {
849 message
+= dumpObject(dump
);
854 if (arguments
.length
> 1) {
855 message
+= Array
.prototype.slice
.call(arguments
, 1).join('');
859 message
= prefix
+ ' - ' + message
;
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
;
869 message
+= '\nCaller: ' + fn
.toString();
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.
875 if (con
) { // if (Firebug-like console)
886 if (stack
&& con
.trace
) {
887 // Firebug's console.error() includes a trace already...
888 if (!con
.firebug
|| level
!== 'error') {
892 } else if (Ext
.isOpera
) {
893 opera
.postError(message
); // jshint ignore:line
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%
907 // Mostly informational, but the Ext.Error notifier uses them:
909 ++log
.counters
[level
];
912 function logx (level
, args
) {
913 if (typeof args
[0] === 'string') {
916 args
[0].level
= level
;
917 log
.apply(this, args
);
920 log
.error = function () {
921 logx('error', Array
.prototype.slice
.call(arguments
));
923 log
.info = function () {
924 logx('info', Array
.prototype.slice
.call(arguments
));
926 log
.warn = function () {
927 logx('warn', Array
.prototype.slice
.call(arguments
));
931 log
.counters
= { error
: 0, warn
: 0, info
: 0, log
: 0 };
940 var nullLog = function () {};
941 nullLog
.info
= nullLog
.warn
= nullLog
.error
= Ext
.emptyFn
;