/**
* @author Jacky Nguyen <jacky@sencha.com>
* @docauthor Jacky Nguyen <jacky@sencha.com>
* @class Ext.Object
*
* A collection of useful static methods to deal with objects
*
* @singleton
*/
(function() {
var ExtObject = Ext.Object = {
/**
* Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
* query strings. For example:
var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
// objects then equals:
[
{ name: 'hobbies', value: 'reading' },
{ name: 'hobbies', value: 'cooking' },
{ name: 'hobbies', value: 'swimming' },
];
var objects = Ext.Object.toQueryObjects('dateOfBirth', {
day: 3,
month: 8,
year: 1987,
extra: {
hour: 4
minute: 30
}
}, true); // Recursive
// objects then equals:
[
{ name: 'dateOfBirth[day]', value: 3 },
{ name: 'dateOfBirth[month]', value: 8 },
{ name: 'dateOfBirth[year]', value: 1987 },
{ name: 'dateOfBirth[extra][hour]', value: 4 },
{ name: 'dateOfBirth[extra][minute]', value: 30 },
];
* @param {String} name
* @param {Mixed} value
* @param {Boolean} recursive
* @markdown
*/
toQueryObjects: function(name, value, recursive) {
var self = ExtObject.toQueryObjects,
objects = [],
i, ln;
if (Ext.isArray(value)) {
for (i = 0, ln = value.length; i < ln; i++) {
if (recursive) {
objects = objects.concat(self(name + '[' + i + ']', value[i], true));
}
else {
objects.push({
name: name,
value: value[i]
});
}
}
}
else if (Ext.isObject(value)) {
for (i in value) {
if (value.hasOwnProperty(i)) {
if (recursive) {
objects = objects.concat(self(name + '[' + i + ']', value[i], true));
}
else {
objects.push({
name: name,
value: value[i]
});
}
}
}
}
else {
objects.push({
name: name,
value: value
});
}
return objects;
},
/**
* Takes an object and converts it to an encoded query string
- Non-recursive:
Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
- Recursive:
Ext.Object.toQueryString({
username: 'Jacky',
dateOfBirth: {
day: 1,
month: 2,
year: 1911
},
hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
}, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
// username=Jacky
// &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
// &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
*
* @param {Object} object The object to encode
* @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
* (PHP / Ruby on Rails servers and similar). Defaults to false
* @return {String} queryString
* @markdown
*/
toQueryString: function(object, recursive) {
var paramObjects = [],
params = [],
i, j, ln, paramObject, value;
for (i in object) {
if (object.hasOwnProperty(i)) {
paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
}
}
for (j = 0, ln = paramObjects.length; j < ln; j++) {
paramObject = paramObjects[j];
value = paramObject.value;
if (Ext.isEmpty(value)) {
value = '';
}
else if (Ext.isDate(value)) {
value = Ext.Date.toString(value);
}
params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
}
return params.join('&');
},
/**
* Converts a query string back into an object.
*
- Non-recursive:
Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
- Recursive:
Ext.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
// returns
{
username: 'Jacky',
dateOfBirth: {
day: '1',
month: '2',
year: '1911'
},
hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
}
* @param {String} queryString The query string to decode
* @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
* PHP / Ruby on Rails servers and similar. Defaults to false
* @return {Object}
*/
fromQueryString: function(queryString, recursive) {
var parts = queryString.replace(/^\?/, '').split('&'),
object = {},
temp, components, name, value, i, ln,
part, j, subLn, matchedKeys, matchedName,
keys, key, nextKey;
for (i = 0, ln = parts.length; i < ln; i++) {
part = parts[i];
if (part.length > 0) {
components = part.split('=');
name = decodeURIComponent(components[0]);
value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
if (!recursive) {
if (object.hasOwnProperty(name)) {
if (!Ext.isArray(object[name])) {
object[name] = [object[name]];
}
object[name].push(value);
}
else {
object[name] = value;
}
}
else {
matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
matchedName = name.match(/^([^\[]+)/);
//<debug error>
if (!matchedName) {
Ext.Error.raise({
sourceClass: "Ext.Object",
sourceMethod: "fromQueryString",
queryString: queryString,
recursive: recursive,
msg: 'Malformed query string given, failed parsing name from "' + part + '"'
});
}
//</debug>
name = matchedName[0];
keys = [];
if (matchedKeys === null) {
object[name] = value;
continue;
}
for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
key = matchedKeys[j];
key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
keys.push(key);
}
keys.unshift(name);
temp = object;
for (j = 0, subLn = keys.length; j < subLn; j++) {
key = keys[j];
if (j === subLn - 1) {
if (Ext.isArray(temp) && key === '') {
temp.push(value);
}
else {
temp[key] = value;
}
}
else {
if (temp[key] === undefined || typeof temp[key] === 'string') {
nextKey = keys[j+1];
temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
}
temp = temp[key];
}
}
}
}
}
return object;
},
/**
* Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
* by returning `false` in the callback function. For example:
var person = {
name: 'Jacky'
hairColor: 'black'
loves: ['food', 'sleeping', 'wife']
};
Ext.Object.each(person, function(key, value, myself) {
console.log(key + ":" + value);
if (key === 'hairColor') {
return false; // stop the iteration
}
});
* @param {Object} object The object to iterate
* @param {Function} fn The callback function. Passed arguments for each iteration are:
- {String} `key`
- {Mixed} `value`
- {Object} `object` The object itself
* @param {Object} scope (Optional) The execution scope (`this`) of the callback function
* @markdown
*/
each: function(object, fn, scope) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
if (fn.call(scope || object, property, object[property], object) === false) {
return;
}
}
}
},
/**
* Merges any number of objects recursively without referencing them or their children.
var extjs = {
companyName: 'Ext JS',
products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
isSuperCool: true
office: {
size: 2000,
location: 'Palo Alto',
isFun: true
}
};
var newStuff = {
companyName: 'Sencha Inc.',
products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
office: {
size: 40000,
location: 'Redwood City'
}
};
var sencha = Ext.Object.merge(extjs, newStuff);
// extjs and sencha then equals to
{
companyName: 'Sencha Inc.',
products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
isSuperCool: true
office: {
size: 30000,
location: 'Redwood City'
isFun: true
}
}
* @param {Object} object,...
* @return {Object} merged The object that is created as a result of merging all the objects passed in.
* @markdown
*/
merge: function(source, key, value) {
if (typeof key === 'string') {
if (value && value.constructor === Object) {
if (source[key] && source[key].constructor === Object) {
ExtObject.merge(source[key], value);
}
else {
source[key] = Ext.clone(value);
}
}
else {
source[key] = value;
}
return source;
}
var i = 1,
ln = arguments.length,
object, property;
for (; i < ln; i++) {
object = arguments[i];
for (property in object) {
if (object.hasOwnProperty(property)) {
ExtObject.merge(source, property, object[property]);
}
}
}
return source;
},
/**
* Returns the first matching key corresponding to the given value.
* If no matching value is found, null is returned.
var person = {
name: 'Jacky',
loves: 'food'
};
alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
* @param {Object} object
* @param {Object} value The value to find
* @markdown
*/
getKey: function(object, value) {
for (var property in object) {
if (object.hasOwnProperty(property) && object[property] === value) {
return property;
}
}
return null;
},
/**
* Gets all values of the given object as an array.
var values = Ext.Object.getValues({
name: 'Jacky',
loves: 'food'
}); // ['Jacky', 'food']
* @param {Object} object
* @return {Array} An array of values from the object
* @markdown
*/
getValues: function(object) {
var values = [],
property;
for (property in object) {
if (object.hasOwnProperty(property)) {
values.push(object[property]);
}
}
return values;
},
/**
* Gets all keys of the given object as an array.
var values = Ext.Object.getKeys({
name: 'Jacky',
loves: 'food'
}); // ['name', 'loves']
* @param {Object} object
* @return {Array} An array of keys from the object
*/
getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
var keys = [],
property;
for (property in object) {
if (object.hasOwnProperty(property)) {
keys.push(property);
}
}
return keys;
},
/**
* Gets the total number of this object's own properties
var size = Ext.Object.getSize({
name: 'Jacky',
loves: 'food'
}); // size equals 2
* @param {Object} object
* @return {Number} size
* @markdown
*/
getSize: function(object) {
var size = 0,
property;
for (property in object) {
if (object.hasOwnProperty(property)) {
size++;
}
}
return size;
}
};
/**
* A convenient alias method for {@link Ext.Object#merge}
*
* @member Ext
* @method merge
*/
Ext.merge = Ext.Object.merge;
/**
* A convenient alias method for {@link Ext.Object#toQueryString}
*
* @member Ext
* @method urlEncode
* @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
*/
Ext.urlEncode = function() {
var args = Ext.Array.from(arguments),
prefix = '';
// Support for the old `pre` argument
if ((typeof args[1] === 'string')) {
prefix = args[1] + '&';
args[1] = false;
}
return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
};
/**
* A convenient alias method for {@link Ext.Object#fromQueryString}
*
* @member Ext
* @method urlDecode
* @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
*/
Ext.urlDecode = function() {
return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
};
})();