PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/ext-4.1.0_b3/src/data/Model.js

https://bitbucket.org/srogerf/javascript
JavaScript | 1519 lines | 701 code | 162 blank | 656 comment | 149 complexity | 48e53a990f903d50d13e37ab49add77f MD5 | raw file
  1. /**
  2. * @author Ed Spencer
  3. *
  4. * A Model represents some object that your application manages. For example, one might define a Model for Users,
  5. * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
  6. * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
  7. * of the data-bound components in Ext.
  8. *
  9. * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
  10. *
  11. * Ext.define('User', {
  12. * extend: 'Ext.data.Model',
  13. * fields: [
  14. * {name: 'name', type: 'string'},
  15. * {name: 'age', type: 'int'},
  16. * {name: 'phone', type: 'string'},
  17. * {name: 'alive', type: 'boolean', defaultValue: true}
  18. * ],
  19. *
  20. * changeName: function() {
  21. * var oldName = this.get('name'),
  22. * newName = oldName + " The Barbarian";
  23. *
  24. * this.set('name', newName);
  25. * }
  26. * });
  27. *
  28. * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
  29. * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
  30. *
  31. * Now we can create instances of our User model and call any model logic we defined:
  32. *
  33. * var user = Ext.create('User', {
  34. * name : 'Conan',
  35. * age : 24,
  36. * phone: '555-555-5555'
  37. * });
  38. *
  39. * user.changeName();
  40. * user.get('name'); //returns "Conan The Barbarian"
  41. *
  42. * # Validations
  43. *
  44. * Models have built-in support for validations, which are executed against the validator functions in {@link
  45. * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
  46. * models:
  47. *
  48. * Ext.define('User', {
  49. * extend: 'Ext.data.Model',
  50. * fields: [
  51. * {name: 'name', type: 'string'},
  52. * {name: 'age', type: 'int'},
  53. * {name: 'phone', type: 'string'},
  54. * {name: 'gender', type: 'string'},
  55. * {name: 'username', type: 'string'},
  56. * {name: 'alive', type: 'boolean', defaultValue: true}
  57. * ],
  58. *
  59. * validations: [
  60. * {type: 'presence', field: 'age'},
  61. * {type: 'length', field: 'name', min: 2},
  62. * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
  63. * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
  64. * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
  65. * ]
  66. * });
  67. *
  68. * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
  69. * object:
  70. *
  71. * var instance = Ext.create('User', {
  72. * name: 'Ed',
  73. * gender: 'Male',
  74. * username: 'edspencer'
  75. * });
  76. *
  77. * var errors = instance.validate();
  78. *
  79. * # Associations
  80. *
  81. * Models can have associations with other Models via {@link Ext.data.association.HasOne},
  82. * {@link Ext.data.association.BelongsTo belongsTo} and {@link Ext.data.association.HasMany hasMany} associations.
  83. * For example, let's say we're writing a blog administration application which deals with Users, Posts and Comments.
  84. * We can express the relationships between these models like this:
  85. *
  86. * Ext.define('Post', {
  87. * extend: 'Ext.data.Model',
  88. * fields: ['id', 'user_id'],
  89. *
  90. * belongsTo: 'User',
  91. * hasMany : {model: 'Comment', name: 'comments'}
  92. * });
  93. *
  94. * Ext.define('Comment', {
  95. * extend: 'Ext.data.Model',
  96. * fields: ['id', 'user_id', 'post_id'],
  97. *
  98. * belongsTo: 'Post'
  99. * });
  100. *
  101. * Ext.define('User', {
  102. * extend: 'Ext.data.Model',
  103. * fields: ['id'],
  104. *
  105. * hasMany: [
  106. * 'Post',
  107. * {model: 'Comment', name: 'comments'}
  108. * ]
  109. * });
  110. *
  111. * See the docs for {@link Ext.data.association.HasOne}, {@link Ext.data.association.BelongsTo} and
  112. * {@link Ext.data.association.HasMany} for details on the usage and configuration of associations.
  113. * Note that associations can also be specified like this:
  114. *
  115. * Ext.define('User', {
  116. * extend: 'Ext.data.Model',
  117. * fields: ['id'],
  118. *
  119. * associations: [
  120. * {type: 'hasMany', model: 'Post', name: 'posts'},
  121. * {type: 'hasMany', model: 'Comment', name: 'comments'}
  122. * ]
  123. * });
  124. *
  125. * # Using a Proxy
  126. *
  127. * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
  128. * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
  129. * can be set directly on the Model:
  130. *
  131. * Ext.define('User', {
  132. * extend: 'Ext.data.Model',
  133. * fields: ['id', 'name', 'email'],
  134. *
  135. * proxy: {
  136. * type: 'rest',
  137. * url : '/users'
  138. * }
  139. * });
  140. *
  141. * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
  142. * RESTful backend. Let's see how this works:
  143. *
  144. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  145. *
  146. * user.save(); //POST /users
  147. *
  148. * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
  149. * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
  150. * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
  151. * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
  152. *
  153. * Loading data via the Proxy is equally easy:
  154. *
  155. * //get a reference to the User model class
  156. * var User = Ext.ModelManager.getModel('User');
  157. *
  158. * //Uses the configured RestProxy to make a GET request to /users/123
  159. * User.load(123, {
  160. * success: function(user) {
  161. * console.log(user.getId()); //logs 123
  162. * }
  163. * });
  164. *
  165. * Models can also be updated and destroyed easily:
  166. *
  167. * //the user Model we loaded in the last snippet:
  168. * user.set('name', 'Edward Spencer');
  169. *
  170. * //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
  171. * user.save({
  172. * success: function() {
  173. * console.log('The User was updated');
  174. * }
  175. * });
  176. *
  177. * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
  178. * user.destroy({
  179. * success: function() {
  180. * console.log('The User was destroyed!');
  181. * }
  182. * });
  183. *
  184. * # Usage in Stores
  185. *
  186. * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
  187. * creating a {@link Ext.data.Store Store}:
  188. *
  189. * var store = Ext.create('Ext.data.Store', {
  190. * model: 'User'
  191. * });
  192. *
  193. * //uses the Proxy we set up on Model to load the Store data
  194. * store.load();
  195. *
  196. * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
  197. * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
  198. * Ext.data.Store Store docs} for more information on Stores.
  199. *
  200. * @constructor
  201. * Creates new Model instance.
  202. * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
  203. */
  204. Ext.define('Ext.data.Model', {
  205. alternateClassName: 'Ext.data.Record',
  206. mixins: {
  207. observable: 'Ext.util.Observable'
  208. },
  209. requires: [
  210. 'Ext.ModelManager',
  211. 'Ext.data.IdGenerator',
  212. 'Ext.data.Field',
  213. 'Ext.data.Errors',
  214. 'Ext.data.Operation',
  215. 'Ext.data.validations',
  216. 'Ext.data.proxy.Ajax',
  217. 'Ext.util.MixedCollection'
  218. ],
  219. sortConvertFields: function(f1, f2) {
  220. var f1SpecialConvert = f1.type && f1.convert !== f1.type.convert,
  221. f2SpecialConvert = f2.type && f2.convert !== f2.type.convert;
  222. if (f1SpecialConvert && !f2SpecialConvert) {
  223. return 1;
  224. }
  225. if (!f1SpecialConvert && f2SpecialConvert) {
  226. return -1;
  227. }
  228. return 0;
  229. },
  230. itemNameFn: function(item) {
  231. return item.name;
  232. },
  233. onClassExtended: function(cls, data, hooks) {
  234. var onBeforeClassCreated = hooks.onBeforeCreated;
  235. hooks.onBeforeCreated = function(cls, data) {
  236. var me = this,
  237. name = Ext.getClassName(cls),
  238. prototype = cls.prototype,
  239. superCls = cls.prototype.superclass,
  240. validations = data.validations || [],
  241. fields = data.fields || [],
  242. associations = data.associations || [],
  243. belongsTo = data.belongsTo,
  244. hasMany = data.hasMany,
  245. hasOne = data.hasOne,
  246. addAssociations = function(items, type) {
  247. var i = 0,
  248. len,
  249. item;
  250. if (items) {
  251. items = Ext.Array.from(items);
  252. for (len = items.length; i < len; ++i) {
  253. item = items[i];
  254. if (!Ext.isObject(item)) {
  255. item = {model: item};
  256. }
  257. item.type = type;
  258. associations.push(item);
  259. }
  260. }
  261. },
  262. idgen = data.idgen,
  263. fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
  264. associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
  265. superValidations = superCls.validations,
  266. superFields = superCls.fields,
  267. superAssociations = superCls.associations,
  268. association, i, ln,
  269. dependencies = [],
  270. idProperty = data.idProperty || cls.prototype.idProperty,
  271. fieldConvertSortFn = Ext.Function.bind(
  272. fieldsMixedCollection.sortBy,
  273. fieldsMixedCollection,
  274. [prototype.sortConvertFields], false);
  275. // Save modelName on class and its prototype
  276. cls.modelName = name;
  277. prototype.modelName = name;
  278. // Merge the validations of the superclass and the new subclass
  279. if (superValidations) {
  280. validations = superValidations.concat(validations);
  281. }
  282. data.validations = validations;
  283. // Merge the fields of the superclass and the new subclass
  284. if (superFields) {
  285. fields = superFields.items.concat(fields);
  286. }
  287. fieldsMixedCollection.on({
  288. add: fieldConvertSortFn,
  289. replace: fieldConvertSortFn
  290. });
  291. for (i = 0, ln = fields.length; i < ln; ++i) {
  292. fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
  293. }
  294. if (!fieldsMixedCollection.get(idProperty)) {
  295. fieldsMixedCollection.add(new Ext.data.Field(idProperty));
  296. }
  297. data.fields = fieldsMixedCollection;
  298. if (idgen) {
  299. data.idgen = Ext.data.IdGenerator.get(idgen);
  300. }
  301. //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
  302. //we support that here
  303. addAssociations(data.belongsTo, 'belongsTo');
  304. delete data.belongsTo;
  305. addAssociations(data.hasMany, 'hasMany');
  306. delete data.hasMany;
  307. addAssociations(data.hasOne, 'hasOne');
  308. delete data.hasOne;
  309. if (superAssociations) {
  310. associations = superAssociations.items.concat(associations);
  311. }
  312. for (i = 0, ln = associations.length; i < ln; ++i) {
  313. dependencies.push('association.' + associations[i].type.toLowerCase());
  314. }
  315. if (data.proxy) {
  316. if (typeof data.proxy === 'string') {
  317. dependencies.push('proxy.' + data.proxy);
  318. }
  319. else if (typeof data.proxy.type === 'string') {
  320. dependencies.push('proxy.' + data.proxy.type);
  321. }
  322. }
  323. Ext.require(dependencies, function() {
  324. Ext.ModelManager.registerType(name, cls);
  325. for (i = 0, ln = associations.length; i < ln; ++i) {
  326. association = associations[i];
  327. Ext.apply(association, {
  328. ownerModel: name,
  329. associatedModel: association.model
  330. });
  331. if (Ext.ModelManager.getModel(association.model) === undefined) {
  332. Ext.ModelManager.registerDeferredAssociation(association);
  333. } else {
  334. associationsMixedCollection.add(Ext.data.association.Association.create(association));
  335. }
  336. }
  337. data.associations = associationsMixedCollection;
  338. onBeforeClassCreated.call(me, cls, data, hooks);
  339. cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
  340. // Fire the onModelDefined template method on ModelManager
  341. Ext.ModelManager.onModelDefined(cls);
  342. });
  343. };
  344. },
  345. inheritableStatics: {
  346. /**
  347. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  348. * {@link Ext#createByAlias Ext.createByAlias}.
  349. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  350. * @return {Ext.data.proxy.Proxy}
  351. * @static
  352. * @inheritable
  353. */
  354. setProxy: function(proxy) {
  355. //make sure we have an Ext.data.proxy.Proxy object
  356. if (!proxy.isProxy) {
  357. if (typeof proxy == "string") {
  358. proxy = {
  359. type: proxy
  360. };
  361. }
  362. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  363. }
  364. proxy.setModel(this);
  365. this.proxy = this.prototype.proxy = proxy;
  366. return proxy;
  367. },
  368. /**
  369. * Returns the configured Proxy for this Model
  370. * @return {Ext.data.proxy.Proxy} The proxy
  371. * @static
  372. * @inheritable
  373. */
  374. getProxy: function() {
  375. return this.proxy;
  376. },
  377. /**
  378. * Apply a new set of field definitions to the existing model. This will replace any existing
  379. * fields, including fields inherited from superclasses. Mainly for reconfiguring the
  380. * model based on changes in meta data (called from Reader's onMetaChange method).
  381. * @static
  382. * @inheritable
  383. */
  384. setFields: function(fields) {
  385. var me = this,
  386. prototypeFields = me.prototype.fields,
  387. len = fields.length,
  388. i = 0;
  389. if (prototypeFields) {
  390. prototypeFields.clear();
  391. }
  392. else {
  393. prototypeFields = me.prototype.fields = new Ext.util.MixedCollection(false, function(field) {
  394. return field.name;
  395. });
  396. }
  397. for (; i < len; i++) {
  398. prototypeFields.add(new Ext.data.Field(fields[i]));
  399. }
  400. me.fields = prototypeFields;
  401. return prototypeFields;
  402. },
  403. getFields: function() {
  404. return this.fields;
  405. },
  406. /**
  407. * Asynchronously loads a model instance by id. Sample usage:
  408. *
  409. * Ext.define('MyApp.User', {
  410. * extend: 'Ext.data.Model',
  411. * fields: [
  412. * {name: 'id', type: 'int'},
  413. * {name: 'name', type: 'string'}
  414. * ]
  415. * });
  416. *
  417. * MyApp.User.load(10, {
  418. * scope: this,
  419. * failure: function(record, operation) {
  420. * //do something if the load failed
  421. * },
  422. * success: function(record, operation) {
  423. * //do something if the load succeeded
  424. * },
  425. * callback: function(record, operation) {
  426. * //do something whether the load succeeded or failed
  427. * }
  428. * });
  429. *
  430. * @param {Number/String} id The id of the model to load
  431. * @param {Object} config (optional) config object containing success, failure and callback functions, plus
  432. * optional scope
  433. * @static
  434. * @inheritable
  435. */
  436. load: function(id, config) {
  437. config = Ext.apply({}, config);
  438. config = Ext.applyIf(config, {
  439. action: 'read',
  440. id : id
  441. });
  442. var operation = new Ext.data.Operation(config),
  443. scope = config.scope || this,
  444. record = null,
  445. callback;
  446. callback = function(operation) {
  447. if (operation.wasSuccessful()) {
  448. record = operation.getRecords()[0];
  449. Ext.callback(config.success, scope, [record, operation]);
  450. } else {
  451. Ext.callback(config.failure, scope, [record, operation]);
  452. }
  453. Ext.callback(config.callback, scope, [record, operation]);
  454. };
  455. this.proxy.read(operation, callback, this);
  456. }
  457. },
  458. statics: {
  459. PREFIX : 'ext-record',
  460. AUTO_ID: 1,
  461. EDIT : 'edit',
  462. REJECT : 'reject',
  463. COMMIT : 'commit',
  464. /**
  465. * Generates a sequential id. This method is typically called when a record is {@link Ext#create
  466. * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
  467. * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
  468. *
  469. * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
  470. * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
  471. *
  472. * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
  473. * @return {String} auto-generated string id, `"ext-record-i++"`;
  474. * @static
  475. */
  476. id: function(rec) {
  477. var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
  478. rec.phantom = true;
  479. rec.internalId = id;
  480. return id;
  481. }
  482. },
  483. /**
  484. * @cfg {String/Object} idgen
  485. * The id generator to use for this model. The default id generator does not generate
  486. * values for the {@link #idProperty}.
  487. *
  488. * This can be overridden at the model level to provide a custom generator for a model.
  489. * The simplest form of this would be:
  490. *
  491. * Ext.define('MyApp.data.MyModel', {
  492. * extend: 'Ext.data.Model',
  493. * requires: ['Ext.data.SequentialIdGenerator'],
  494. * idgen: 'sequential',
  495. * ...
  496. * });
  497. *
  498. * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
  499. * as 1, 2, 3 etc..
  500. *
  501. * Another useful id generator is {@link Ext.data.UuidGenerator}:
  502. *
  503. * Ext.define('MyApp.data.MyModel', {
  504. * extend: 'Ext.data.Model',
  505. * requires: ['Ext.data.UuidGenerator'],
  506. * idgen: 'uuid',
  507. * ...
  508. * });
  509. *
  510. * An id generation can also be further configured:
  511. *
  512. * Ext.define('MyApp.data.MyModel', {
  513. * extend: 'Ext.data.Model',
  514. * idgen: {
  515. * type: 'sequential',
  516. * seed: 1000,
  517. * prefix: 'ID_'
  518. * }
  519. * });
  520. *
  521. * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
  522. *
  523. * If multiple models share an id space, a single generator can be shared:
  524. *
  525. * Ext.define('MyApp.data.MyModelX', {
  526. * extend: 'Ext.data.Model',
  527. * idgen: {
  528. * type: 'sequential',
  529. * id: 'xy'
  530. * }
  531. * });
  532. *
  533. * Ext.define('MyApp.data.MyModelY', {
  534. * extend: 'Ext.data.Model',
  535. * idgen: {
  536. * type: 'sequential',
  537. * id: 'xy'
  538. * }
  539. * });
  540. *
  541. * For more complex, shared id generators, a custom generator is the best approach.
  542. * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
  543. *
  544. * @markdown
  545. */
  546. idgen: {
  547. isGenerator: true,
  548. type: 'default',
  549. generate: function () {
  550. return null;
  551. },
  552. getRecId: function (rec) {
  553. return rec.modelName + '-' + rec.internalId;
  554. }
  555. },
  556. /**
  557. * @property {Boolean} editing
  558. * Internal flag used to track whether or not the model instance is currently being edited.
  559. * @readonly
  560. */
  561. editing : false,
  562. /**
  563. * @property {Boolean} dirty
  564. * True if this Record has been modified.
  565. * @readonly
  566. */
  567. dirty : false,
  568. /**
  569. * @cfg {String} persistenceProperty
  570. * The name of the property on this Persistable object that its data is saved to. Defaults to 'data'
  571. * (i.e: all persistable data resides in `this.data`.)
  572. */
  573. persistenceProperty: 'data',
  574. evented: false,
  575. /**
  576. * @property {Boolean} isModel
  577. * `true` in this class to identify an objact as an instantiated Model, or subclass thereof.
  578. */
  579. isModel: true,
  580. /**
  581. * @property {Boolean} phantom
  582. * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
  583. * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
  584. */
  585. phantom : false,
  586. /**
  587. * @cfg {String} idProperty
  588. * The name of the field treated as this Model's unique id. Defaults to 'id'.
  589. */
  590. idProperty: 'id',
  591. /**
  592. * @cfg {String} [clientIdProperty='clientId']
  593. * The name of a property that is used for submitting this Model's unique client-side identifier
  594. * to the server when multiple phantom records are saved as part of the same {@link Ext.data.Operation Operation}.
  595. * In such a case, the server response should include the client id for each record
  596. * so that the server response data can be used to update the client-side records if necessary.
  597. * This property cannot have the same name as any of this Model's fields.
  598. */
  599. clientIdProperty: 'clientId',
  600. /**
  601. * @cfg {String} defaultProxyType
  602. * The string type of the default Model Proxy. Defaults to 'ajax'.
  603. */
  604. defaultProxyType: 'ajax',
  605. // Fields config and property
  606. /**
  607. * @cfg {Object[]/String[]} fields
  608. * The fields for this model. This is an Array of **{@link Ext.data.Field Field}** definition objects. A Field
  609. * definition may simply be the *name* of the Field, but a Field encapsulates {@link Ext.data.Field#type data type},
  610. * {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
  611. * property to specify by name of index, how to extract a field's value from a raw data object, so it is best practice
  612. * to specify a full set of {@link Ext.data.Field Field} config objects.
  613. */
  614. /**
  615. * @property {Ext.util.MixedCollection} fields
  616. * A {@link Ext.util.MixedCollection Collection} of the fields defined for this Model (including fields defined in superclasses)
  617. *
  618. * This is a collection of {@link Ext.data.Field} instances, each of which encapsulates information that the field was configured with.
  619. * By default, you can specify a field as simply a String, representing the *name* of the field, but a Field encapsulates
  620. * {@link Ext.data.Field#type data type}, {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
  621. * property to specify by name of index, how to extract a field's value from a raw data object.
  622. */
  623. /**
  624. * @cfg {Object[]} validations
  625. * An array of {@link Ext.data.validations validations} for this model.
  626. */
  627. // Associations configs and properties
  628. /**
  629. * @cfg {Object[]} associations
  630. * An array of {@link Ext.data.Association associations} for this model.
  631. */
  632. /**
  633. * @cfg {String/Object/String[]/Object[]} hasMany
  634. * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
  635. */
  636. /**
  637. * @cfg {String/Object/String[]/Object[]} belongsTo
  638. * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
  639. */
  640. /**
  641. * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
  642. * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
  643. */
  644. /**
  645. * @event idchanged
  646. * Fired when this model's id changes
  647. * @param {Ext.data.Model} this
  648. * @param {Number/String} oldId The old id
  649. * @param {Number/String} newId The new id
  650. */
  651. // id, raw and convertedData not documented intentionally, meant to be used internally.
  652. constructor: function(data, id, raw, convertedData) {
  653. data = data || {};
  654. var me = this,
  655. fields,
  656. length,
  657. field,
  658. name,
  659. value,
  660. newId,
  661. persistenceProperty,
  662. i;
  663. /**
  664. * @property {Number/String} internalId
  665. * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
  666. * @private
  667. */
  668. me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
  669. /**
  670. * @property {Object} raw The raw data used to create this model if created via a reader.
  671. */
  672. me.raw = raw;
  673. if (!me.data) {
  674. me.data = {};
  675. }
  676. /**
  677. * @property {Object} modified Key: value pairs of all fields whose values have changed
  678. */
  679. me.modified = {};
  680. // Deal with spelling error in previous releases
  681. if (me.persistanceProperty) {
  682. //<debug>
  683. if (Ext.isDefined(Ext.global.console)) {
  684. Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
  685. }
  686. //</debug>
  687. me.persistenceProperty = me.persistanceProperty;
  688. }
  689. me[me.persistenceProperty] = convertedData || {};
  690. me.mixins.observable.constructor.call(me);
  691. if (!convertedData) {
  692. //add default field values if present
  693. fields = me.fields.items;
  694. length = fields.length;
  695. i = 0;
  696. persistenceProperty = me[me.persistenceProperty];
  697. if (Ext.isArray(data)) {
  698. for (; i < length; i++) {
  699. field = fields[i];
  700. name = field.name;
  701. value = data[i];
  702. if (value === undefined) {
  703. value = field.defaultValue;
  704. }
  705. // Have to map array data so the values get assigned to the named fields
  706. // rather than getting set as the field names with undefined values.
  707. if (field.convert) {
  708. value = field.convert(value, me);
  709. }
  710. persistenceProperty[name] = value ;
  711. }
  712. } else {
  713. for (; i < length; i++) {
  714. field = fields[i];
  715. name = field.name;
  716. value = data[name];
  717. if (value === undefined) {
  718. value = field.defaultValue;
  719. }
  720. if (field.convert) {
  721. value = field.convert(value, me);
  722. }
  723. persistenceProperty[name] = value ;
  724. }
  725. }
  726. }
  727. /**
  728. * @property {Array} stores
  729. * An array of {@link Ext.data.AbstractStore} objects that this record is bound to.
  730. */
  731. me.stores = [];
  732. if (me.getId()) {
  733. me.phantom = false;
  734. } else if (me.phantom) {
  735. newId = me.idgen.generate();
  736. if (newId !== null) {
  737. me.setId(newId);
  738. }
  739. }
  740. // clear any dirty/modified since we're initializing
  741. me.dirty = false;
  742. me.modified = {};
  743. if (typeof me.init == 'function') {
  744. me.init();
  745. }
  746. me.id = me.idgen.getRecId(me);
  747. },
  748. /**
  749. * Returns the value of the given field
  750. * @param {String} fieldName The field to fetch the value for
  751. * @return {Object} The value
  752. */
  753. get: function(field) {
  754. return this[this.persistenceProperty][field];
  755. },
  756. /**
  757. * Sets the given field to the given value, marks the instance as dirty
  758. * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
  759. * @param {Object} value The value to set
  760. */
  761. set: function(fieldName, value) {
  762. var me = this,
  763. fields = me.fields,
  764. modified = me.modified,
  765. modifiedFieldNames = [],
  766. field, key, i, currentValue, notEditing, count, length;
  767. /*
  768. * If we're passed an object, iterate over that object.
  769. */
  770. if (arguments.length == 1 && Ext.isObject(fieldName)) {
  771. notEditing = !me.editing;
  772. count = 0;
  773. fields = me.fields.items;
  774. length = fields.length;
  775. for (i = 0; i < length; i++) {
  776. field = fields[i].name;
  777. if (fieldName.hasOwnProperty(field)) {
  778. if (!count && notEditing) {
  779. me.beginEdit();
  780. }
  781. ++count;
  782. me.set(field, fieldName[field]);
  783. }
  784. }
  785. if (notEditing && count) {
  786. me.endEdit(false, modifiedFieldNames);
  787. }
  788. } else {
  789. fields = me.fields;
  790. if (fields) {
  791. field = fields.get(fieldName);
  792. if (field && field.convert) {
  793. value = field.convert(value, me);
  794. }
  795. }
  796. currentValue = me.get(fieldName);
  797. me[me.persistenceProperty][fieldName] = value;
  798. if (field && field.persist && !me.isEqual(currentValue, value)) {
  799. if (me.isModified(fieldName)) {
  800. if (me.isEqual(modified[fieldName], value)) {
  801. // the original value in me.modified equals the new value, so the
  802. // field is no longer modified
  803. delete modified[fieldName];
  804. // we might have removed the last modified field, so check to see if
  805. // there are any modified fields remaining and correct me.dirty:
  806. me.dirty = false;
  807. for (key in modified) {
  808. if (modified.hasOwnProperty(key)){
  809. me.dirty = true;
  810. break;
  811. }
  812. }
  813. }
  814. } else {
  815. me.dirty = true;
  816. modified[fieldName] = currentValue;
  817. }
  818. }
  819. if(fieldName === me.idProperty && currentValue !== value) {
  820. me.fireEvent('idchanged', me, currentValue, value);
  821. }
  822. if (!me.editing) {
  823. me.afterEdit([fieldName]);
  824. }
  825. }
  826. },
  827. /**
  828. * Checks if two values are equal, taking into account certain
  829. * special factors, for example dates.
  830. * @private
  831. * @param {Object} a The first value
  832. * @param {Object} b The second value
  833. * @return {Boolean} True if the values are equal
  834. */
  835. isEqual: function(a, b){
  836. if (Ext.isDate(a) && Ext.isDate(b)) {
  837. return Ext.Date.isEqual(a, b);
  838. }
  839. return a === b;
  840. },
  841. /**
  842. * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
  843. * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
  844. */
  845. beginEdit : function(){
  846. var me = this;
  847. if (!me.editing) {
  848. me.editing = true;
  849. me.dirtySave = me.dirty;
  850. me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
  851. me.modifiedSave = Ext.apply({}, me.modified);
  852. }
  853. },
  854. /**
  855. * Cancels all changes made in the current edit operation.
  856. */
  857. cancelEdit : function(){
  858. var me = this;
  859. if (me.editing) {
  860. me.editing = false;
  861. // reset the modified state, nothing changed since the edit began
  862. me.modified = me.modifiedSave;
  863. me[me.persistenceProperty] = me.dataSave;
  864. me.dirty = me.dirtySave;
  865. delete me.modifiedSave;
  866. delete me.dataSave;
  867. delete me.dirtySave;
  868. }
  869. },
  870. /**
  871. * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
  872. * fire).
  873. * @param {Boolean} silent True to not notify the store of the change
  874. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  875. */
  876. endEdit : function(silent, modifiedFieldNames){
  877. var me = this,
  878. changed;
  879. if (me.editing) {
  880. me.editing = false;
  881. if(!modifiedFieldNames) {
  882. modifiedFieldNames = me.getModifiedFieldNames();
  883. }
  884. changed = me.dirty || modifiedFieldNames.length > 0;
  885. delete me.modifiedSave;
  886. delete me.dataSave;
  887. delete me.dirtySave;
  888. if (changed && silent !== true) {
  889. me.afterEdit(modifiedFieldNames);
  890. }
  891. }
  892. },
  893. /**
  894. * Gets the names of all the fields that were modified during an edit
  895. * @private
  896. * @return {String[]} An array of modified field names
  897. */
  898. getModifiedFieldNames: function(){
  899. var me = this,
  900. saved = me.dataSave,
  901. data = me[me.persistenceProperty],
  902. modified = [],
  903. key;
  904. for (key in data) {
  905. if (data.hasOwnProperty(key)) {
  906. if (!me.isEqual(data[key], saved[key])) {
  907. modified.push(key);
  908. }
  909. }
  910. }
  911. return modified;
  912. },
  913. /**
  914. * Gets a hash of only the fields that have been modified since this Model was created or commited.
  915. * @return {Object}
  916. */
  917. getChanges : function(){
  918. var modified = this.modified,
  919. changes = {},
  920. field;
  921. for (field in modified) {
  922. if (modified.hasOwnProperty(field)){
  923. changes[field] = this.get(field);
  924. }
  925. }
  926. return changes;
  927. },
  928. /**
  929. * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
  930. * @param {String} fieldName {@link Ext.data.Field#name}
  931. * @return {Boolean}
  932. */
  933. isModified : function(fieldName) {
  934. return this.modified.hasOwnProperty(fieldName);
  935. },
  936. /**
  937. * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
  938. * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
  939. *
  940. * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
  941. * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
  942. */
  943. setDirty : function() {
  944. var me = this,
  945. fields = me.fields.items,
  946. fLen = fields.length,
  947. field, name, f;
  948. me.dirty = true;
  949. for (f = 0; f < fLen; f++) {
  950. field = fields[f];
  951. if (field.persist) {
  952. name = field.name;
  953. me.modified[name] = me.get(name);
  954. }
  955. }
  956. },
  957. //<debug>
  958. markDirty : function() {
  959. if (Ext.isDefined(Ext.global.console)) {
  960. Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
  961. }
  962. return this.setDirty.apply(this, arguments);
  963. },
  964. //</debug>
  965. /**
  966. * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
  967. * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
  968. * reverted to their original values.
  969. *
  970. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
  971. * operations.
  972. *
  973. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  974. * Defaults to false.
  975. */
  976. reject : function(silent) {
  977. var me = this,
  978. modified = me.modified,
  979. field;
  980. for (field in modified) {
  981. if (modified.hasOwnProperty(field)) {
  982. if (typeof modified[field] != "function") {
  983. me[me.persistenceProperty][field] = modified[field];
  984. }
  985. }
  986. }
  987. me.dirty = false;
  988. me.editing = false;
  989. me.modified = {};
  990. if (silent !== true) {
  991. me.afterReject();
  992. }
  993. },
  994. /**
  995. * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
  996. * instance since either creation or the last commit operation.
  997. *
  998. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
  999. * operations.
  1000. *
  1001. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  1002. * Defaults to false.
  1003. */
  1004. commit : function(silent) {
  1005. var me = this;
  1006. me.phantom = me.dirty = me.editing = false;
  1007. me.modified = {};
  1008. if (silent !== true) {
  1009. me.afterCommit();
  1010. }
  1011. },
  1012. /**
  1013. * Creates a copy (clone) of this Model instance.
  1014. *
  1015. * @param {String} [id] A new id, defaults to the id of the instance being copied.
  1016. * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
  1017. *
  1018. * var rec = record.copy(); // clone the record
  1019. * Ext.data.Model.id(rec); // automatically generate a unique sequential id
  1020. *
  1021. * @return {Ext.data.Model}
  1022. */
  1023. copy : function(newId) {
  1024. var me = this;
  1025. return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId);
  1026. },
  1027. /**
  1028. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  1029. * {@link Ext#createByAlias Ext.createByAlias}.
  1030. *
  1031. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  1032. * @return {Ext.data.proxy.Proxy}
  1033. */
  1034. setProxy: function(proxy) {
  1035. //make sure we have an Ext.data.proxy.Proxy object
  1036. if (!proxy.isProxy) {
  1037. if (typeof proxy === "string") {
  1038. proxy = {
  1039. type: proxy
  1040. };
  1041. }
  1042. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  1043. }
  1044. proxy.setModel(this.self);
  1045. this.proxy = proxy;
  1046. return proxy;
  1047. },
  1048. /**
  1049. * Returns the configured Proxy for this Model.
  1050. * @return {Ext.data.proxy.Proxy} The proxy
  1051. */
  1052. getProxy: function() {
  1053. return this.proxy;
  1054. },
  1055. /**
  1056. * Validates the current data against all of its configured {@link #validations}.
  1057. * @return {Ext.data.Errors} The errors object
  1058. */
  1059. validate: function() {
  1060. var errors = new Ext.data.Errors(),
  1061. validations = this.validations,
  1062. validators = Ext.data.validations,
  1063. length, validation, field, valid, type, i;
  1064. if (validations) {
  1065. length = validations.length;
  1066. for (i = 0; i < length; i++) {
  1067. validation = validations[i];
  1068. field = validation.field || validation.name;
  1069. type = validation.type;
  1070. valid = validators[type](validation, this.get(field));
  1071. if (!valid) {
  1072. errors.add({
  1073. field : field,
  1074. message: validation.message || validators[type + 'Message']
  1075. });
  1076. }
  1077. }
  1078. }
  1079. return errors;
  1080. },
  1081. /**
  1082. * Checks if the model is valid. See {@link #validate}.
  1083. * @return {Boolean} True if the model is valid.
  1084. */
  1085. isValid: function(){
  1086. return this.validate().isValid();
  1087. },
  1088. /**
  1089. * Saves the model instance using the configured proxy.
  1090. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  1091. * @return {Ext.data.Model} The Model instance
  1092. */
  1093. save: function(options) {
  1094. options = Ext.apply({}, options);
  1095. var me = this,
  1096. action = me.phantom ? 'create' : 'update',
  1097. scope = options.scope || me,
  1098. stores = me.stores,
  1099. i = 0,
  1100. storeCount,
  1101. store,
  1102. args,
  1103. operation,
  1104. callback;
  1105. Ext.apply(options, {
  1106. records: [me],
  1107. action : action
  1108. });
  1109. operation = new Ext.data.Operation(options);
  1110. callback = function(operation) {
  1111. args = [me, operation];
  1112. if (operation.wasSuccessful()) {
  1113. for(storeCount = stores.length; i < storeCount; i++) {
  1114. store = stores[i];
  1115. store.fireEvent('write', store, operation);
  1116. store.fireEvent('datachanged', store);
  1117. // Not firing refresh here, since it's a single record
  1118. }
  1119. Ext.callback(options.success, scope, args);
  1120. } else {
  1121. Ext.callback(options.failure, scope, args);
  1122. }
  1123. Ext.callback(options.callback, scope, args);
  1124. };
  1125. me.getProxy()[action](operation, callback, me);
  1126. return me;
  1127. },
  1128. /**
  1129. * Destroys the model using the configured proxy.
  1130. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  1131. * @return {Ext.data.Model} The Model instance
  1132. */
  1133. destroy: function(options){
  1134. options = Ext.apply({}, options);
  1135. var me = this,
  1136. scope = options.scope || me,
  1137. stores = me.stores,
  1138. i = 0,
  1139. storeCount,
  1140. store,
  1141. args,
  1142. operation,
  1143. callback;
  1144. Ext.apply(options, {
  1145. records: [me],
  1146. action : 'destroy'
  1147. });
  1148. operation = new Ext.data.Operation(options);
  1149. callback = function(operation) {
  1150. args = [me, operation];
  1151. if (operation.wasSuccessful()) {
  1152. for(storeCount = stores.length; i < storeCount; i++) {
  1153. store = stores[i];
  1154. store.fireEvent('write', store, operation);
  1155. store.fireEvent('datachanged', store);
  1156. // Not firing refresh here, since it's a single record
  1157. }
  1158. me.clearListeners();
  1159. Ext.callback(options.success, scope, args);
  1160. } else {
  1161. Ext.callback(options.failure, scope, args);
  1162. }
  1163. Ext.callback(options.callback, scope, args);
  1164. };
  1165. me.getProxy().destroy(operation, callback, me);
  1166. return me;
  1167. },
  1168. /**
  1169. * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
  1170. * @return {Number/String} The id
  1171. */
  1172. getId: function() {
  1173. return this.get(this.idProperty);
  1174. },
  1175. /**
  1176. * @private
  1177. */
  1178. getObservableId: function() {
  1179. return this.id;
  1180. },
  1181. /**
  1182. * Sets the model instance's id field to the given id.
  1183. * @param {Number/String} id The new id
  1184. */
  1185. setId: function(id) {
  1186. this.set(this.idProperty, id);
  1187. this.phantom = !(id || id === 0);
  1188. },
  1189. /**
  1190. * Tells this model instance that it has been added to a store.
  1191. * @param {Ext.data.Store} store The store to which this model has been added.
  1192. */
  1193. join : function(store) {
  1194. Ext.Array.include(this.stores, store);
  1195. },
  1196. /**
  1197. * Tells this model instance that it has been removed from the store.
  1198. * @param {Ext.data.Store} store The store from which this model has been removed.
  1199. */
  1200. unjoin: function(store) {
  1201. Ext.Array.remove(this.stores, store);
  1202. },
  1203. /**
  1204. * @private
  1205. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  1206. * afterEdit method is called
  1207. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  1208. */
  1209. afterEdit : function(modifiedFieldNames) {
  1210. this.callStore('afterEdit', modifiedFieldNames);
  1211. },
  1212. /**
  1213. * @private
  1214. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  1215. * afterReject method is called
  1216. */
  1217. afterReject : function() {
  1218. this.callStore("afterReject");
  1219. },
  1220. /**
  1221. * @private
  1222. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  1223. * afterCommit method is called
  1224. */
  1225. afterCommit: function() {
  1226. this.callStore('afterCommit');
  1227. },
  1228. /**
  1229. * @private
  1230. * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
  1231. * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
  1232. * will always be called with the model instance as its single argument. If this model is joined to
  1233. * a Ext.data.NodeStore, then this method calls the given method on the NodeStore and the associated Ext.data.TreeStore
  1234. * @param {String} fn The function to call on the store
  1235. */
  1236. callStore: function(fn) {
  1237. var args = Ext.Array.clone(arguments),
  1238. stores = this.stores,
  1239. i = 0,
  1240. len = stores.length,
  1241. store, treeStore;
  1242. args[0] = this;
  1243. for (; i < len; ++i) {
  1244. store = stores[i];
  1245. if (store && typeof store[fn] == "function") {
  1246. store[fn].apply(store, args);
  1247. }
  1248. // if the record is bound to a NodeStore call the TreeStore's method as well
  1249. treeStore = store.treeStore;
  1250. if (treeStore && typeof treeStore[fn] == "function") {
  1251. treeStore[fn].apply(treeStore, args);
  1252. }
  1253. }
  1254. },
  1255. /**
  1256. * Gets all values for each field in this model and returns an object
  1257. * containing the current data.
  1258. * @param {Boolean} includeAssociated True to also include associated data. Defaults to false.
  1259. * @return {Object} An object hash containing all the values in this model
  1260. */
  1261. getData: function(includeAssociated){
  1262. var me = this,
  1263. fields = me.fields.items,
  1264. fLen = fields.length,
  1265. data = {},
  1266. name, f;
  1267. for (f = 0; f < fLen; f++) {
  1268. name = fields[f].name;
  1269. data[name] = me.get(name);
  1270. }
  1271. if (includeAssociated === true) {
  1272. Ext.apply(data, me.getAssociatedData());
  1273. }
  1274. return data;
  1275. },
  1276. /**
  1277. * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
  1278. * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
  1279. *
  1280. * {
  1281. * orders: [
  1282. * {
  1283. * id: 123,
  1284. * status: 'shipped',
  1285. * orderItems: [
  1286. * ...
  1287. * ]
  1288. * }
  1289. * ]
  1290. * }
  1291. *
  1292. * @return {Object} The nested data set for the Model's loaded associations
  1293. */
  1294. getAssociatedData: function(){
  1295. return this.prepareAssociatedData(this, [], null);
  1296. },
  1297. /**
  1298. * @private
  1299. * This complex-looking method takes a given Model instance and returns an object containing all data from
  1300. * all of that Model's *loaded* associations. See {@link #getAssociatedData}
  1301. * @param {Ext.data.Model} record The Model instance
  1302. * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
  1303. * @param {String} associationType (optional) The name of the type of association to limit to.
  1304. * @return {Object} The nested data set for the Model's loaded associations
  1305. */
  1306. prepareAssociatedData: function(record, ids, associationType) {
  1307. //we keep track of all of the internalIds of the models that we have loaded so far in here
  1308. var associations = record.associations.items,
  1309. associationCount = associations.length,
  1310. associationData = {},
  1311. associatedStore, associatedRecords, associatedRecord,
  1312. associatedRecordCount, association, id, i, j, type, allow;
  1313. for (i = 0; i < associationCount; i++) {
  1314. association = associations[i];
  1315. type = association.type;
  1316. allow = true;
  1317. if (associationType) {
  1318. allow = type == associationType;
  1319. }
  1320. if (allow && type == 'hasMany') {
  1321. //this is the hasMany store filled with the associated data
  1322. associatedStore = record[association.storeName];
  1323. //we will use this to contain each associated record's data
  1324. associationData[association.name] = [];
  1325. //if it's loaded, put it into the association data
  1326. if (associatedStore && associatedStore.getCount() > 0) {
  1327. associatedRecords = associatedStore.data.items;
  1328. associatedRecordCount = associatedRecords.length;
  1329. //now we're finally iterating over the records in the association. We do this recursively
  1330. for (j = 0; j < associatedRecordCount; j++) {
  1331. associatedRecord = associatedRecords[j];
  1332. // Use the id, since it is prefixed with the model name, guaranteed to be unique
  1333. id = associatedRecord.id;
  1334. //when we load the associations for a specific model instance we add it to the set of loaded ids so that
  1335. //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
  1336. if (Ext.Array.indexOf(ids, id) == -1) {
  1337. ids.push(id);
  1338. associationData[association.name][j] = associatedRecord.getData();
  1339. Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
  1340. }
  1341. }
  1342. }
  1343. } else if (allow && (type == 'belongsTo' || type == 'hasOne')) {
  1344. associatedRecord = record[association.instanceName];
  1345. if (associatedRecord !== undefined) {
  1346. id = associatedRecord.id;
  1347. if (Ext.Array.indexOf(ids, id) === -1) {
  1348. ids.push(id);
  1349. associationData[association.name] = associatedRecord.getData();
  1350. Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
  1351. }
  1352. }
  1353. }
  1354. }
  1355. return associationData;
  1356. }
  1357. });