/** * @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 * @method */ 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); }; })();