(function() { var Ext = window.Ext4 || window.Ext; /** * A generic ModelFactory that will delegate calls to Rally.data.ModelFactory.getModels(s) to * the correct model factory. Model factories must register themselves to this class using the * ::registerType(s) methods. * * This class is meant to abstract where models come from so all the user has to do is get * models by standardized model names. * * See [Data Models](#!/guide/data_models) for more information on working with models. * * Rally.data.ModelFactory.getModel({ * type: 'Defect', * context: { * workspace: '/workspace/12345' * } * success: function(model) { * * } * }); */ Ext.define('Rally.data.ModelFactory', { singleton: true, uses: [ 'Rally.data.wsapi.ModelFactory' ], /** * @property {Object} types * A hash object of types. This is where factories are registered based on a type string */ constructor: function(config) { Ext.apply(this, config || {}); this.types = {}; }, /** * Register a model factory to create a model for a given model type, exp: 'userstory' * @param {String} type The type/key string representing a model. Ex: 'userstory' * @param {Class} factory A factory that implements stuff to get models given a config */ registerType: function (type, factory) { this.types[this._getCompatibleType(type)] = factory; }, /** * Register a model factory to create models for an array of model types * @param {Array} types An array of type strings to register * @param {Class} factory */ registerTypes: function (types, factory) { _.each(types, function (type) { this.registerType(type, factory); }, this); }, clearTypes: function () { this.types = {}; }, getFactory: function (type) { return this.types[this._getCompatibleType(type)]; }, /** * Get a model * @param {Object} options * @param {String} options.type the type to build the model for, e.g., defect * @param {Object} [options.context] An object containing the scope (workspace) where the model is defined * If not specified it will default to the workspace defined by Rally.env.Environment#getContext. * @param {String/Number} [options.wsapiVersion] The WSAPI version to use when building the model * If not specified it will default to the version defined by Rally.env.Environment#getServer. * @param {Function} [options.success] success callback, given the model * @param {Rally.data.Model} options.success.model The matching model object * @param {Object} [options.scope=this] an object to call the success callback against * @param {Function} [options.failure] if we can't build * @return {Deft.Promise} promise The promise to be resolved or rejected by the delegated model factory. The alternative is using success and failure callbacks * * Rally.data.ModelFactory.getModel({ * type: 'Defect', * context: { * workspace: '/workspace/12345' * } * success: function(model) { * * } * }); */ getModel: function(options) { // <debug> if (!options.type || !this.getFactory(options.type)) { Ext.Error.raise('Could not find registered factory for type: ' + options.type); } // </debug> var factory = this.getFactory(options.type); return factory.getModel.call(factory, options); }, /** * Get multiple models. * @param options * @param {String[]} options.types An array of types to build the model for, e.g., [defect,task] * @param {Object} options.context An object containing the scope (workspace) where the model is defined * If not specified it will default to the workspace defined by Rally.env.Environment#getContext. * @param {String/Number} options.wsapiVersion The WSAPI version to use when building the model * If not specified it will default to the version defined by Rally.env.Environment#getServer. * @param {Function} options.success success callback, returns an object with property names set to the name of the model: ie models.defect is the defect model * @param {Object} options.success.models Matching models * @param {Object} [options.scope=window] an object to call the success callback against (optional) * @param {Function} options.failure called if an error occurs * @return {Deft.Promise} promise The promise to be resolved or rejected by the delegated model factories. The alternative is using success and failure callbacks * * Rally.data.ModelFactory.getModels({ * types: ['Defect', 'UserStory'], * context: { * workspace: '/workspace/12345' * } * success: function(models) { * var defectModel = models.Defect; * } * }); */ getModels: function(options) { var me = Rally.data.ModelFactory, successFn = options.success, failureFn = options.failure, factories = {}, promises = []; // remove success and failure callbacks from the config to force promises options.success = Ext.emptyFn; options.failure = Ext.emptyFn; options.types = this._ensureTypesAreDefined(options.types); _.each(options.types, function(type) { // <debug> if (!me.getFactory(me._getCompatibleType(type))) { Ext.Error.raise('Factory is not registered for type: ' + type); } // </debug> var factory = me.getFactory(me._getCompatibleType(type)).$className; // batch up types to the correct factory if (factories[factory]) { factories[factory].push(type); } else { factories[factory] = [type]; } }, me); _.each(factories, function (types, className) { var factory = Ext.ClassManager.get(className); // create a new options object and override the types var promise = factory.getModels(Ext.applyIf({ types: types }, options)); // <debug> if (promise === undefined) { Ext.Error.raise(className + '.getModels should return a value or a promise'); } // </debug> promises.push(promise); }); // return a new promise that will wait for all promises to resolve // Also call any passed callbacks var deferred = new Deft.Deferred(); Deft.Promise.all(promises).then(function(values) { if (successFn) { // Prevent unpredictable sync/async paths Ext.callback(successFn, options.scope, [me._flattenPromiseValues(values)], 1); } deferred.resolve(me._flattenPromiseValues(values)); }).otherwise(function (err) { if (failureFn) { // Prevent unpredictable sync/async paths Ext.callback(failureFn, options.scope, [err], 1); } deferred.reject(err); }); return deferred.promise; }, _ensureTypesAreDefined: function(types) { // <debug> if (!types) { Ext.Error.raise('getModels requires "types" to be defined'); } // </debug> types = _.isArray(types) ? types : [types]; // <debug> if (!types.length) { Ext.Error.raise('getModels requires "types" to be defined'); } // </debug> return types; }, _getCompatibleType: function (type) { if(type) { return type.toLowerCase().replace(/\s/g, '').split('/')[0]; } }, _flattenPromiseValues: function (values) { var flattenedValue = {}; _.each(values, function(value) { Ext.apply(flattenedValue, value); }); return flattenedValue; } }); })();