Model.html 65 KB

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