Reader.html 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  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-reader-Reader'>/**
  19. </span> * @author Ed Spencer
  20. *
  21. * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
  22. * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
  23. * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
  24. * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
  25. *
  26. * Ext.create('Ext.data.Store', {
  27. * model: 'User',
  28. * proxy: {
  29. * type: 'ajax',
  30. * url : 'users.json',
  31. * reader: {
  32. * type: 'json',
  33. * root: 'users'
  34. * }
  35. * },
  36. * });
  37. *
  38. * The above reader is configured to consume a JSON string that looks something like this:
  39. *
  40. * {
  41. * &quot;success&quot;: true,
  42. * &quot;users&quot;: [
  43. * { &quot;name&quot;: &quot;User 1&quot; },
  44. * { &quot;name&quot;: &quot;User 2&quot; }
  45. * ]
  46. * }
  47. *
  48. *
  49. * # Loading Nested Data
  50. *
  51. * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.association.Association
  52. * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
  53. * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
  54. *
  55. * Ext.define(&quot;User&quot;, {
  56. * extend: 'Ext.data.Model',
  57. * fields: [
  58. * 'id', 'name'
  59. * ],
  60. *
  61. * hasMany: {model: 'Order', name: 'orders'},
  62. *
  63. * proxy: {
  64. * type: 'rest',
  65. * url : 'users.json',
  66. * reader: {
  67. * type: 'json',
  68. * root: 'users'
  69. * }
  70. * }
  71. * });
  72. *
  73. * Ext.define(&quot;Order&quot;, {
  74. * extend: 'Ext.data.Model',
  75. * fields: [
  76. * 'id', 'total'
  77. * ],
  78. *
  79. * hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
  80. * belongsTo: 'User'
  81. * });
  82. *
  83. * Ext.define(&quot;OrderItem&quot;, {
  84. * extend: 'Ext.data.Model',
  85. * fields: [
  86. * 'id', 'price', 'quantity', 'order_id', 'product_id'
  87. * ],
  88. *
  89. * belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
  90. * });
  91. *
  92. * Ext.define(&quot;Product&quot;, {
  93. * extend: 'Ext.data.Model',
  94. * fields: [
  95. * 'id', 'name'
  96. * ],
  97. *
  98. * hasMany: 'OrderItem'
  99. * });
  100. *
  101. * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
  102. * Finally, each OrderItem has a single Product. This allows us to consume data like this:
  103. *
  104. * {
  105. * &quot;users&quot;: [
  106. * {
  107. * &quot;id&quot;: 123,
  108. * &quot;name&quot;: &quot;Ed&quot;,
  109. * &quot;orders&quot;: [
  110. * {
  111. * &quot;id&quot;: 50,
  112. * &quot;total&quot;: 100,
  113. * &quot;order_items&quot;: [
  114. * {
  115. * &quot;id&quot; : 20,
  116. * &quot;price&quot; : 40,
  117. * &quot;quantity&quot;: 2,
  118. * &quot;product&quot; : {
  119. * &quot;id&quot;: 1000,
  120. * &quot;name&quot;: &quot;MacBook Pro&quot;
  121. * }
  122. * },
  123. * {
  124. * &quot;id&quot; : 21,
  125. * &quot;price&quot; : 20,
  126. * &quot;quantity&quot;: 3,
  127. * &quot;product&quot; : {
  128. * &quot;id&quot;: 1001,
  129. * &quot;name&quot;: &quot;iPhone&quot;
  130. * }
  131. * }
  132. * ]
  133. * }
  134. * ]
  135. * }
  136. * ]
  137. * }
  138. *
  139. * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
  140. * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
  141. * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
  142. *
  143. * var store = Ext.create('Ext.data.Store', {
  144. * model: &quot;User&quot;
  145. * });
  146. *
  147. * store.load({
  148. * callback: function() {
  149. * //the user that was loaded
  150. * var user = store.first();
  151. *
  152. * console.log(&quot;Orders for &quot; + user.get('name') + &quot;:&quot;)
  153. *
  154. * //iterate over the Orders for each User
  155. * user.orders().each(function(order) {
  156. * console.log(&quot;Order ID: &quot; + order.getId() + &quot;, which contains items:&quot;);
  157. *
  158. * //iterate over the OrderItems for each Order
  159. * order.orderItems().each(function(orderItem) {
  160. * //we know that the Product data is already loaded, so we can use the synchronous getProduct
  161. * //usually, we would use the asynchronous version (see {@link Ext.data.association.BelongsTo})
  162. * var product = orderItem.getProduct();
  163. *
  164. * console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
  165. * });
  166. * });
  167. * }
  168. * });
  169. *
  170. * Running the code above results in the following:
  171. *
  172. * Orders for Ed:
  173. * Order ID: 50, which contains items:
  174. * 2 orders of MacBook Pro
  175. * 3 orders of iPhone
  176. */
  177. Ext.define('Ext.data.reader.Reader', {
  178. requires: ['Ext.data.ResultSet', 'Ext.XTemplate'],
  179. alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
  180. mixins: {
  181. observable: 'Ext.util.Observable'
  182. },
  183. <span id='Ext-data-reader-Reader-cfg-idProperty'> /**
  184. </span> * @cfg {String} idProperty
  185. * Name of the property within a row object that contains a record identifier value. Defaults to the id of the
  186. * model. If an idProperty is explicitly specified it will override the idProperty defined on the model.
  187. */
  188. <span id='Ext-data-reader-Reader-cfg-totalProperty'> /**
  189. </span> * @cfg {String} [totalProperty=&quot;total&quot;]
  190. * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
  191. * the whole dataset is not passed in one go, but is being paged from the remote server.
  192. */
  193. totalProperty: 'total',
  194. <span id='Ext-data-reader-Reader-cfg-successProperty'> /**
  195. </span> * @cfg {String} [successProperty=&quot;success&quot;]
  196. * Name of the property from which to retrieve the `success` attribute, the value of which indicates
  197. * whether a given request succeeded or failed (typically a boolean or 'true'|'false'). See
  198. * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
  199. */
  200. successProperty: 'success',
  201. <span id='Ext-data-reader-Reader-cfg-root'> /**
  202. </span> * @cfg {String} root
  203. * The name of the property which contains the data items corresponding to the Model(s) for which this
  204. * Reader is configured. For JSON reader it's a property name (or a dot-separated list of property names
  205. * if the root is nested). For XML reader it's a CSS selector. For Array reader the root is not applicable
  206. * since the data is assumed to be a single-level array of arrays.
  207. *
  208. * By default the natural root of the data will be used: the root JSON array, the root XML element, or the array.
  209. *
  210. * The data packet value for this property should be an empty array to clear the data or show no data.
  211. */
  212. root: '',
  213. <span id='Ext-data-reader-Reader-cfg-messageProperty'> /**
  214. </span> * @cfg {String} messageProperty
  215. * The name of the property which contains a response message. This property is optional.
  216. */
  217. <span id='Ext-data-reader-Reader-cfg-implicitIncludes'> /**
  218. </span> * @cfg {Boolean} [implicitIncludes=true]
  219. * True to automatically parse models nested within other models in a response object. See the
  220. * Ext.data.reader.Reader intro docs for full explanation.
  221. */
  222. implicitIncludes: true,
  223. <span id='Ext-data-reader-Reader-cfg-readRecordsOnFailure'> /**
  224. </span> * @cfg {Boolean} [readRecordsOnFailure=true]
  225. * True to extract the records from a data packet even if the {@link #successProperty} returns false.
  226. */
  227. readRecordsOnFailure: true,
  228. <span id='Ext-data-reader-Reader-property-metaData'> /**
  229. </span> * @property {Object} metaData
  230. * The raw meta data that was most recently read, if any. Meta data can include existing
  231. * Reader config options like {@link #idProperty}, {@link #totalProperty}, etc. that get
  232. * automatically applied to the Reader, and those can still be accessed directly from the Reader
  233. * if needed. However, meta data is also often used to pass other custom data to be processed
  234. * by application code. For example, it is common when reconfiguring the data model of a grid to
  235. * also pass a corresponding column model config to be applied to the grid. Any such data will
  236. * not get applied to the Reader directly (it just gets passed through and is ignored by Ext).
  237. * This metaData property gives you access to all meta data that was passed, including any such
  238. * custom data ignored by the reader.
  239. *
  240. * This is a read-only property, and it will get replaced each time a new meta data object is
  241. * passed to the reader. Note that typically you would handle proxy's
  242. * {@link Ext.data.proxy.Proxy#metachange metachange} event which passes this exact same meta
  243. * object to listeners. However this property is available if it's more convenient to access it
  244. * via the reader directly in certain cases.
  245. * @readonly
  246. */
  247. /*
  248. * @property {Boolean} isReader
  249. * `true` in this class to identify an object as an instantiated Reader, or subclass thereof.
  250. */
  251. isReader: true,
  252. // Private flag to the generated convertRecordData function to indicate whether to apply Field default
  253. // values to fields for which no value is present in the raw data.
  254. // This is set to false by a Server Proxy which is reading the response from a &quot;create&quot; or &quot;update&quot; operation.
  255. applyDefaults: true,
  256. lastFieldGeneration: null,
  257. <span id='Ext-data-reader-Reader-method-constructor'> /**
  258. </span> * Creates new Reader.
  259. * @param {Object} config (optional) Config object.
  260. */
  261. constructor: function(config) {
  262. var me = this;
  263. me.mixins.observable.constructor.call(me, config);
  264. me.fieldCount = 0;
  265. me.model = Ext.ModelManager.getModel(me.model);
  266. me.accessExpressionFn = Ext.Function.bind(me.createFieldAccessExpression, me);
  267. // Extractors can only be calculated if the fields MixedCollection has been set.
  268. // A Model may only complete its setup (set the prototype properties) after asynchronous loading
  269. // which would mean that there may be no &quot;fields&quot;
  270. // If this happens, the load callback will call proxy.setModel which calls reader.setModel which
  271. // triggers buildExtractors.
  272. if (me.model &amp;&amp; me.model.prototype.fields) {
  273. me.buildExtractors();
  274. }
  275. this.addEvents(
  276. <span id='Ext-data-reader-Reader-event-exception'> /**
  277. </span> * @event
  278. * Fires when the reader receives improperly encoded data from the server
  279. * @param {Ext.data.reader.Reader} reader A reference to this reader
  280. * @param {XMLHttpRequest} response The XMLHttpRequest response object
  281. * @param {Ext.data.ResultSet} error The error object
  282. */
  283. 'exception'
  284. );
  285. },
  286. <span id='Ext-data-reader-Reader-method-setModel'> /**
  287. </span> * Sets a new model for the reader.
  288. * @private
  289. * @param {Object} model The model to set.
  290. * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
  291. */
  292. setModel: function(model, setOnProxy) {
  293. var me = this;
  294. me.model = Ext.ModelManager.getModel(model);
  295. me.buildExtractors(true);
  296. if (setOnProxy &amp;&amp; me.proxy) {
  297. me.proxy.setModel(me.model, true);
  298. }
  299. },
  300. <span id='Ext-data-reader-Reader-method-read'> /**
  301. </span> * Reads the given response object. This method normalizes the different types of response object that may be passed to it.
  302. * If it's an XMLHttpRequest object, hand off to the subclass' {@link #getResponseData} method.
  303. * Else, hand off the reading of records to the {@link #readRecords} method.
  304. * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
  305. * @return {Ext.data.ResultSet} The parsed or default ResultSet object
  306. */
  307. read: function(response) {
  308. var data;
  309. if (response) {
  310. data = response.responseText ? this.getResponseData(response) : this.readRecords(response);
  311. }
  312. return data || this.nullResultSet;
  313. },
  314. <span id='Ext-data-reader-Reader-method-readRecords'> /**
  315. </span> * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
  316. * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
  317. * processing should not be needed.
  318. * @param {Object} data The raw data object
  319. * @return {Ext.data.ResultSet} A ResultSet object
  320. */
  321. readRecords: function(data) {
  322. var me = this,
  323. success,
  324. recordCount,
  325. records,
  326. root,
  327. total,
  328. value,
  329. message;
  330. /*
  331. * We check here whether fields collection has changed since the last read.
  332. * This works around an issue when a Model is used for both a Tree and another
  333. * source, because the tree decorates the model with extra fields and it causes
  334. * issues because the readers aren't notified.
  335. */
  336. if (me.lastFieldGeneration !== me.model.prototype.fields.generation) {
  337. me.buildExtractors(true);
  338. }
  339. <span id='Ext-data-reader-Reader-property-rawData'> /**
  340. </span> * @property {Object} rawData
  341. * The raw data object that was last passed to {@link #readRecords}. Stored for further processing if needed.
  342. */
  343. me.rawData = data;
  344. data = me.getData(data);
  345. success = true;
  346. recordCount = 0;
  347. records = [];
  348. if (me.successProperty) {
  349. value = me.getSuccess(data);
  350. if (value === false || value === 'false') {
  351. success = false;
  352. }
  353. }
  354. if (me.messageProperty) {
  355. message = me.getMessage(data);
  356. }
  357. // Only try and extract other data if call was successful
  358. if (me.readRecordsOnFailure || success) {
  359. // If we pass an array as the data, we dont use getRoot on the data.
  360. // Instead the root equals to the data.
  361. root = Ext.isArray(data) ? data : me.getRoot(data);
  362. if (root) {
  363. total = root.length;
  364. }
  365. if (me.totalProperty) {
  366. value = parseInt(me.getTotal(data), 10);
  367. if (!isNaN(value)) {
  368. total = value;
  369. }
  370. }
  371. if (root) {
  372. records = me.extractData(root);
  373. recordCount = records.length;
  374. }
  375. }
  376. return new Ext.data.ResultSet({
  377. total : total || recordCount,
  378. count : recordCount,
  379. records: records,
  380. success: success,
  381. message: message
  382. });
  383. },
  384. <span id='Ext-data-reader-Reader-method-extractData'> /**
  385. </span> * Returns extracted, type-cast rows of data.
  386. * @param {Object[]/Object} root from server response
  387. * @return {Array} An array of records containing the extracted data
  388. * @private
  389. */
  390. extractData : function(root) {
  391. var me = this,
  392. records = [],
  393. Model = me.model,
  394. length = root.length,
  395. convertedValues, node, record, i;
  396. if (!root.length &amp;&amp; Ext.isObject(root)) {
  397. root = [root];
  398. length = 1;
  399. }
  400. for (i = 0; i &lt; length; i++) {
  401. node = root[i];
  402. if (!node.isModel) {
  403. // Create a record with an empty data object.
  404. // Populate that data object by extracting and converting field values from raw data
  405. record = new Model(undefined, me.getId(node), node, convertedValues = {});
  406. // If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
  407. // We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
  408. record.phantom = false;
  409. // Use generated function to extract all fields at once
  410. me.convertRecordData(convertedValues, node, record);
  411. records.push(record);
  412. if (me.implicitIncludes) {
  413. me.readAssociated(record, node);
  414. }
  415. } else {
  416. // If we're given a model instance in the data, just push it on
  417. // without doing any conversion
  418. records.push(node);
  419. }
  420. }
  421. return records;
  422. },
  423. <span id='Ext-data-reader-Reader-method-readAssociated'> /**
  424. </span> * @private
  425. * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
  426. * on the record provided.
  427. * @param {Ext.data.Model} record The record to load associations for
  428. * @param {Object} data The data object
  429. * @return {String} Return value description
  430. */
  431. readAssociated: function(record, data) {
  432. var associations = record.associations.items,
  433. i = 0,
  434. length = associations.length,
  435. association, associationData, proxy, reader;
  436. for (; i &lt; length; i++) {
  437. association = associations[i];
  438. associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
  439. if (associationData) {
  440. reader = association.getReader();
  441. if (!reader) {
  442. proxy = association.associatedModel.proxy;
  443. // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
  444. if (proxy) {
  445. reader = proxy.getReader();
  446. } else {
  447. reader = new this.constructor({
  448. model: association.associatedName
  449. });
  450. }
  451. }
  452. association.read(record, reader, associationData);
  453. }
  454. }
  455. },
  456. <span id='Ext-data-reader-Reader-method-getAssociatedDataRoot'> /**
  457. </span> * @private
  458. * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
  459. * record, this should return the relevant part of that data for the given association name. This is only really
  460. * needed to support the XML Reader, which has to do a query to get the associated data object
  461. * @param {Object} data The raw data object
  462. * @param {String} associationName The name of the association to get data for (uses associationKey if present)
  463. * @return {Object} The root
  464. */
  465. getAssociatedDataRoot: function(data, associationName) {
  466. return data[associationName];
  467. },
  468. getFields: function() {
  469. return this.model.prototype.fields.items;
  470. },
  471. <span id='Ext-data-reader-Reader-method-getData'> /**
  472. </span> * @private
  473. * By default this function just returns what is passed to it. It can be overridden in a subclass
  474. * to return something else. See XmlReader for an example.
  475. * @param {Object} data The data object
  476. * @return {Object} The normalized data object
  477. */
  478. getData: function(data) {
  479. return data;
  480. },
  481. <span id='Ext-data-reader-Reader-method-getRoot'> /**
  482. </span> * @private
  483. * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
  484. * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
  485. * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
  486. * @param {Object} data The data object
  487. * @return {Object} The same data object
  488. */
  489. getRoot: function(data) {
  490. return data;
  491. },
  492. <span id='Ext-data-reader-Reader-method-getResponseData'> /**
  493. </span> * Takes a raw response object (as passed to the {@link #read} method) and returns the useful data
  494. * segment from it. This must be implemented by each subclass.
  495. * @param {Object} response The response object
  496. * @return {Ext.data.ResultSet} A ResultSet object
  497. */
  498. getResponseData: function(response) {
  499. //&lt;debug&gt;
  500. Ext.Error.raise(&quot;getResponseData must be implemented in the Ext.data.reader.Reader subclass&quot;);
  501. //&lt;/debug&gt;
  502. },
  503. <span id='Ext-data-reader-Reader-method-onMetaChange'> /**
  504. </span> * @private
  505. * Reconfigures the meta data tied to this Reader
  506. */
  507. onMetaChange : function(meta) {
  508. var me = this,
  509. fields = meta.fields || me.getFields(),
  510. newModel,
  511. clientIdProperty;
  512. // save off the raw meta data
  513. me.metaData = meta;
  514. // set any reader-specific configs from meta if available
  515. me.root = meta.root || me.root;
  516. me.idProperty = meta.idProperty || me.idProperty;
  517. me.totalProperty = meta.totalProperty || me.totalProperty;
  518. me.successProperty = meta.successProperty || me.successProperty;
  519. me.messageProperty = meta.messageProperty || me.messageProperty;
  520. clientIdProperty = meta.clientIdProperty;
  521. if (me.model) {
  522. me.model.setFields(fields, me.idProperty, clientIdProperty);
  523. me.setModel(me.model, true);
  524. }
  525. else {
  526. newModel = Ext.define(&quot;Ext.data.reader.Json-Model&quot; + Ext.id(), {
  527. extend: 'Ext.data.Model',
  528. fields: fields,
  529. clientIdProperty: clientIdProperty
  530. });
  531. if (me.idProperty) {
  532. // We only do this if the reader actually has a custom idProperty set,
  533. // otherwise let the model use its own default value. It is valid for
  534. // the reader idProperty to be undefined, in which case it will use the
  535. // model's idProperty (in getIdProperty()).
  536. newModel.idProperty = me.idProperty;
  537. }
  538. me.setModel(newModel, true);
  539. }
  540. },
  541. <span id='Ext-data-reader-Reader-method-getIdProperty'> /**
  542. </span> * Get the idProperty to use for extracting data
  543. * @private
  544. * @return {String} The id property
  545. */
  546. getIdProperty: function(){
  547. return this.idProperty || this.model.prototype.idProperty;
  548. },
  549. <span id='Ext-data-reader-Reader-method-buildExtractors'> /**
  550. </span> * @private
  551. * This builds optimized functions for retrieving record data and meta data from an object.
  552. * Subclasses may need to implement their own getRoot function.
  553. * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
  554. */
  555. buildExtractors: function(force) {
  556. var me = this,
  557. idProp = me.getIdProperty(),
  558. totalProp = me.totalProperty,
  559. successProp = me.successProperty,
  560. messageProp = me.messageProperty,
  561. accessor,
  562. idField,
  563. map;
  564. if (force === true) {
  565. delete me.convertRecordData;
  566. }
  567. if (me.convertRecordData) {
  568. return;
  569. }
  570. //build the extractors for all the meta data
  571. if (totalProp) {
  572. me.getTotal = me.createAccessor(totalProp);
  573. }
  574. if (successProp) {
  575. me.getSuccess = me.createAccessor(successProp);
  576. }
  577. if (messageProp) {
  578. me.getMessage = me.createAccessor(messageProp);
  579. }
  580. if (idProp) {
  581. idField = me.model.prototype.fields.get(idProp);
  582. if (idField) {
  583. map = idField.mapping;
  584. idProp = (map !== undefined &amp;&amp; map !== null) ? map : idProp;
  585. }
  586. accessor = me.createAccessor(idProp);
  587. me.getId = function(record) {
  588. var id = accessor.call(me, record);
  589. return (id === undefined || id === '') ? null : id;
  590. };
  591. } else {
  592. me.getId = function() {
  593. return null;
  594. };
  595. }
  596. me.convertRecordData = me.buildRecordDataExtractor();
  597. me.lastFieldGeneration = me.model.prototype.fields.generation;
  598. },
  599. recordDataExtractorTemplate : [
  600. 'var me = this\n',
  601. ' ,fields = me.model.prototype.fields\n',
  602. ' ,value\n',
  603. ' ,internalId\n',
  604. '&lt;tpl for=&quot;fields&quot;&gt;',
  605. ' ,__field{#} = fields.get(&quot;{name}&quot;)\n',
  606. '&lt;/tpl&gt;', ';\n',
  607. 'return function(dest, source, record) {\n',
  608. '&lt;tpl for=&quot;fields&quot;&gt;',
  609. // createFieldAccessExpression must be implemented in subclasses to extract data from the source object in the correct way
  610. ' value = {[ this.createFieldAccessExpression(values, &quot;__field&quot; + xindex, &quot;source&quot;) ]};\n',
  611. // Code for processing a source property when a custom convert is defined
  612. '&lt;tpl if=&quot;hasCustomConvert&quot;&gt;',
  613. ' dest[&quot;{name}&quot;] = value === undefined ? __field{#}.convert(__field{#}.defaultValue, record) : __field{#}.convert(value, record);\n',
  614. // Code for processing a source property when there is a default value
  615. '&lt;tpl elseif=&quot;defaultValue !== undefined&quot;&gt;',
  616. ' if (value === undefined) {\n',
  617. ' if (me.applyDefaults) {\n',
  618. '&lt;tpl if=&quot;convert&quot;&gt;',
  619. ' dest[&quot;{name}&quot;] = __field{#}.convert(__field{#}.defaultValue, record);\n',
  620. '&lt;tpl else&gt;',
  621. ' dest[&quot;{name}&quot;] = __field{#}.defaultValue\n',
  622. '&lt;/tpl&gt;',
  623. ' };\n',
  624. ' } else {\n',
  625. '&lt;tpl if=&quot;convert&quot;&gt;',
  626. ' dest[&quot;{name}&quot;] = __field{#}.convert(value, record);\n',
  627. '&lt;tpl else&gt;',
  628. ' dest[&quot;{name}&quot;] = value;\n',
  629. '&lt;/tpl&gt;',
  630. ' };',
  631. // Code for processing a source property value when there is no default value
  632. '&lt;tpl else&gt;',
  633. ' if (value !== undefined) {\n',
  634. '&lt;tpl if=&quot;convert&quot;&gt;',
  635. ' dest[&quot;{name}&quot;] = __field{#}.convert(value, record);\n',
  636. '&lt;tpl else&gt;',
  637. ' dest[&quot;{name}&quot;] = value;\n',
  638. '&lt;/tpl&gt;',
  639. ' }\n',
  640. '&lt;/tpl&gt;',
  641. '&lt;/tpl&gt;',
  642. // set the client id as the internalId of the record.
  643. // clientId handles the case where a client side record did not previously exist on the server,
  644. // so the server is passing back a client id that can be used to pair the server side record up with the client record
  645. '&lt;tpl if=&quot;clientIdProp&quot;&gt;',
  646. ' if (record &amp;&amp; (internalId = {[ this.createFieldAccessExpression(\{mapping: values.clientIdProp\}, null, &quot;source&quot;) ]})) {\n',
  647. ' record.{[&quot;internalId&quot;]} = internalId;\n',
  648. ' }\n',
  649. '&lt;/tpl&gt;',
  650. '};'
  651. ],
  652. <span id='Ext-data-reader-Reader-method-buildRecordDataExtractor'> /**
  653. </span> * @private
  654. * Return a function which will read a raw row object in the format this Reader accepts, and populates
  655. * a record's data object with converted data values.
  656. *
  657. * The returned function must be passed the following parameters:
  658. *
  659. * - dest A record's empty data object into which the new field value properties are injected.
  660. * - source A raw row data object of whatever type this Reader consumes
  661. * - record The record which is being populated.
  662. *
  663. */
  664. buildRecordDataExtractor: function() {
  665. var me = this,
  666. modelProto = me.model.prototype,
  667. templateData = {
  668. clientIdProp: modelProto.clientIdProperty,
  669. fields: modelProto.fields.items
  670. };
  671. me.recordDataExtractorTemplate.createFieldAccessExpression = me.accessExpressionFn;
  672. // Here we are creating a new Function and invoking it immediately in the scope of this Reader
  673. // It declares several vars capturing the configured context of this Reader, and returns a function
  674. // which, when passed a record data object, a raw data row in the format this Reader is configured to read,
  675. // and the record which is being created, will populate the record's data object from the raw row data.
  676. return Ext.functionFactory(me.recordDataExtractorTemplate.apply(templateData)).call(me);
  677. },
  678. destroyReader: function() {
  679. var me = this;
  680. delete me.proxy;
  681. delete me.model;
  682. delete me.convertRecordData;
  683. delete me.getId;
  684. delete me.getTotal;
  685. delete me.getSuccess;
  686. delete me.getMessage;
  687. }
  688. }, function() {
  689. var proto = this.prototype;
  690. Ext.apply(proto, {
  691. // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
  692. nullResultSet: new Ext.data.ResultSet({
  693. total : 0,
  694. count : 0,
  695. records: [],
  696. success: true
  697. }),
  698. recordDataExtractorTemplate: new Ext.XTemplate(proto.recordDataExtractorTemplate)
  699. });
  700. });
  701. </pre>
  702. </body>
  703. </html>