Forked mumble-django project from https://bitbucket.org/Svedrin/mumble-django
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4717 lines
192 KiB

  1. /*!
  2. * Ext JS Library 3.2.0
  3. * Copyright(c) 2006-2010 Ext JS, Inc.
  4. * licensing@extjs.com
  5. * http://www.extjs.com/license
  6. */
  7. /**
  8. * @class Ext.data.Api
  9. * @extends Object
  10. * Ext.data.Api is a singleton designed to manage the data API including methods
  11. * for validating a developer's DataProxy API. Defines variables for CRUD actions
  12. * create, read, update and destroy in addition to a mapping of RESTful HTTP methods
  13. * GET, POST, PUT and DELETE to CRUD actions.
  14. * @singleton
  15. */
  16. Ext.data.Api = (function() {
  17. // private validActions. validActions is essentially an inverted hash of Ext.data.Api.actions, where value becomes the key.
  18. // Some methods in this singleton (e.g.: getActions, getVerb) will loop through actions with the code <code>for (var verb in this.actions)</code>
  19. // For efficiency, some methods will first check this hash for a match. Those methods which do acces validActions will cache their result here.
  20. // We cannot pre-define this hash since the developer may over-ride the actions at runtime.
  21. var validActions = {};
  22. return {
  23. /**
  24. * Defined actions corresponding to remote actions:
  25. * <pre><code>
  26. actions: {
  27. create : 'create', // Text representing the remote-action to create records on server.
  28. read : 'read', // Text representing the remote-action to read/load data from server.
  29. update : 'update', // Text representing the remote-action to update records on server.
  30. destroy : 'destroy' // Text representing the remote-action to destroy records on server.
  31. }
  32. * </code></pre>
  33. * @property actions
  34. * @type Object
  35. */
  36. actions : {
  37. create : 'create',
  38. read : 'read',
  39. update : 'update',
  40. destroy : 'destroy'
  41. },
  42. /**
  43. * Defined {CRUD action}:{HTTP method} pairs to associate HTTP methods with the
  44. * corresponding actions for {@link Ext.data.DataProxy#restful RESTful proxies}.
  45. * Defaults to:
  46. * <pre><code>
  47. restActions : {
  48. create : 'POST',
  49. read : 'GET',
  50. update : 'PUT',
  51. destroy : 'DELETE'
  52. },
  53. * </code></pre>
  54. */
  55. restActions : {
  56. create : 'POST',
  57. read : 'GET',
  58. update : 'PUT',
  59. destroy : 'DELETE'
  60. },
  61. /**
  62. * Returns true if supplied action-name is a valid API action defined in <code>{@link #actions}</code> constants
  63. * @param {String} action Action to test for availability.
  64. * @return {Boolean}
  65. */
  66. isAction : function(action) {
  67. return (Ext.data.Api.actions[action]) ? true : false;
  68. },
  69. /**
  70. * Returns the actual CRUD action KEY "create", "read", "update" or "destroy" from the supplied action-name. This method is used internally and shouldn't generally
  71. * need to be used directly. The key/value pair of Ext.data.Api.actions will often be identical but this is not necessarily true. A developer can override this naming
  72. * convention if desired. However, the framework internally calls methods based upon the KEY so a way of retreiving the the words "create", "read", "update" and "destroy" is
  73. * required. This method will cache discovered KEYS into the private validActions hash.
  74. * @param {String} name The runtime name of the action.
  75. * @return {String||null} returns the action-key, or verb of the user-action or null if invalid.
  76. * @nodoc
  77. */
  78. getVerb : function(name) {
  79. if (validActions[name]) {
  80. return validActions[name]; // <-- found in cache. return immediately.
  81. }
  82. for (var verb in this.actions) {
  83. if (this.actions[verb] === name) {
  84. validActions[name] = verb;
  85. break;
  86. }
  87. }
  88. return (validActions[name] !== undefined) ? validActions[name] : null;
  89. },
  90. /**
  91. * Returns true if the supplied API is valid; that is, check that all keys match defined actions
  92. * otherwise returns an array of mistakes.
  93. * @return {String[]|true}
  94. */
  95. isValid : function(api){
  96. var invalid = [];
  97. var crud = this.actions; // <-- cache a copy of the actions.
  98. for (var action in api) {
  99. if (!(action in crud)) {
  100. invalid.push(action);
  101. }
  102. }
  103. return (!invalid.length) ? true : invalid;
  104. },
  105. /**
  106. * Returns true if the supplied verb upon the supplied proxy points to a unique url in that none of the other api-actions
  107. * point to the same url. The question is important for deciding whether to insert the "xaction" HTTP parameter within an
  108. * Ajax request. This method is used internally and shouldn't generally need to be called directly.
  109. * @param {Ext.data.DataProxy} proxy
  110. * @param {String} verb
  111. * @return {Boolean}
  112. */
  113. hasUniqueUrl : function(proxy, verb) {
  114. var url = (proxy.api[verb]) ? proxy.api[verb].url : null;
  115. var unique = true;
  116. for (var action in proxy.api) {
  117. if ((unique = (action === verb) ? true : (proxy.api[action].url != url) ? true : false) === false) {
  118. break;
  119. }
  120. }
  121. return unique;
  122. },
  123. /**
  124. * This method is used internally by <tt>{@link Ext.data.DataProxy DataProxy}</tt> and should not generally need to be used directly.
  125. * Each action of a DataProxy api can be initially defined as either a String or an Object. When specified as an object,
  126. * one can explicitly define the HTTP method (GET|POST) to use for each CRUD action. This method will prepare the supplied API, setting
  127. * each action to the Object form. If your API-actions do not explicitly define the HTTP method, the "method" configuration-parameter will
  128. * be used. If the method configuration parameter is not specified, POST will be used.
  129. <pre><code>
  130. new Ext.data.HttpProxy({
  131. method: "POST", // <-- default HTTP method when not specified.
  132. api: {
  133. create: 'create.php',
  134. load: 'read.php',
  135. save: 'save.php',
  136. destroy: 'destroy.php'
  137. }
  138. });
  139. // Alternatively, one can use the object-form to specify the API
  140. new Ext.data.HttpProxy({
  141. api: {
  142. load: {url: 'read.php', method: 'GET'},
  143. create: 'create.php',
  144. destroy: 'destroy.php',
  145. save: 'update.php'
  146. }
  147. });
  148. </code></pre>
  149. *
  150. * @param {Ext.data.DataProxy} proxy
  151. */
  152. prepare : function(proxy) {
  153. if (!proxy.api) {
  154. proxy.api = {}; // <-- No api? create a blank one.
  155. }
  156. for (var verb in this.actions) {
  157. var action = this.actions[verb];
  158. proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn;
  159. if (typeof(proxy.api[action]) == 'string') {
  160. proxy.api[action] = {
  161. url: proxy.api[action],
  162. method: (proxy.restful === true) ? Ext.data.Api.restActions[action] : undefined
  163. };
  164. }
  165. }
  166. },
  167. /**
  168. * Prepares a supplied Proxy to be RESTful. Sets the HTTP method for each api-action to be one of
  169. * GET, POST, PUT, DELETE according to the defined {@link #restActions}.
  170. * @param {Ext.data.DataProxy} proxy
  171. */
  172. restify : function(proxy) {
  173. proxy.restful = true;
  174. for (var verb in this.restActions) {
  175. proxy.api[this.actions[verb]].method ||
  176. (proxy.api[this.actions[verb]].method = this.restActions[verb]);
  177. }
  178. // TODO: perhaps move this interceptor elsewhere? like into DataProxy, perhaps? Placed here
  179. // to satisfy initial 3.0 final release of REST features.
  180. proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) {
  181. var reader = o.reader;
  182. var res = new Ext.data.Response({
  183. action: action,
  184. raw: response
  185. });
  186. switch (response.status) {
  187. case 200: // standard 200 response, send control back to HttpProxy#onWrite by returning true from this intercepted #onWrite
  188. return true;
  189. break;
  190. case 201: // entity created but no response returned
  191. if (Ext.isEmpty(res.raw.responseText)) {
  192. res.success = true;
  193. } else {
  194. //if the response contains data, treat it like a 200
  195. return true;
  196. }
  197. break;
  198. case 204: // no-content. Create a fake response.
  199. res.success = true;
  200. res.data = null;
  201. break;
  202. default:
  203. return true;
  204. break;
  205. }
  206. if (res.success === true) {
  207. this.fireEvent("write", this, action, res.data, res, rs, o.request.arg);
  208. } else {
  209. this.fireEvent('exception', this, 'remote', action, o, res, rs);
  210. }
  211. o.request.callback.call(o.request.scope, res.data, res, res.success);
  212. return false; // <-- false to prevent intercepted function from running.
  213. }, proxy);
  214. }
  215. };
  216. })();
  217. /**
  218. * Ext.data.Response
  219. * Experimental. Do not use directly.
  220. */
  221. Ext.data.Response = function(params, response) {
  222. Ext.apply(this, params, {
  223. raw: response
  224. });
  225. };
  226. Ext.data.Response.prototype = {
  227. message : null,
  228. success : false,
  229. status : null,
  230. root : null,
  231. raw : null,
  232. getMessage : function() {
  233. return this.message;
  234. },
  235. getSuccess : function() {
  236. return this.success;
  237. },
  238. getStatus : function() {
  239. return this.status;
  240. },
  241. getRoot : function() {
  242. return this.root;
  243. },
  244. getRawResponse : function() {
  245. return this.raw;
  246. }
  247. };
  248. /**
  249. * @class Ext.data.Api.Error
  250. * @extends Ext.Error
  251. * Error class for Ext.data.Api errors
  252. */
  253. Ext.data.Api.Error = Ext.extend(Ext.Error, {
  254. constructor : function(message, arg) {
  255. this.arg = arg;
  256. Ext.Error.call(this, message);
  257. },
  258. name: 'Ext.data.Api'
  259. });
  260. Ext.apply(Ext.data.Api.Error.prototype, {
  261. lang: {
  262. 'action-url-undefined': 'No fallback url defined for this action. When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.',
  263. 'invalid': 'received an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions',
  264. 'invalid-url': 'Invalid url. Please review your proxy configuration.',
  265. 'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'
  266. }
  267. });
  268. /**
  269. * @class Ext.data.SortTypes
  270. * @singleton
  271. * Defines the default sorting (casting?) comparison functions used when sorting data.
  272. */
  273. Ext.data.SortTypes = {
  274. /**
  275. * Default sort that does nothing
  276. * @param {Mixed} s The value being converted
  277. * @return {Mixed} The comparison value
  278. */
  279. none : function(s){
  280. return s;
  281. },
  282. /**
  283. * The regular expression used to strip tags
  284. * @type {RegExp}
  285. * @property
  286. */
  287. stripTagsRE : /<\/?[^>]+>/gi,
  288. /**
  289. * Strips all HTML tags to sort on text only
  290. * @param {Mixed} s The value being converted
  291. * @return {String} The comparison value
  292. */
  293. asText : function(s){
  294. return String(s).replace(this.stripTagsRE, "");
  295. },
  296. /**
  297. * Strips all HTML tags to sort on text only - Case insensitive
  298. * @param {Mixed} s The value being converted
  299. * @return {String} The comparison value
  300. */
  301. asUCText : function(s){
  302. return String(s).toUpperCase().replace(this.stripTagsRE, "");
  303. },
  304. /**
  305. * Case insensitive string
  306. * @param {Mixed} s The value being converted
  307. * @return {String} The comparison value
  308. */
  309. asUCString : function(s) {
  310. return String(s).toUpperCase();
  311. },
  312. /**
  313. * Date sorting
  314. * @param {Mixed} s The value being converted
  315. * @return {Number} The comparison value
  316. */
  317. asDate : function(s) {
  318. if(!s){
  319. return 0;
  320. }
  321. if(Ext.isDate(s)){
  322. return s.getTime();
  323. }
  324. return Date.parse(String(s));
  325. },
  326. /**
  327. * Float sorting
  328. * @param {Mixed} s The value being converted
  329. * @return {Float} The comparison value
  330. */
  331. asFloat : function(s) {
  332. var val = parseFloat(String(s).replace(/,/g, ""));
  333. return isNaN(val) ? 0 : val;
  334. },
  335. /**
  336. * Integer sorting
  337. * @param {Mixed} s The value being converted
  338. * @return {Number} The comparison value
  339. */
  340. asInt : function(s) {
  341. var val = parseInt(String(s).replace(/,/g, ""), 10);
  342. return isNaN(val) ? 0 : val;
  343. }
  344. };/**
  345. * @class Ext.data.Record
  346. * <p>Instances of this class encapsulate both Record <em>definition</em> information, and Record
  347. * <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs
  348. * to access Records cached in an {@link Ext.data.Store} object.</p>
  349. * <p>Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
  350. * Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data
  351. * objects.</p>
  352. * <p>Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.
  353. * In order to copy data from one Store to another, use the {@link #copy} method to create an exact
  354. * copy of the Record, and insert the new instance into the other Store.</p>
  355. * <p>When serializing a Record for submission to the server, be aware that it contains many private
  356. * properties, and also a reference to its owning Store which in turn holds references to its Records.
  357. * This means that a whole Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the
  358. * <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
  359. * <p>Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.</p>
  360. * @constructor
  361. * <p>This constructor should not be used to create Record objects. Instead, use {@link #create} to
  362. * generate a subclass of Ext.data.Record configured with information about its constituent fields.<p>
  363. * <p><b>The generated constructor has the same signature as this constructor.</b></p>
  364. * @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
  365. * fields. If not specified the <code>{@link Ext.data.Field#defaultValue defaultValue}</code>
  366. * for each field will be assigned.
  367. * @param {Object} id (Optional) The id of the Record. The id is used by the
  368. * {@link Ext.data.Store} object which owns the Record to index its collection
  369. * of Records (therefore this id should be unique within each store). If an
  370. * <code>id</code> is not specified a <b><code>{@link #phantom}</code></b>
  371. * Record will be created with an {@link #Record.id automatically generated id}.
  372. */
  373. Ext.data.Record = function(data, id){
  374. // if no id, call the auto id method
  375. this.id = (id || id === 0) ? id : Ext.data.Record.id(this);
  376. this.data = data || {};
  377. };
  378. /**
  379. * Generate a constructor for a specific Record layout.
  380. * @param {Array} o An Array of <b>{@link Ext.data.Field Field}</b> definition objects.
  381. * The constructor generated by this method may be used to create new Record instances. The data
  382. * object must contain properties named after the {@link Ext.data.Field field}
  383. * <b><tt>{@link Ext.data.Field#name}s</tt></b>. Example usage:<pre><code>
  384. // create a Record constructor from a description of the fields
  385. var TopicRecord = Ext.data.Record.create([ // creates a subclass of Ext.data.Record
  386. {{@link Ext.data.Field#name name}: 'title', {@link Ext.data.Field#mapping mapping}: 'topic_title'},
  387. {name: 'author', mapping: 'username', allowBlank: false},
  388. {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
  389. {name: 'lastPost', mapping: 'post_time', type: 'date'},
  390. {name: 'lastPoster', mapping: 'user2'},
  391. {name: 'excerpt', mapping: 'post_text', allowBlank: false},
  392. // In the simplest case, if no properties other than <tt>name</tt> are required,
  393. // a field definition may consist of just a String for the field name.
  394. 'signature'
  395. ]);
  396. // create Record instance
  397. var myNewRecord = new TopicRecord(
  398. {
  399. title: 'Do my job please',
  400. author: 'noobie',
  401. totalPosts: 1,
  402. lastPost: new Date(),
  403. lastPoster: 'Animal',
  404. excerpt: 'No way dude!',
  405. signature: ''
  406. },
  407. id // optionally specify the id of the record otherwise {@link #Record.id one is auto-assigned}
  408. );
  409. myStore.{@link Ext.data.Store#add add}(myNewRecord);
  410. </code></pre>
  411. * @method create
  412. * @return {Function} A constructor which is used to create new Records according
  413. * to the definition. The constructor has the same signature as {@link #Record}.
  414. * @static
  415. */
  416. Ext.data.Record.create = function(o){
  417. var f = Ext.extend(Ext.data.Record, {});
  418. var p = f.prototype;
  419. p.fields = new Ext.util.MixedCollection(false, function(field){
  420. return field.name;
  421. });
  422. for(var i = 0, len = o.length; i < len; i++){
  423. p.fields.add(new Ext.data.Field(o[i]));
  424. }
  425. f.getField = function(name){
  426. return p.fields.get(name);
  427. };
  428. return f;
  429. };
  430. Ext.data.Record.PREFIX = 'ext-record';
  431. Ext.data.Record.AUTO_ID = 1;
  432. Ext.data.Record.EDIT = 'edit';
  433. Ext.data.Record.REJECT = 'reject';
  434. Ext.data.Record.COMMIT = 'commit';
  435. /**
  436. * Generates a sequential id. This method is typically called when a record is {@link #create}d
  437. * and {@link #Record no id has been specified}. The returned id takes the form:
  438. * <tt>&#123;PREFIX}-&#123;AUTO_ID}</tt>.<div class="mdetail-params"><ul>
  439. * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.PREFIX</tt>
  440. * (defaults to <tt>'ext-record'</tt>)</p></li>
  441. * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.AUTO_ID</tt>
  442. * (defaults to <tt>1</tt> initially)</p></li>
  443. * </ul></div>
  444. * @param {Record} rec The record being created. The record does not exist, it's a {@link #phantom}.
  445. * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
  446. */
  447. Ext.data.Record.id = function(rec) {
  448. rec.phantom = true;
  449. return [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
  450. };
  451. Ext.data.Record.prototype = {
  452. /**
  453. * <p><b>This property is stored in the Record definition's <u>prototype</u></b></p>
  454. * A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record. Read-only.
  455. * @property fields
  456. * @type Ext.util.MixedCollection
  457. */
  458. /**
  459. * An object hash representing the data for this Record. Every field name in the Record definition
  460. * is represented by a property of that name in this object. Note that unless you specified a field
  461. * with {@link Ext.data.Field#name name} "id" in the Record definition, this will <b>not</b> contain
  462. * an <tt>id</tt> property.
  463. * @property data
  464. * @type {Object}
  465. */
  466. /**
  467. * The unique ID of the Record {@link #Record as specified at construction time}.
  468. * @property id
  469. * @type {Object}
  470. */
  471. /**
  472. * <p><b>Only present if this Record was created by an {@link Ext.data.XmlReader XmlReader}</b>.</p>
  473. * <p>The XML element which was the source of the data for this Record.</p>
  474. * @property node
  475. * @type {XMLElement}
  476. */
  477. /**
  478. * <p><b>Only present if this Record was created by an {@link Ext.data.ArrayReader ArrayReader} or a {@link Ext.data.JsonReader JsonReader}</b>.</p>
  479. * <p>The Array or object which was the source of the data for this Record.</p>
  480. * @property json
  481. * @type {Array|Object}
  482. */
  483. /**
  484. * Readonly flag - true if this Record has been modified.
  485. * @type Boolean
  486. */
  487. dirty : false,
  488. editing : false,
  489. error : null,
  490. /**
  491. * This object contains a key and value storing the original values of all modified
  492. * fields or is null if no fields have been modified.
  493. * @property modified
  494. * @type {Object}
  495. */
  496. modified : null,
  497. /**
  498. * <tt>true</tt> when the record does not yet exist in a server-side database (see
  499. * {@link #markDirty}). Any record which has a real database pk set as its id property
  500. * is NOT a phantom -- it's real.
  501. * @property phantom
  502. * @type {Boolean}
  503. */
  504. phantom : false,
  505. // private
  506. join : function(store){
  507. /**
  508. * The {@link Ext.data.Store} to which this Record belongs.
  509. * @property store
  510. * @type {Ext.data.Store}
  511. */
  512. this.store = store;
  513. },
  514. /**
  515. * Set the {@link Ext.data.Field#name named field} to the specified value. For example:
  516. * <pre><code>
  517. // record has a field named 'firstname'
  518. var Employee = Ext.data.Record.{@link #create}([
  519. {name: 'firstname'},
  520. ...
  521. ]);
  522. // update the 2nd record in the store:
  523. var rec = myStore.{@link Ext.data.Store#getAt getAt}(1);
  524. // set the value (shows dirty flag):
  525. rec.set('firstname', 'Betty');
  526. // commit the change (removes dirty flag):
  527. rec.{@link #commit}();
  528. // update the record in the store, bypass setting dirty flag,
  529. // and do not store the change in the {@link Ext.data.Store#getModifiedRecords modified records}
  530. rec.{@link #data}['firstname'] = 'Wilma'; // updates record, but not the view
  531. rec.{@link #commit}(); // updates the view
  532. * </code></pre>
  533. * <b>Notes</b>:<div class="mdetail-params"><ul>
  534. * <li>If the store has a writer and <code>autoSave=true</code>, each set()
  535. * will execute an XHR to the server.</li>
  536. * <li>Use <code>{@link #beginEdit}</code> to prevent the store's <code>update</code>
  537. * event firing while using set().</li>
  538. * <li>Use <code>{@link #endEdit}</code> to have the store's <code>update</code>
  539. * event fire.</li>
  540. * </ul></div>
  541. * @param {String} name The {@link Ext.data.Field#name name of the field} to set.
  542. * @param {String/Object/Array} value The value to set the field to.
  543. */
  544. set : function(name, value){
  545. var encode = Ext.isPrimitive(value) ? String : Ext.encode;
  546. if(encode(this.data[name]) == encode(value)) {
  547. return;
  548. }
  549. this.dirty = true;
  550. if(!this.modified){
  551. this.modified = {};
  552. }
  553. if(this.modified[name] === undefined){
  554. this.modified[name] = this.data[name];
  555. }
  556. this.data[name] = value;
  557. if(!this.editing){
  558. this.afterEdit();
  559. }
  560. },
  561. // private
  562. afterEdit : function(){
  563. if (this.store != undefined && typeof this.store.afterEdit == "function") {
  564. this.store.afterEdit(this);
  565. }
  566. },
  567. // private
  568. afterReject : function(){
  569. if(this.store){
  570. this.store.afterReject(this);
  571. }
  572. },
  573. // private
  574. afterCommit : function(){
  575. if(this.store){
  576. this.store.afterCommit(this);
  577. }
  578. },
  579. /**
  580. * Get the value of the {@link Ext.data.Field#name named field}.
  581. * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
  582. * @return {Object} The value of the field.
  583. */
  584. get : function(name){
  585. return this.data[name];
  586. },
  587. /**
  588. * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
  589. * are relayed to the containing store.
  590. * See also: <code>{@link #endEdit}</code> and <code>{@link #cancelEdit}</code>.
  591. */
  592. beginEdit : function(){
  593. this.editing = true;
  594. this.modified = this.modified || {};
  595. },
  596. /**
  597. * Cancels all changes made in the current edit operation.
  598. */
  599. cancelEdit : function(){
  600. this.editing = false;
  601. delete this.modified;
  602. },
  603. /**
  604. * End an edit. If any data was modified, the containing store is notified
  605. * (ie, the store's <code>update</code> event will fire).
  606. */
  607. endEdit : function(){
  608. this.editing = false;
  609. if(this.dirty){
  610. this.afterEdit();
  611. }
  612. },
  613. /**
  614. * Usually called by the {@link Ext.data.Store} which owns the Record.
  615. * Rejects all changes made to the Record since either creation, or the last commit operation.
  616. * Modified fields are reverted to their original values.
  617. * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
  618. * to have their code notified of reject operations.</p>
  619. * @param {Boolean} silent (optional) True to skip notification of the owning
  620. * store of the change (defaults to false)
  621. */
  622. reject : function(silent){
  623. var m = this.modified;
  624. for(var n in m){
  625. if(typeof m[n] != "function"){
  626. this.data[n] = m[n];
  627. }
  628. }
  629. this.dirty = false;
  630. delete this.modified;
  631. this.editing = false;
  632. if(silent !== true){
  633. this.afterReject();
  634. }
  635. },
  636. /**
  637. * Usually called by the {@link Ext.data.Store} which owns the Record.
  638. * Commits all changes made to the Record since either creation, or the last commit operation.
  639. * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
  640. * to have their code notified of commit operations.</p>
  641. * @param {Boolean} silent (optional) True to skip notification of the owning
  642. * store of the change (defaults to false)
  643. */
  644. commit : function(silent){
  645. this.dirty = false;
  646. delete this.modified;
  647. this.editing = false;
  648. if(silent !== true){
  649. this.afterCommit();
  650. }
  651. },
  652. /**
  653. * Gets a hash of only the fields that have been modified since this Record was created or commited.
  654. * @return Object
  655. */
  656. getChanges : function(){
  657. var m = this.modified, cs = {};
  658. for(var n in m){
  659. if(m.hasOwnProperty(n)){
  660. cs[n] = this.data[n];
  661. }
  662. }
  663. return cs;
  664. },
  665. // private
  666. hasError : function(){
  667. return this.error !== null;
  668. },
  669. // private
  670. clearError : function(){
  671. this.error = null;
  672. },
  673. /**
  674. * Creates a copy (clone) of this Record.
  675. * @param {String} id (optional) A new Record id, defaults to the id
  676. * of the record being copied. See <code>{@link #id}</code>.
  677. * To generate a phantom record with a new id use:<pre><code>
  678. var rec = record.copy(); // clone the record
  679. Ext.data.Record.id(rec); // automatically generate a unique sequential id
  680. * </code></pre>
  681. * @return {Record}
  682. */
  683. copy : function(newId) {
  684. return new this.constructor(Ext.apply({}, this.data), newId || this.id);
  685. },
  686. /**
  687. * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
  688. * since the load or last commit.
  689. * @param {String} fieldName {@link Ext.data.Field.{@link Ext.data.Field#name}
  690. * @return {Boolean}
  691. */
  692. isModified : function(fieldName){
  693. return !!(this.modified && this.modified.hasOwnProperty(fieldName));
  694. },
  695. /**
  696. * By default returns <tt>false</tt> if any {@link Ext.data.Field field} within the
  697. * record configured with <tt>{@link Ext.data.Field#allowBlank} = false</tt> returns
  698. * <tt>true</tt> from an {@link Ext}.{@link Ext#isEmpty isempty} test.
  699. * @return {Boolean}
  700. */
  701. isValid : function() {
  702. return this.fields.find(function(f) {
  703. return (f.allowBlank === false && Ext.isEmpty(this.data[f.name])) ? true : false;
  704. },this) ? false : true;
  705. },
  706. /**
  707. * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method
  708. * is used interally when adding <code>{@link #phantom}</code> records to a
  709. * {@link Ext.data.Store#writer writer enabled store}.</p>
  710. * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
  711. * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
  712. * have a create action composed for it during {@link Ext.data.Store#save store save}
  713. * operations.</p>
  714. */
  715. markDirty : function(){
  716. this.dirty = true;
  717. if(!this.modified){
  718. this.modified = {};
  719. }
  720. this.fields.each(function(f) {
  721. this.modified[f.name] = this.data[f.name];
  722. },this);
  723. }
  724. };
  725. /**
  726. * @class Ext.StoreMgr
  727. * @extends Ext.util.MixedCollection
  728. * The default global group of stores.
  729. * @singleton
  730. */
  731. Ext.StoreMgr = Ext.apply(new Ext.util.MixedCollection(), {
  732. /**
  733. * @cfg {Object} listeners @hide
  734. */
  735. /**
  736. * Registers one or more Stores with the StoreMgr. You do not normally need to register stores
  737. * manually. Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
  738. * @param {Ext.data.Store} store1 A Store instance
  739. * @param {Ext.data.Store} store2 (optional)
  740. * @param {Ext.data.Store} etc... (optional)
  741. */
  742. register : function(){
  743. for(var i = 0, s; (s = arguments[i]); i++){
  744. this.add(s);
  745. }
  746. },
  747. /**
  748. * Unregisters one or more Stores with the StoreMgr
  749. * @param {String/Object} id1 The id of the Store, or a Store instance
  750. * @param {String/Object} id2 (optional)
  751. * @param {String/Object} etc... (optional)
  752. */
  753. unregister : function(){
  754. for(var i = 0, s; (s = arguments[i]); i++){
  755. this.remove(this.lookup(s));
  756. }
  757. },
  758. /**
  759. * Gets a registered Store by id
  760. * @param {String/Object} id The id of the Store, or a Store instance
  761. * @return {Ext.data.Store}
  762. */
  763. lookup : function(id){
  764. if(Ext.isArray(id)){
  765. var fields = ['field1'], expand = !Ext.isArray(id[0]);
  766. if(!expand){
  767. for(var i = 2, len = id[0].length; i <= len; ++i){
  768. fields.push('field' + i);
  769. }
  770. }
  771. return new Ext.data.ArrayStore({
  772. fields: fields,
  773. data: id,
  774. expandData: expand,
  775. autoDestroy: true,
  776. autoCreated: true
  777. });
  778. }
  779. return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
  780. },
  781. // getKey implementation for MixedCollection
  782. getKey : function(o){
  783. return o.storeId;
  784. }
  785. });/**
  786. * @class Ext.data.Store
  787. * @extends Ext.util.Observable
  788. * <p>The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
  789. * objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
  790. * the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}.</p>
  791. * <p><u>Retrieving Data</u></p>
  792. * <p>A Store object may access a data object using:<div class="mdetail-params"><ul>
  793. * <li>{@link #proxy configured implementation} of {@link Ext.data.DataProxy DataProxy}</li>
  794. * <li>{@link #data} to automatically pass in data</li>
  795. * <li>{@link #loadData} to manually pass in data</li>
  796. * </ul></div></p>
  797. * <p><u>Reading Data</u></p>
  798. * <p>A Store object has no inherent knowledge of the format of the data object (it could be
  799. * an Array, XML, or JSON). A Store object uses an appropriate {@link #reader configured implementation}
  800. * of a {@link Ext.data.DataReader DataReader} to create {@link Ext.data.Record Record} instances from the data
  801. * object.</p>
  802. * <p><u>Store Types</u></p>
  803. * <p>There are several implementations of Store available which are customized for use with
  804. * a specific DataReader implementation. Here is an example using an ArrayStore which implicitly
  805. * creates a reader commensurate to an Array data object.</p>
  806. * <pre><code>
  807. var myStore = new Ext.data.ArrayStore({
  808. fields: ['fullname', 'first'],
  809. idIndex: 0 // id for each record will be the first element
  810. });
  811. * </code></pre>
  812. * <p>For custom implementations create a basic {@link Ext.data.Store} configured as needed:</p>
  813. * <pre><code>
  814. // create a {@link Ext.data.Record Record} constructor:
  815. var rt = Ext.data.Record.create([
  816. {name: 'fullname'},
  817. {name: 'first'}
  818. ]);
  819. var myStore = new Ext.data.Store({
  820. // explicitly create reader
  821. reader: new Ext.data.ArrayReader(
  822. {
  823. idIndex: 0 // id for each record will be the first element
  824. },
  825. rt // recordType
  826. )
  827. });
  828. * </code></pre>
  829. * <p>Load some data into store (note the data object is an array which corresponds to the reader):</p>
  830. * <pre><code>
  831. var myData = [
  832. [1, 'Fred Flintstone', 'Fred'], // note that id for the record is the first element
  833. [2, 'Barney Rubble', 'Barney']
  834. ];
  835. myStore.loadData(myData);
  836. * </code></pre>
  837. * <p>Records are cached and made available through accessor functions. An example of adding
  838. * a record to the store:</p>
  839. * <pre><code>
  840. var defaultData = {
  841. fullname: 'Full Name',
  842. first: 'First Name'
  843. };
  844. var recId = 100; // provide unique id for the record
  845. var r = new myStore.recordType(defaultData, ++recId); // create new record
  846. myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add})
  847. * </code></pre>
  848. * <p><u>Writing Data</u></p>
  849. * <p>And <b>new in Ext version 3</b>, use the new {@link Ext.data.DataWriter DataWriter} to create an automated, <a href="http://extjs.com/deploy/dev/examples/writer/writer.html">Writable Store</a>
  850. * along with <a href="http://extjs.com/deploy/dev/examples/restful/restful.html">RESTful features.</a>
  851. * @constructor
  852. * Creates a new Store.
  853. * @param {Object} config A config object containing the objects needed for the Store to access data,
  854. * and read the data into Records.
  855. * @xtype store
  856. */
  857. Ext.data.Store = Ext.extend(Ext.util.Observable, {
  858. /**
  859. * @cfg {String} storeId If passed, the id to use to register with the <b>{@link Ext.StoreMgr StoreMgr}</b>.
  860. * <p><b>Note</b>: if a (deprecated) <tt>{@link #id}</tt> is specified it will supersede the <tt>storeId</tt>
  861. * assignment.</p>
  862. */
  863. /**
  864. * @cfg {String} url If a <tt>{@link #proxy}</tt> is not specified the <tt>url</tt> will be used to
  865. * implicitly configure a {@link Ext.data.HttpProxy HttpProxy} if an <tt>url</tt> is specified.
  866. * Typically this option, or the <code>{@link #data}</code> option will be specified.
  867. */
  868. /**
  869. * @cfg {Boolean/Object} autoLoad If <tt>{@link #data}</tt> is not specified, and if <tt>autoLoad</tt>
  870. * is <tt>true</tt> or an <tt>Object</tt>, this store's {@link #load} method is automatically called
  871. * after creation. If the value of <tt>autoLoad</tt> is an <tt>Object</tt>, this <tt>Object</tt> will
  872. * be passed to the store's {@link #load} method.
  873. */
  874. /**
  875. * @cfg {Ext.data.DataProxy} proxy The {@link Ext.data.DataProxy DataProxy} object which provides
  876. * access to a data object. See <code>{@link #url}</code>.
  877. */
  878. /**
  879. * @cfg {Array} data An inline data object readable by the <code>{@link #reader}</code>.
  880. * Typically this option, or the <code>{@link #url}</code> option will be specified.
  881. */
  882. /**
  883. * @cfg {Ext.data.DataReader} reader The {@link Ext.data.DataReader Reader} object which processes the
  884. * data object and returns an Array of {@link Ext.data.Record} objects which are cached keyed by their
  885. * <b><tt>{@link Ext.data.Record#id id}</tt></b> property.
  886. */
  887. /**
  888. * @cfg {Ext.data.DataWriter} writer
  889. * <p>The {@link Ext.data.DataWriter Writer} object which processes a record object for being written
  890. * to the server-side database.</p>
  891. * <br><p>When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
  892. * events on the store are monitored in order to remotely {@link #createRecords create records},
  893. * {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.</p>
  894. * <br><p>The proxy for this store will relay any {@link #writexception} events to this store.</p>
  895. * <br><p>Sample implementation:
  896. * <pre><code>
  897. var writer = new {@link Ext.data.JsonWriter}({
  898. encode: true,
  899. writeAllFields: true // write all fields, not just those that changed
  900. });
  901. // Typical Store collecting the Proxy, Reader and Writer together.
  902. var store = new Ext.data.Store({
  903. storeId: 'user',
  904. root: 'records',
  905. proxy: proxy,
  906. reader: reader,
  907. writer: writer, // <-- plug a DataWriter into the store just as you would a Reader
  908. paramsAsHash: true,
  909. autoSave: false // <-- false to delay executing create, update, destroy requests
  910. // until specifically told to do so.
  911. });
  912. * </code></pre></p>
  913. */
  914. writer : undefined,
  915. /**
  916. * @cfg {Object} baseParams
  917. * <p>An object containing properties which are to be sent as parameters
  918. * for <i>every</i> HTTP request.</p>
  919. * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
  920. * <p><b>Note</b>: <code>baseParams</code> may be superseded by any <code>params</code>
  921. * specified in a <code>{@link #load}</code> request, see <code>{@link #load}</code>
  922. * for more details.</p>
  923. * This property may be modified after creation using the <code>{@link #setBaseParam}</code>
  924. * method.
  925. * @property
  926. */
  927. /**
  928. * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's
  929. * {@link #load} operation. Note that for local sorting, the <tt>direction</tt> property is
  930. * case-sensitive. See also {@link #remoteSort} and {@link #paramNames}.
  931. * For example:<pre><code>
  932. sortInfo: {
  933. field: 'fieldName',
  934. direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
  935. }
  936. </code></pre>
  937. */
  938. /**
  939. * @cfg {boolean} remoteSort <tt>true</tt> if sorting is to be handled by requesting the <tt>{@link #proxy Proxy}</tt>
  940. * to provide a refreshed version of the data object in sorted order, as opposed to sorting the Record cache
  941. * in place (defaults to <tt>false</tt>).
  942. * <p>If <tt>remoteSort</tt> is <tt>true</tt>, then clicking on a {@link Ext.grid.Column Grid Column}'s
  943. * {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
  944. * the following two parameters to the <b><tt>{@link #load params}</tt></b>:<div class="mdetail-params"><ul>
  945. * <li><b><tt>sort</tt></b> : String<p class="sub-desc">The <tt>name</tt> (as specified in the Record's
  946. * {@link Ext.data.Field Field definition}) of the field to sort on.</p></li>
  947. * <li><b><tt>dir</tt></b> : String<p class="sub-desc">The direction of the sort, 'ASC' or 'DESC' (case-sensitive).</p></li>
  948. * </ul></div></p>
  949. */
  950. remoteSort : false,
  951. /**
  952. * @cfg {Boolean} autoDestroy <tt>true</tt> to destroy the store when the component the store is bound
  953. * to is destroyed (defaults to <tt>false</tt>).
  954. * <p><b>Note</b>: this should be set to true when using stores that are bound to only 1 component.</p>
  955. */
  956. autoDestroy : false,
  957. /**
  958. * @cfg {Boolean} pruneModifiedRecords <tt>true</tt> to clear all modified record information each time
  959. * the store is loaded or when a record is removed (defaults to <tt>false</tt>). See {@link #getModifiedRecords}
  960. * for the accessor method to retrieve the modified records.
  961. */
  962. pruneModifiedRecords : false,
  963. /**
  964. * Contains the last options object used as the parameter to the {@link #load} method. See {@link #load}
  965. * for the details of what this may contain. This may be useful for accessing any params which were used
  966. * to load the current Record cache.
  967. * @property
  968. */
  969. lastOptions : null,
  970. /**
  971. * @cfg {Boolean} autoSave
  972. * <p>Defaults to <tt>true</tt> causing the store to automatically {@link #save} records to
  973. * the server when a record is modified (ie: becomes 'dirty'). Specify <tt>false</tt> to manually call {@link #save}
  974. * to send all modifiedRecords to the server.</p>
  975. * <br><p><b>Note</b>: each CRUD action will be sent as a separate request.</p>
  976. */
  977. autoSave : true,
  978. /**
  979. * @cfg {Boolean} batch
  980. * <p>Defaults to <tt>true</tt> (unless <code>{@link #restful}:true</code>). Multiple
  981. * requests for each CRUD action (CREATE, READ, UPDATE and DESTROY) will be combined
  982. * and sent as one transaction. Only applies when <code>{@link #autoSave}</code> is set
  983. * to <tt>false</tt>.</p>
  984. * <br><p>If Store is RESTful, the DataProxy is also RESTful, and a unique transaction is
  985. * generated for each record.</p>
  986. */
  987. batch : true,
  988. /**
  989. * @cfg {Boolean} restful
  990. * Defaults to <tt>false</tt>. Set to <tt>true</tt> to have the Store and the set
  991. * Proxy operate in a RESTful manner. The store will automatically generate GET, POST,
  992. * PUT and DELETE requests to the server. The HTTP method used for any given CRUD
  993. * action is described in {@link Ext.data.Api#restActions}. For additional information
  994. * see {@link Ext.data.DataProxy#restful}.
  995. * <p><b>Note</b>: if <code>{@link #restful}:true</code> <code>batch</code> will
  996. * internally be set to <tt>false</tt>.</p>
  997. */
  998. restful: false,
  999. /**
  1000. * @cfg {Object} paramNames
  1001. * <p>An object containing properties which specify the names of the paging and
  1002. * sorting parameters passed to remote servers when loading blocks of data. By default, this
  1003. * object takes the following form:</p><pre><code>
  1004. {
  1005. start : 'start', // The parameter name which specifies the start row
  1006. limit : 'limit', // The parameter name which specifies number of rows to return
  1007. sort : 'sort', // The parameter name which specifies the column to sort on
  1008. dir : 'dir' // The parameter name which specifies the sort direction
  1009. }
  1010. </code></pre>
  1011. * <p>The server must produce the requested data block upon receipt of these parameter names.
  1012. * If different parameter names are required, this property can be overriden using a configuration
  1013. * property.</p>
  1014. * <p>A {@link Ext.PagingToolbar PagingToolbar} bound to this Store uses this property to determine
  1015. * the parameter names to use in its {@link #load requests}.
  1016. */
  1017. paramNames : undefined,
  1018. /**
  1019. * @cfg {Object} defaultParamNames
  1020. * Provides the default values for the {@link #paramNames} property. To globally modify the parameters
  1021. * for all stores, this object should be changed on the store prototype.
  1022. */
  1023. defaultParamNames : {
  1024. start : 'start',
  1025. limit : 'limit',
  1026. sort : 'sort',
  1027. dir : 'dir'
  1028. },
  1029. /**
  1030. * @property isDestroyed
  1031. * @type Boolean
  1032. * True if the store has been destroyed already. Read only
  1033. */
  1034. isDestroyed: false,
  1035. /**
  1036. * @property hasMultiSort
  1037. * @type Boolean
  1038. * True if this store is currently sorted by more than one field/direction combination.
  1039. */
  1040. hasMultiSort: false,
  1041. // private
  1042. batchKey : '_ext_batch_',
  1043. constructor : function(config){
  1044. this.data = new Ext.util.MixedCollection(false);
  1045. this.data.getKey = function(o){
  1046. return o.id;
  1047. };
  1048. // temporary removed-records cache
  1049. this.removed = [];
  1050. if(config && config.data){
  1051. this.inlineData = config.data;
  1052. delete config.data;
  1053. }
  1054. Ext.apply(this, config);
  1055. /**
  1056. * See the <code>{@link #baseParams corresponding configuration option}</code>
  1057. * for a description of this property.
  1058. * To modify this property see <code>{@link #setBaseParam}</code>.
  1059. * @property
  1060. */
  1061. this.baseParams = Ext.isObject(this.baseParams) ? this.baseParams : {};
  1062. this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
  1063. if((this.url || this.api) && !this.proxy){
  1064. this.proxy = new Ext.data.HttpProxy({url: this.url, api: this.api});
  1065. }
  1066. // If Store is RESTful, so too is the DataProxy
  1067. if (this.restful === true && this.proxy) {
  1068. // When operating RESTfully, a unique transaction is generated for each record.
  1069. // TODO might want to allow implemention of faux REST where batch is possible using RESTful routes only.
  1070. this.batch = false;
  1071. Ext.data.Api.restify(this.proxy);
  1072. }
  1073. if(this.reader){ // reader passed
  1074. if(!this.recordType){
  1075. this.recordType = this.reader.recordType;
  1076. }
  1077. if(this.reader.onMetaChange){
  1078. this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this);
  1079. }
  1080. if (this.writer) { // writer passed
  1081. if (this.writer instanceof(Ext.data.DataWriter) === false) { // <-- config-object instead of instance.
  1082. this.writer = this.buildWriter(this.writer);
  1083. }
  1084. this.writer.meta = this.reader.meta;
  1085. this.pruneModifiedRecords = true;
  1086. }
  1087. }
  1088. /**
  1089. * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
  1090. * {@link Ext.data.DataReader Reader}. Read-only.
  1091. * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
  1092. * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
  1093. * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
  1094. * <p>This property may be used to create new Records of the type held in this Store, for example:</p><pre><code>
  1095. // create the data store
  1096. var store = new Ext.data.ArrayStore({
  1097. autoDestroy: true,
  1098. fields: [
  1099. {name: 'company'},
  1100. {name: 'price', type: 'float'},
  1101. {name: 'change', type: 'float'},
  1102. {name: 'pctChange', type: 'float'},
  1103. {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
  1104. ]
  1105. });
  1106. store.loadData(myData);
  1107. // create the Grid
  1108. var grid = new Ext.grid.EditorGridPanel({
  1109. store: store,
  1110. colModel: new Ext.grid.ColumnModel({
  1111. columns: [
  1112. {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
  1113. {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
  1114. {header: 'Change', renderer: change, dataIndex: 'change'},
  1115. {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
  1116. {header: 'Last Updated', width: 85,
  1117. renderer: Ext.util.Format.dateRenderer('m/d/Y'),
  1118. dataIndex: 'lastChange'}
  1119. ],
  1120. defaults: {
  1121. sortable: true,
  1122. width: 75
  1123. }
  1124. }),
  1125. autoExpandColumn: 'company', // match the id specified in the column model
  1126. height:350,
  1127. width:600,
  1128. title:'Array Grid',
  1129. tbar: [{
  1130. text: 'Add Record',
  1131. handler : function(){
  1132. var defaultData = {
  1133. change: 0,
  1134. company: 'New Company',
  1135. lastChange: (new Date()).clearTime(),
  1136. pctChange: 0,
  1137. price: 10
  1138. };
  1139. var recId = 3; // provide unique id
  1140. var p = new store.recordType(defaultData, recId); // create new record
  1141. grid.stopEditing();
  1142. store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
  1143. grid.startEditing(0, 0);
  1144. }
  1145. }]
  1146. });
  1147. * </code></pre>
  1148. * @property recordType
  1149. * @type Function
  1150. */
  1151. if(this.recordType){
  1152. /**
  1153. * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
  1154. * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
  1155. * @property fields
  1156. * @type Ext.util.MixedCollection
  1157. */
  1158. this.fields = this.recordType.prototype.fields;
  1159. }
  1160. this.modified = [];
  1161. this.addEvents(
  1162. /**
  1163. * @event datachanged
  1164. * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
  1165. * widget that is using this Store as a Record cache should refresh its view.
  1166. * @param {Store} this
  1167. */
  1168. 'datachanged',
  1169. /**
  1170. * @event metachange
  1171. * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
  1172. * @param {Store} this
  1173. * @param {Object} meta The JSON metadata
  1174. */
  1175. 'metachange',
  1176. /**
  1177. * @event add
  1178. * Fires when Records have been {@link #add}ed to the Store
  1179. * @param {Store} this
  1180. * @param {Ext.data.Record[]} records The array of Records added
  1181. * @param {Number} index The index at which the record(s) were added
  1182. */
  1183. 'add',
  1184. /**
  1185. * @event remove
  1186. * Fires when a Record has been {@link #remove}d from the Store
  1187. * @param {Store} this
  1188. * @param {Ext.data.Record} record The Record that was removed
  1189. * @param {Number} index The index at which the record was removed
  1190. */
  1191. 'remove',
  1192. /**
  1193. * @event update
  1194. * Fires when a Record has been updated
  1195. * @param {Store} this
  1196. * @param {Ext.data.Record} record The Record that was updated
  1197. * @param {String} operation The update operation being performed. Value may be one of:
  1198. * <pre><code>
  1199. Ext.data.Record.EDIT
  1200. Ext.data.Record.REJECT
  1201. Ext.data.Record.COMMIT
  1202. * </code></pre>
  1203. */
  1204. 'update',
  1205. /**
  1206. * @event clear
  1207. * Fires when the data cache has been cleared.
  1208. * @param {Store} this
  1209. * @param {Record[]} The records that were cleared.
  1210. */
  1211. 'clear',
  1212. /**
  1213. * @event exception
  1214. * <p>Fires if an exception occurs in the Proxy during a remote request.
  1215. * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
  1216. * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
  1217. * for additional details.
  1218. * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
  1219. * for description.
  1220. */
  1221. 'exception',
  1222. /**
  1223. * @event beforeload
  1224. * Fires before a request is made for a new data object. If the beforeload handler returns
  1225. * <tt>false</tt> the {@link #load} action will be canceled.
  1226. * @param {Store} this
  1227. * @param {Object} options The loading options that were specified (see {@link #load} for details)
  1228. */
  1229. 'beforeload',
  1230. /**
  1231. * @event load
  1232. * Fires after a new set of Records has been loaded.
  1233. * @param {Store} this
  1234. * @param {Ext.data.Record[]} records The Records that were loaded
  1235. * @param {Object} options The loading options that were specified (see {@link #load} for details)
  1236. */
  1237. 'load',
  1238. /**
  1239. * @event loadexception
  1240. * <p>This event is <b>deprecated</b> in favor of the catch-all <b><code>{@link #exception}</code></b>
  1241. * event instead.</p>
  1242. * <p>This event is relayed through the corresponding {@link Ext.data.DataProxy}.
  1243. * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
  1244. * for additional details.
  1245. * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
  1246. * for description.
  1247. */
  1248. 'loadexception',
  1249. /**
  1250. * @event beforewrite
  1251. * @param {Ext.data.Store} store
  1252. * @param {String} action [Ext.data.Api.actions.create|update|destroy]
  1253. * @param {Record/Record[]} rs The Record(s) being written.
  1254. * @param {Object} options The loading options that were specified. Edit <code>options.params</code> to add Http parameters to the request. (see {@link #save} for details)
  1255. * @param {Object} arg The callback's arg object passed to the {@link #request} function
  1256. */
  1257. 'beforewrite',
  1258. /**
  1259. * @event write
  1260. * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
  1261. * Success of the action is determined in the <code>result['successProperty']</code>property (<b>NOTE</b> for RESTful stores,
  1262. * a simple 20x response is sufficient for the actions "destroy" and "update". The "create" action should should return 200 along with a database pk).
  1263. * @param {Ext.data.Store} store
  1264. * @param {String} action [Ext.data.Api.actions.create|update|destroy]
  1265. * @param {Object} result The 'data' picked-out out of the response for convenience.
  1266. * @param {Ext.Direct.Transaction} res
  1267. * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
  1268. */
  1269. 'write',
  1270. /**
  1271. * @event beforesave
  1272. * Fires before a save action is called. A save encompasses destroying records, updating records and creating records.
  1273. * @param {Ext.data.Store} store
  1274. * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action,
  1275. * with an array of records for each action.
  1276. */
  1277. 'beforesave',
  1278. /**
  1279. * @event save
  1280. * Fires after a save is completed. A save encompasses destroying records, updating records and creating records.
  1281. * @param {Ext.data.Store} store
  1282. * @param {Number} batch The identifier for the batch that was saved.
  1283. * @param {Object} data An object containing the data that is to be saved. The object will contain a key for each appropriate action,
  1284. * with an array of records for each action.
  1285. */
  1286. 'save'
  1287. );
  1288. if(this.proxy){
  1289. // TODO remove deprecated loadexception with ext-3.0.1
  1290. this.relayEvents(this.proxy, ['loadexception', 'exception']);
  1291. }
  1292. // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
  1293. if (this.writer) {
  1294. this.on({
  1295. scope: this,
  1296. add: this.createRecords,
  1297. remove: this.destroyRecord,
  1298. update: this.updateRecord,
  1299. clear: this.onClear
  1300. });
  1301. }
  1302. this.sortToggle = {};
  1303. if(this.sortField){
  1304. this.setDefaultSort(this.sortField, this.sortDir);
  1305. }else if(this.sortInfo){
  1306. this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
  1307. }
  1308. Ext.data.Store.superclass.constructor.call(this);
  1309. if(this.id){
  1310. this.storeId = this.id;
  1311. delete this.id;
  1312. }
  1313. if(this.storeId){
  1314. Ext.StoreMgr.register(this);
  1315. }
  1316. if(this.inlineData){
  1317. this.loadData(this.inlineData);
  1318. delete this.inlineData;
  1319. }else if(this.autoLoad){
  1320. this.load.defer(10, this, [
  1321. typeof this.autoLoad == 'object' ?
  1322. this.autoLoad : undefined]);
  1323. }
  1324. // used internally to uniquely identify a batch
  1325. this.batchCounter = 0;
  1326. this.batches = {};
  1327. },
  1328. /**
  1329. * builds a DataWriter instance when Store constructor is provided with a writer config-object instead of an instace.
  1330. * @param {Object} config Writer configuration
  1331. * @return {Ext.data.DataWriter}
  1332. * @private
  1333. */
  1334. buildWriter : function(config) {
  1335. var klass = undefined,
  1336. type = (config.format || 'json').toLowerCase();
  1337. switch (type) {
  1338. case 'json':
  1339. klass = Ext.data.JsonWriter;
  1340. break;
  1341. case 'xml':
  1342. klass = Ext.data.XmlWriter;
  1343. break;
  1344. default:
  1345. klass = Ext.data.JsonWriter;
  1346. }
  1347. return new klass(config);
  1348. },
  1349. /**
  1350. * Destroys the store.
  1351. */
  1352. destroy : function(){
  1353. if(!this.isDestroyed){
  1354. if(this.storeId){
  1355. Ext.StoreMgr.unregister(this);
  1356. }
  1357. this.clearData();
  1358. this.data = null;
  1359. Ext.destroy(this.proxy);
  1360. this.reader = this.writer = null;
  1361. this.purgeListeners();
  1362. this.isDestroyed = true;
  1363. }
  1364. },
  1365. /**
  1366. * Add Records to the Store and fires the {@link #add} event. To add Records
  1367. * to the store from a remote source use <code>{@link #load}({add:true})</code>.
  1368. * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
  1369. * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
  1370. * to add to the cache. See {@link #recordType}.
  1371. */
  1372. add : function(records){
  1373. records = [].concat(records);
  1374. if(records.length < 1){
  1375. return;
  1376. }
  1377. for(var i = 0, len = records.length; i < len; i++){
  1378. records[i].join(this);
  1379. }
  1380. var index = this.data.length;
  1381. this.data.addAll(records);
  1382. if(this.snapshot){
  1383. this.snapshot.addAll(records);
  1384. }
  1385. this.fireEvent('add', this, records, index);
  1386. },
  1387. /**
  1388. * (Local sort only) Inserts the passed Record into the Store at the index where it
  1389. * should go based on the current sort information.
  1390. * @param {Ext.data.Record} record
  1391. */
  1392. addSorted : function(record){
  1393. var index = this.findInsertIndex(record);
  1394. this.insert(index, record);
  1395. },
  1396. /**
  1397. * Remove Records from the Store and fires the {@link #remove} event.
  1398. * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache.
  1399. */
  1400. remove : function(record){
  1401. if(Ext.isArray(record)){
  1402. Ext.each(record, function(r){
  1403. this.remove(r);
  1404. }, this);
  1405. }
  1406. var index = this.data.indexOf(record);
  1407. if(index > -1){
  1408. record.join(null);
  1409. this.data.removeAt(index);
  1410. }
  1411. if(this.pruneModifiedRecords){
  1412. this.modified.remove(record);
  1413. }
  1414. if(this.snapshot){
  1415. this.snapshot.remove(record);
  1416. }
  1417. if(index > -1){
  1418. this.fireEvent('remove', this, record, index);
  1419. }
  1420. },
  1421. /**
  1422. * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
  1423. * @param {Number} index The index of the record to remove.
  1424. */
  1425. removeAt : function(index){
  1426. this.remove(this.getAt(index));
  1427. },
  1428. /**
  1429. * Remove all Records from the Store and fires the {@link #clear} event.
  1430. * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire clear event.
  1431. */
  1432. removeAll : function(silent){
  1433. var items = [];
  1434. this.each(function(rec){
  1435. items.push(rec);
  1436. });
  1437. this.clearData();
  1438. if(this.snapshot){
  1439. this.snapshot.clear();
  1440. }
  1441. if(this.pruneModifiedRecords){
  1442. this.modified = [];
  1443. }
  1444. if (silent !== true) { // <-- prevents write-actions when we just want to clear a store.
  1445. this.fireEvent('clear', this, items);
  1446. }
  1447. },
  1448. // private
  1449. onClear: function(store, records){
  1450. Ext.each(records, function(rec, index){
  1451. this.destroyRecord(this, rec, index);
  1452. }, this);
  1453. },
  1454. /**
  1455. * Inserts Records into the Store at the given index and fires the {@link #add} event.
  1456. * See also <code>{@link #add}</code> and <code>{@link #addSorted}</code>.
  1457. * @param {Number} index The start index at which to insert the passed Records.
  1458. * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
  1459. */
  1460. insert : function(index, records){
  1461. records = [].concat(records);
  1462. for(var i = 0, len = records.length; i < len; i++){
  1463. this.data.insert(index, records[i]);
  1464. records[i].join(this);
  1465. }
  1466. if(this.snapshot){
  1467. this.snapshot.addAll(records);
  1468. }
  1469. this.fireEvent('add', this, records, index);
  1470. },
  1471. /**
  1472. * Get the index within the cache of the passed Record.
  1473. * @param {Ext.data.Record} record The Ext.data.Record object to find.
  1474. * @return {Number} The index of the passed Record. Returns -1 if not found.
  1475. */
  1476. indexOf : function(record){
  1477. return this.data.indexOf(record);
  1478. },
  1479. /**
  1480. * Get the index within the cache of the Record with the passed id.
  1481. * @param {String} id The id of the Record to find.
  1482. * @return {Number} The index of the Record. Returns -1 if not found.
  1483. */
  1484. indexOfId : function(id){
  1485. return this.data.indexOfKey(id);
  1486. },
  1487. /**
  1488. * Get the Record with the specified id.
  1489. * @param {String} id The id of the Record to find.
  1490. * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
  1491. */
  1492. getById : function(id){
  1493. return (this.snapshot || this.data).key(id);
  1494. },
  1495. /**
  1496. * Get the Record at the specified index.
  1497. * @param {Number} index The index of the Record to find.
  1498. * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
  1499. */
  1500. getAt : function(index){
  1501. return this.data.itemAt(index);
  1502. },
  1503. /**
  1504. * Returns a range of Records between specified indices.
  1505. * @param {Number} startIndex (optional) The starting index (defaults to 0)
  1506. * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
  1507. * @return {Ext.data.Record[]} An array of Records
  1508. */
  1509. getRange : function(start, end){
  1510. return this.data.getRange(start, end);
  1511. },
  1512. // private
  1513. storeOptions : function(o){
  1514. o = Ext.apply({}, o);
  1515. delete o.callback;
  1516. delete o.scope;
  1517. this.lastOptions = o;
  1518. },
  1519. // private
  1520. clearData: function(){
  1521. this.data.each(function(rec) {
  1522. rec.join(null);
  1523. });
  1524. this.data.clear();
  1525. },
  1526. /**
  1527. * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
  1528. * <br><p>Notes:</p><div class="mdetail-params"><ul>
  1529. * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
  1530. * loaded. To perform any post-processing where information from the load call is required, specify
  1531. * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
  1532. * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
  1533. * properties in the <code>options.params</code> property to establish the initial position within the
  1534. * dataset, and the number of Records to cache on each read from the Proxy.</li>
  1535. * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
  1536. * will be automatically included with the posted parameters according to the specified
  1537. * <code>{@link #paramNames}</code>.</li>
  1538. * </ul></div>
  1539. * @param {Object} options An object containing properties which control loading options:<ul>
  1540. * <li><b><tt>params</tt></b> :Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
  1541. * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
  1542. * <code>{@link #baseParams}</code> of the same name.</p>
  1543. * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
  1544. * <li><b>callback</b> : Function<div class="sub-desc"><p>A function to be called after the Records
  1545. * have been loaded. The callback is called after the load event is fired, and is passed the following arguments:<ul>
  1546. * <li>r : Ext.data.Record[] An Array of Records loaded.</li>
  1547. * <li>options : Options object from the load call.</li>
  1548. * <li>success : Boolean success indicator.</li></ul></p></div></li>
  1549. * <li><b>scope</b> : Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
  1550. * to the Store object)</p></div></li>
  1551. * <li><b>add</b> : Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
  1552. * replace the current cache. <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
  1553. * </ul>
  1554. * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
  1555. * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
  1556. */
  1557. load : function(options) {
  1558. options = Ext.apply({}, options);
  1559. this.storeOptions(options);
  1560. if(this.sortInfo && this.remoteSort){
  1561. var pn = this.paramNames;
  1562. options.params = Ext.apply({}, options.params);
  1563. options.params[pn.sort] = this.sortInfo.field;
  1564. options.params[pn.dir] = this.sortInfo.direction;
  1565. }
  1566. try {
  1567. return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
  1568. } catch(e) {
  1569. this.handleException(e);
  1570. return false;
  1571. }
  1572. },
  1573. /**
  1574. * updateRecord Should not be used directly. This method will be called automatically if a Writer is set.
  1575. * Listens to 'update' event.
  1576. * @param {Object} store
  1577. * @param {Object} record
  1578. * @param {Object} action
  1579. * @private
  1580. */
  1581. updateRecord : function(store, record, action) {
  1582. if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid()))) {
  1583. this.save();
  1584. }
  1585. },
  1586. /**
  1587. * Should not be used directly. Store#add will call this automatically if a Writer is set
  1588. * @param {Object} store
  1589. * @param {Object} rs
  1590. * @param {Object} index
  1591. * @private
  1592. */
  1593. createRecords : function(store, rs, index) {
  1594. for (var i = 0, len = rs.length; i < len; i++) {
  1595. if (rs[i].phantom && rs[i].isValid()) {
  1596. rs[i].markDirty(); // <-- Mark new records dirty
  1597. this.modified.push(rs[i]); // <-- add to modified
  1598. }
  1599. }
  1600. if (this.autoSave === true) {
  1601. this.save();
  1602. }
  1603. },
  1604. /**
  1605. * Destroys a Record. Should not be used directly. It's called by Store#remove if a Writer is set.
  1606. * @param {Store} store this
  1607. * @param {Ext.data.Record} record
  1608. * @param {Number} index
  1609. * @private
  1610. */
  1611. destroyRecord : function(store, record, index) {
  1612. if (this.modified.indexOf(record) != -1) { // <-- handled already if @cfg pruneModifiedRecords == true
  1613. this.modified.remove(record);
  1614. }
  1615. if (!record.phantom) {
  1616. this.removed.push(record);
  1617. // since the record has already been removed from the store but the server request has not yet been executed,
  1618. // must keep track of the last known index this record existed. If a server error occurs, the record can be
  1619. // put back into the store. @see Store#createCallback where the record is returned when response status === false
  1620. record.lastIndex = index;
  1621. if (this.autoSave === true) {
  1622. this.save();
  1623. }
  1624. }
  1625. },
  1626. /**
  1627. * This method should generally not be used directly. This method is called internally
  1628. * by {@link #load}, or if a Writer is set will be called automatically when {@link #add},
  1629. * {@link #remove}, or {@link #update} events fire.
  1630. * @param {String} action Action name ('read', 'create', 'update', or 'destroy')
  1631. * @param {Record/Record[]} rs
  1632. * @param {Object} options
  1633. * @throws Error
  1634. * @private
  1635. */
  1636. execute : function(action, rs, options, /* private */ batch) {
  1637. // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY
  1638. if (!Ext.data.Api.isAction(action)) {
  1639. throw new Ext.data.Api.Error('execute', action);
  1640. }
  1641. // make sure options has a fresh, new params hash
  1642. options = Ext.applyIf(options||{}, {
  1643. params: {}
  1644. });
  1645. if(batch !== undefined){
  1646. this.addToBatch(batch);
  1647. }
  1648. // have to separate before-events since load has a different signature than create,destroy and save events since load does not
  1649. // include the rs (record resultset) parameter. Capture return values from the beforeaction into doRequest flag.
  1650. var doRequest = true;
  1651. if (action === 'read') {
  1652. doRequest = this.fireEvent('beforeload', this, options);
  1653. Ext.applyIf(options.params, this.baseParams);
  1654. }
  1655. else {
  1656. // if Writer is configured as listful, force single-record rs to be [{}] instead of {}
  1657. // TODO Move listful rendering into DataWriter where the @cfg is defined. Should be easy now.
  1658. if (this.writer.listful === true && this.restful !== true) {
  1659. rs = (Ext.isArray(rs)) ? rs : [rs];
  1660. }
  1661. // if rs has just a single record, shift it off so that Writer writes data as '{}' rather than '[{}]'
  1662. else if (Ext.isArray(rs) && rs.length == 1) {
  1663. rs = rs.shift();
  1664. }
  1665. // Write the action to options.params
  1666. if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
  1667. this.writer.apply(options.params, this.baseParams, action, rs);
  1668. }
  1669. }
  1670. if (doRequest !== false) {
  1671. // Send request to proxy.
  1672. if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
  1673. options.params.xaction = action; // <-- really old, probaby unecessary.
  1674. }
  1675. // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions.
  1676. // We'll flip it now and send the value into DataProxy#request, since it's the value which maps to
  1677. // the user's configured DataProxy#api
  1678. // TODO Refactor all Proxies to accept an instance of Ext.data.Request (not yet defined) instead of this looooooong list
  1679. // of params. This method is an artifact from Ext2.
  1680. this.proxy.request(Ext.data.Api.actions[action], rs, options.params, this.reader, this.createCallback(action, rs, batch), this, options);
  1681. }
  1682. return doRequest;
  1683. },
  1684. /**
  1685. * Saves all pending changes to the store. If the commensurate Ext.data.Api.actions action is not configured, then
  1686. * the configured <code>{@link #url}</code> will be used.
  1687. * <pre>
  1688. * change url
  1689. * --------------- --------------------
  1690. * removed records Ext.data.Api.actions.destroy
  1691. * phantom records Ext.data.Api.actions.create
  1692. * {@link #getModifiedRecords modified records} Ext.data.Api.actions.update
  1693. * </pre>
  1694. * @TODO: Create extensions of Error class and send associated Record with thrown exceptions.
  1695. * e.g.: Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
  1696. * @return {Number} batch Returns a number to uniquely identify the "batch" of saves occurring. -1 will be returned
  1697. * if there are no items to save or the save was cancelled.
  1698. */
  1699. save : function() {
  1700. if (!this.writer) {
  1701. throw new Ext.data.Store.Error('writer-undefined');
  1702. }
  1703. var queue = [],
  1704. len,
  1705. trans,
  1706. batch,
  1707. data = {};
  1708. // DESTROY: First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
  1709. if(this.removed.length){
  1710. queue.push(['destroy', this.removed]);
  1711. }
  1712. // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error.
  1713. var rs = [].concat(this.getModifiedRecords());
  1714. if(rs.length){
  1715. // CREATE: Next check for phantoms within rs. splice-off and execute create.
  1716. var phantoms = [];
  1717. for(var i = rs.length-1; i >= 0; i--){
  1718. if(rs[i].phantom === true){
  1719. var rec = rs.splice(i, 1).shift();
  1720. if(rec.isValid()){
  1721. phantoms.push(rec);
  1722. }
  1723. }else if(!rs[i].isValid()){ // <-- while we're here, splice-off any !isValid real records
  1724. rs.splice(i,1);
  1725. }
  1726. }
  1727. // If we have valid phantoms, create them...
  1728. if(phantoms.length){
  1729. queue.push(['create', phantoms]);
  1730. }
  1731. // UPDATE: And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
  1732. if(rs.length){
  1733. queue.push(['update', rs]);
  1734. }
  1735. }
  1736. len = queue.length;
  1737. if(len){
  1738. batch = ++this.batchCounter;
  1739. for(var i = 0; i < len; ++i){
  1740. trans = queue[i];
  1741. data[trans[0]] = trans[1];
  1742. }
  1743. if(this.fireEvent('beforesave', this, data) !== false){
  1744. for(var i = 0; i < len; ++i){
  1745. trans = queue[i];
  1746. this.doTransaction(trans[0], trans[1], batch);
  1747. }
  1748. return batch;
  1749. }
  1750. }
  1751. return -1;
  1752. },
  1753. // private. Simply wraps call to Store#execute in try/catch. Defers to Store#handleException on error. Loops if batch: false
  1754. doTransaction : function(action, rs, batch) {
  1755. function transaction(records) {
  1756. try{
  1757. this.execute(action, records, undefined, batch);
  1758. }catch (e){
  1759. this.handleException(e);
  1760. }
  1761. }
  1762. if(this.batch === false){
  1763. for(var i = 0, len = rs.length; i < len; i++){
  1764. transaction.call(this, rs[i]);
  1765. }
  1766. }else{
  1767. transaction.call(this, rs);
  1768. }
  1769. },
  1770. // private
  1771. addToBatch : function(batch){
  1772. var b = this.batches,
  1773. key = this.batchKey + batch,
  1774. o = b[key];
  1775. if(!o){
  1776. b[key] = o = {
  1777. id: batch,
  1778. count: 0,
  1779. data: {}
  1780. };
  1781. }
  1782. ++o.count;
  1783. },
  1784. removeFromBatch : function(batch, action, data){
  1785. var b = this.batches,
  1786. key = this.batchKey + batch,
  1787. o = b[key],
  1788. data,
  1789. arr;
  1790. if(o){
  1791. arr = o.data[action] || [];
  1792. o.data[action] = arr.concat(data);
  1793. if(o.count === 1){
  1794. data = o.data;
  1795. delete b[key];
  1796. this.fireEvent('save', this, batch, data);
  1797. }else{
  1798. --o.count;
  1799. }
  1800. }
  1801. },
  1802. // @private callback-handler for remote CRUD actions
  1803. // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
  1804. createCallback : function(action, rs, batch) {
  1805. var actions = Ext.data.Api.actions;
  1806. return (action == 'read') ? this.loadRecords : function(data, response, success) {
  1807. // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
  1808. this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data));
  1809. // If success === false here, exception will have been called in DataProxy
  1810. if (success === true) {
  1811. this.fireEvent('write', this, action, data, response, rs);
  1812. }
  1813. this.removeFromBatch(batch, action, data);
  1814. };
  1815. },
  1816. // Clears records from modified array after an exception event.
  1817. // NOTE: records are left marked dirty. Do we want to commit them even though they were not updated/realized?
  1818. // TODO remove this method?
  1819. clearModified : function(rs) {
  1820. if (Ext.isArray(rs)) {
  1821. for (var n=rs.length-1;n>=0;n--) {
  1822. this.modified.splice(this.modified.indexOf(rs[n]), 1);
  1823. }
  1824. } else {
  1825. this.modified.splice(this.modified.indexOf(rs), 1);
  1826. }
  1827. },
  1828. // remap record ids in MixedCollection after records have been realized. @see Store#onCreateRecords, @see DataReader#realize
  1829. reMap : function(record) {
  1830. if (Ext.isArray(record)) {
  1831. for (var i = 0, len = record.length; i < len; i++) {
  1832. this.reMap(record[i]);
  1833. }
  1834. } else {
  1835. delete this.data.map[record._phid];
  1836. this.data.map[record.id] = record;
  1837. var index = this.data.keys.indexOf(record._phid);
  1838. this.data.keys.splice(index, 1, record.id);
  1839. delete record._phid;
  1840. }
  1841. },
  1842. // @protected onCreateRecord proxy callback for create action
  1843. onCreateRecords : function(success, rs, data) {
  1844. if (success === true) {
  1845. try {
  1846. this.reader.realize(rs, data);
  1847. this.reMap(rs);
  1848. }
  1849. catch (e) {
  1850. this.handleException(e);
  1851. if (Ext.isArray(rs)) {
  1852. // Recurse to run back into the try {}. DataReader#realize splices-off the rs until empty.
  1853. this.onCreateRecords(success, rs, data);
  1854. }
  1855. }
  1856. }
  1857. },
  1858. // @protected, onUpdateRecords proxy callback for update action
  1859. onUpdateRecords : function(success, rs, data) {
  1860. if (success === true) {
  1861. try {
  1862. this.reader.update(rs, data);
  1863. } catch (e) {
  1864. this.handleException(e);
  1865. if (Ext.isArray(rs)) {
  1866. // Recurse to run back into the try {}. DataReader#update splices-off the rs until empty.
  1867. this.onUpdateRecords(success, rs, data);
  1868. }
  1869. }
  1870. }
  1871. },
  1872. // @protected onDestroyRecords proxy callback for destroy action
  1873. onDestroyRecords : function(success, rs, data) {
  1874. // splice each rec out of this.removed
  1875. rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs);
  1876. for (var i=0,len=rs.length;i<len;i++) {
  1877. this.removed.splice(this.removed.indexOf(rs[i]), 1);
  1878. }
  1879. if (success === false) {
  1880. // put records back into store if remote destroy fails.
  1881. // @TODO: Might want to let developer decide.
  1882. for (i=rs.length-1;i>=0;i--) {
  1883. this.insert(rs[i].lastIndex, rs[i]); // <-- lastIndex set in Store#destroyRecord
  1884. }
  1885. }
  1886. },
  1887. // protected handleException. Possibly temporary until Ext framework has an exception-handler.
  1888. handleException : function(e) {
  1889. // @see core/Error.js
  1890. Ext.handleError(e);
  1891. },
  1892. /**
  1893. * <p>Reloads the Record cache from the configured Proxy using the configured
  1894. * {@link Ext.data.Reader Reader} and the options from the last load operation
  1895. * performed.</p>
  1896. * <p><b>Note</b>: see the Important note in {@link #load}.</p>
  1897. * @param {Object} options <p>(optional) An <tt>Object</tt> containing
  1898. * {@link #load loading options} which may override the {@link #lastOptions options}
  1899. * used in the last {@link #load} operation. See {@link #load} for details
  1900. * (defaults to <tt>null</tt>, in which case the {@link #lastOptions} are
  1901. * used).</p>
  1902. * <br><p>To add new params to the existing params:</p><pre><code>
  1903. lastOptions = myStore.lastOptions;
  1904. Ext.apply(lastOptions.params, {
  1905. myNewParam: true
  1906. });
  1907. myStore.reload(lastOptions);
  1908. * </code></pre>
  1909. */
  1910. reload : function(options){
  1911. this.load(Ext.applyIf(options||{}, this.lastOptions));
  1912. },
  1913. // private
  1914. // Called as a callback by the Reader during a load operation.
  1915. loadRecords : function(o, options, success){
  1916. if (this.isDestroyed === true) {
  1917. return;
  1918. }
  1919. if(!o || success === false){
  1920. if(success !== false){
  1921. this.fireEvent('load', this, [], options);
  1922. }
  1923. if(options.callback){
  1924. options.callback.call(options.scope || this, [], options, false, o);
  1925. }
  1926. return;
  1927. }
  1928. var r = o.records, t = o.totalRecords || r.length;
  1929. if(!options || options.add !== true){
  1930. if(this.pruneModifiedRecords){
  1931. this.modified = [];
  1932. }
  1933. for(var i = 0, len = r.length; i < len; i++){
  1934. r[i].join(this);
  1935. }
  1936. if(this.snapshot){
  1937. this.data = this.snapshot;
  1938. delete this.snapshot;
  1939. }
  1940. this.clearData();
  1941. this.data.addAll(r);
  1942. this.totalLength = t;
  1943. this.applySort();
  1944. this.fireEvent('datachanged', this);
  1945. }else{
  1946. this.totalLength = Math.max(t, this.data.length+r.length);
  1947. this.add(r);
  1948. }
  1949. this.fireEvent('load', this, r, options);
  1950. if(options.callback){
  1951. options.callback.call(options.scope || this, r, options, true);
  1952. }
  1953. },
  1954. /**
  1955. * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
  1956. * which understands the format of the data must have been configured in the constructor.
  1957. * @param {Object} data The data block from which to read the Records. The format of the data expected
  1958. * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
  1959. * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
  1960. * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
  1961. * the existing cache.
  1962. * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
  1963. * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
  1964. * new, unique ids will be added.
  1965. */
  1966. loadData : function(o, append){
  1967. var r = this.reader.readRecords(o);
  1968. this.loadRecords(r, {add: append}, true);
  1969. },
  1970. /**
  1971. * Gets the number of cached records.
  1972. * <p>If using paging, this may not be the total size of the dataset. If the data object
  1973. * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
  1974. * the dataset size. <b>Note</b>: see the Important note in {@link #load}.</p>
  1975. * @return {Number} The number of Records in the Store's cache.
  1976. */
  1977. getCount : function(){
  1978. return this.data.length || 0;
  1979. },
  1980. /**
  1981. * Gets the total number of records in the dataset as returned by the server.
  1982. * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
  1983. * must contain the dataset size. For remote data sources, the value for this property
  1984. * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
  1985. * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
  1986. * <b>Note</b>: see the Important note in {@link #load}.</p>
  1987. * @return {Number} The number of Records as specified in the data object passed to the Reader
  1988. * by the Proxy.
  1989. * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
  1990. */
  1991. getTotalCount : function(){
  1992. return this.totalLength || 0;
  1993. },
  1994. /**
  1995. * Returns an object describing the current sort state of this Store.
  1996. * @return {Object} The sort state of the Store. An object with two properties:<ul>
  1997. * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
  1998. * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
  1999. * </ul>
  2000. * See <tt>{@link #sortInfo}</tt> for additional details.
  2001. */
  2002. getSortState : function(){
  2003. return this.sortInfo;
  2004. },
  2005. /**
  2006. * @private
  2007. * Invokes sortData if we have sortInfo to sort on and are not sorting remotely
  2008. */
  2009. applySort : function(){
  2010. if ((this.sortInfo || this.multiSortInfo) && !this.remoteSort) {
  2011. this.sortData();
  2012. }
  2013. },
  2014. /**
  2015. * @private
  2016. * Performs the actual sorting of data. This checks to see if we currently have a multi sort or not. It applies
  2017. * each sorter field/direction pair in turn by building an OR'ed master sorting function and running it against
  2018. * the full dataset
  2019. */
  2020. sortData : function() {
  2021. var sortInfo = this.hasMultiSort ? this.multiSortInfo : this.sortInfo,
  2022. direction = sortInfo.direction || "ASC",
  2023. sorters = sortInfo.sorters,
  2024. sortFns = [];
  2025. //if we just have a single sorter, pretend it's the first in an array
  2026. if (!this.hasMultiSort) {
  2027. sorters = [{direction: direction, field: sortInfo.field}];
  2028. }
  2029. //create a sorter function for each sorter field/direction combo
  2030. for (var i=0, j = sorters.length; i < j; i++) {
  2031. sortFns.push(this.createSortFunction(sorters[i].field, sorters[i].direction));
  2032. }
  2033. if (sortFns.length == 0) {
  2034. return;
  2035. }
  2036. //the direction modifier is multiplied with the result of the sorting functions to provide overall sort direction
  2037. //(as opposed to direction per field)
  2038. var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
  2039. //create a function which ORs each sorter together to enable multi-sort
  2040. var fn = function(r1, r2) {
  2041. var result = sortFns[0].call(this, r1, r2);
  2042. //if we have more than one sorter, OR any additional sorter functions together
  2043. if (sortFns.length > 1) {
  2044. for (var i=1, j = sortFns.length; i < j; i++) {
  2045. result = result || sortFns[i].call(this, r1, r2);
  2046. }
  2047. }
  2048. return directionModifier * result;
  2049. };
  2050. //sort the data
  2051. this.data.sort(direction, fn);
  2052. if (this.snapshot && this.snapshot != this.data) {
  2053. this.snapshot.sort(direction, fn);
  2054. }
  2055. },
  2056. /**
  2057. * Creates and returns a function which sorts an array by the given field and direction
  2058. * @param {String} field The field to create the sorter for
  2059. * @param {String} direction The direction to sort by (defaults to "ASC")
  2060. * @return {Function} A function which sorts by the field/direction combination provided
  2061. */
  2062. createSortFunction: function(field, direction) {
  2063. direction = direction || "ASC";
  2064. var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
  2065. var sortType = this.fields.get(field).sortType;
  2066. //create a comparison function. Takes 2 records, returns 1 if record 1 is greater,
  2067. //-1 if record 2 is greater or 0 if they are equal
  2068. return function(r1, r2) {
  2069. var v1 = sortType(r1.data[field]),
  2070. v2 = sortType(r2.data[field]);
  2071. return directionModifier * (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
  2072. };
  2073. },
  2074. /**
  2075. * Sets the default sort column and order to be used by the next {@link #load} operation.
  2076. * @param {String} fieldName The name of the field to sort by.
  2077. * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
  2078. */
  2079. setDefaultSort : function(field, dir) {
  2080. dir = dir ? dir.toUpperCase() : 'ASC';
  2081. this.sortInfo = {field: field, direction: dir};
  2082. this.sortToggle[field] = dir;
  2083. },
  2084. /**
  2085. * Sort the Records.
  2086. * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
  2087. * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
  2088. * This function accepts two call signatures - pass in a field name as the first argument to sort on a single
  2089. * field, or pass in an array of sort configuration objects to sort by multiple fields.
  2090. * Single sort example:
  2091. * store.sort('name', 'ASC');
  2092. * Multi sort example:
  2093. * store.sort([
  2094. * {
  2095. * field : 'name',
  2096. * direction: 'ASC'
  2097. * },
  2098. * {
  2099. * field : 'salary',
  2100. * direction: 'DESC'
  2101. * }
  2102. * ], 'ASC');
  2103. * In this second form, the sort configs are applied in order, with later sorters sorting within earlier sorters' results.
  2104. * For example, if two records with the same name are present they will also be sorted by salary if given the sort configs
  2105. * above. Any number of sort configs can be added.
  2106. * @param {String/Array} fieldName The name of the field to sort by, or an array of ordered sort configs
  2107. * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
  2108. */
  2109. sort : function(fieldName, dir) {
  2110. if (Ext.isArray(arguments[0])) {
  2111. return this.multiSort.call(this, fieldName, dir);
  2112. } else {
  2113. return this.singleSort(fieldName, dir);
  2114. }
  2115. },
  2116. /**
  2117. * Sorts the store contents by a single field and direction. This is called internally by {@link sort} and would
  2118. * not usually be called manually
  2119. * @param {String} fieldName The name of the field to sort by.
  2120. * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
  2121. */
  2122. singleSort: function(fieldName, dir) {
  2123. var field = this.fields.get(fieldName);
  2124. if (!field) return false;
  2125. var name = field.name,
  2126. sortInfo = this.sortInfo || null,
  2127. sortToggle = this.sortToggle ? this.sortToggle[name] : null;
  2128. if (!dir) {
  2129. if (sortInfo && sortInfo.field == name) { // toggle sort dir
  2130. dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
  2131. } else {
  2132. dir = field.sortDir;
  2133. }
  2134. }
  2135. this.sortToggle[name] = dir;
  2136. this.sortInfo = {field: name, direction: dir};
  2137. this.hasMultiSort = false;
  2138. if (this.remoteSort) {
  2139. if (!this.load(this.lastOptions)) {
  2140. if (sortToggle) {
  2141. this.sortToggle[name] = sortToggle;
  2142. }
  2143. if (sortInfo) {
  2144. this.sortInfo = sortInfo;
  2145. }
  2146. }
  2147. } else {
  2148. this.applySort();
  2149. this.fireEvent('datachanged', this);
  2150. }
  2151. },
  2152. /**
  2153. * Sorts the contents of this store by multiple field/direction sorters. This is called internally by {@link sort}
  2154. * and would not usually be called manually.
  2155. * Multi sorting only currently applies to local datasets - multiple sort data is not currently sent to a proxy
  2156. * if remoteSort is used.
  2157. * @param {Array} sorters Array of sorter objects (field and direction)
  2158. * @param {String} direction Overall direction to sort the ordered results by (defaults to "ASC")
  2159. */
  2160. multiSort: function(sorters, direction) {
  2161. this.hasMultiSort = true;
  2162. direction = direction || "ASC";
  2163. //toggle sort direction
  2164. if (this.multiSortInfo && direction == this.multiSortInfo.direction) {
  2165. direction = direction.toggle("ASC", "DESC");
  2166. }
  2167. /**
  2168. * @property multiSortInfo
  2169. * @type Object
  2170. * Object containing overall sort direction and an ordered array of sorter configs used when sorting on multiple fields
  2171. */
  2172. this.multiSortInfo = {
  2173. sorters : sorters,
  2174. direction: direction
  2175. };
  2176. if (this.remoteSort) {
  2177. this.singleSort(sorters[0].field, sorters[0].direction);
  2178. } else {
  2179. this.applySort();
  2180. this.fireEvent('datachanged', this);
  2181. }
  2182. },
  2183. /**
  2184. * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
  2185. * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
  2186. * Returning <tt>false</tt> aborts and exits the iteration.
  2187. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
  2188. * Defaults to the current {@link Ext.data.Record Record} in the iteration.
  2189. */
  2190. each : function(fn, scope){
  2191. this.data.each(fn, scope);
  2192. },
  2193. /**
  2194. * Gets all {@link Ext.data.Record records} modified since the last commit. Modified records are
  2195. * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
  2196. * included. See also <tt>{@link #pruneModifiedRecords}</tt> and
  2197. * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
  2198. * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
  2199. * modifications. To obtain modified fields within a modified record see
  2200. *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
  2201. */
  2202. getModifiedRecords : function(){
  2203. return this.modified;
  2204. },
  2205. /**
  2206. * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
  2207. * and <tt>end</tt> and returns the result.
  2208. * @param {String} property A field in each record
  2209. * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
  2210. * @param {Number} end (optional) The last record index to include (defaults to length - 1)
  2211. * @return {Number} The sum
  2212. */
  2213. sum : function(property, start, end){
  2214. var rs = this.data.items, v = 0;
  2215. start = start || 0;
  2216. end = (end || end === 0) ? end : rs.length-1;
  2217. for(var i = start; i <= end; i++){
  2218. v += (rs[i].data[property] || 0);
  2219. }
  2220. return v;
  2221. },
  2222. /**
  2223. * @private
  2224. * Returns a filter function used to test a the given property's value. Defers most of the work to
  2225. * Ext.util.MixedCollection's createValueMatcher function
  2226. * @param {String} property The property to create the filter function for
  2227. * @param {String/RegExp} value The string/regex to compare the property value to
  2228. * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
  2229. * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
  2230. * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
  2231. */
  2232. createFilterFn : function(property, value, anyMatch, caseSensitive, exactMatch){
  2233. if(Ext.isEmpty(value, false)){
  2234. return false;
  2235. }
  2236. value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
  2237. return function(r) {
  2238. return value.test(r.data[property]);
  2239. };
  2240. },
  2241. /**
  2242. * Given an array of filter functions (each with optional scope), constructs and returns a single function that returns
  2243. * the result of all of the filters ANDed together
  2244. * @param {Array} filters The array of filter objects (each object should contain an 'fn' and optional scope)
  2245. * @return {Function} The multiple filter function
  2246. */
  2247. createMultipleFilterFn: function(filters) {
  2248. return function(record) {
  2249. var isMatch = true;
  2250. for (var i=0, j = filters.length; i < j; i++) {
  2251. var filter = filters[i],
  2252. fn = filter.fn,
  2253. scope = filter.scope;
  2254. isMatch = isMatch && fn.call(scope, record);
  2255. }
  2256. return isMatch;
  2257. };
  2258. },
  2259. /**
  2260. * Filter the {@link Ext.data.Record records} by a specified property. Alternatively, pass an array of filter
  2261. * options to filter by more than one property.
  2262. * Single filter example:
  2263. * store.filter('name', 'Ed', true, true); //finds all records containing the substring 'Ed'
  2264. * Multiple filter example:
  2265. * store.filter([
  2266. * {
  2267. * property : 'name',
  2268. * value : 'Ed',
  2269. * anyMatch : true, //optional, defaults to true
  2270. * caseSensitive: true //optional, defaults to true
  2271. * },
  2272. *
  2273. * //filter functions can also be passed
  2274. * {
  2275. * fn : function(record) {
  2276. * return record.get('age') == 24
  2277. * },
  2278. * scope: this
  2279. * }
  2280. * ]);
  2281. * @param {String|Array} field A field on your records, or an array containing multiple filter options
  2282. * @param {String/RegExp} value Either a string that the field should begin with, or a RegExp to test
  2283. * against the field.
  2284. * @param {Boolean} anyMatch (optional) <tt>true</tt> to match any part not just the beginning
  2285. * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
  2286. * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
  2287. */
  2288. filter : function(property, value, anyMatch, caseSensitive, exactMatch){
  2289. //we can accept an array of filter objects, or a single filter object - normalize them here
  2290. if (Ext.isObject(property)) {
  2291. property = [property];
  2292. }
  2293. if (Ext.isArray(property)) {
  2294. var filters = [];
  2295. //normalize the filters passed into an array of filter functions
  2296. for (var i=0, j = property.length; i < j; i++) {
  2297. var filter = property[i],
  2298. func = filter.fn,
  2299. scope = filter.scope || this;
  2300. //if we weren't given a filter function, construct one now
  2301. if (!Ext.isFunction(func)) {
  2302. func = this.createFilterFn(filter.property, filter.value, filter.anyMatch, filter.caseSensitive, filter.exactMatch);
  2303. }
  2304. filters.push({fn: func, scope: scope});
  2305. }
  2306. var fn = this.createMultipleFilterFn(filters);
  2307. } else {
  2308. //classic single property filter
  2309. var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
  2310. }
  2311. return fn ? this.filterBy(fn) : this.clearFilter();
  2312. },
  2313. /**
  2314. * Filter by a function. The specified function will be called for each
  2315. * Record in this Store. If the function returns <tt>true</tt> the Record is included,
  2316. * otherwise it is filtered out.
  2317. * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
  2318. * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
  2319. * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
  2320. * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
  2321. * </ul>
  2322. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
  2323. */
  2324. filterBy : function(fn, scope){
  2325. this.snapshot = this.snapshot || this.data;
  2326. this.data = this.queryBy(fn, scope||this);
  2327. this.fireEvent('datachanged', this);
  2328. },
  2329. /**
  2330. * Revert to a view of the Record cache with no filtering applied.
  2331. * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
  2332. * {@link #datachanged} event.
  2333. */
  2334. clearFilter : function(suppressEvent){
  2335. if(this.isFiltered()){
  2336. this.data = this.snapshot;
  2337. delete this.snapshot;
  2338. if(suppressEvent !== true){
  2339. this.fireEvent('datachanged', this);
  2340. }
  2341. }
  2342. },
  2343. /**
  2344. * Returns true if this store is currently filtered
  2345. * @return {Boolean}
  2346. */
  2347. isFiltered : function(){
  2348. return !!this.snapshot && this.snapshot != this.data;
  2349. },
  2350. /**
  2351. * Query the records by a specified property.
  2352. * @param {String} field A field on your records
  2353. * @param {String/RegExp} value Either a string that the field
  2354. * should begin with, or a RegExp to test against the field.
  2355. * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
  2356. * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
  2357. * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  2358. */
  2359. query : function(property, value, anyMatch, caseSensitive){
  2360. var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
  2361. return fn ? this.queryBy(fn) : this.data.clone();
  2362. },
  2363. /**
  2364. * Query the cached records in this Store using a filtering function. The specified function
  2365. * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
  2366. * included in the results.
  2367. * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
  2368. * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
  2369. * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
  2370. * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
  2371. * </ul>
  2372. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
  2373. * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  2374. **/
  2375. queryBy : function(fn, scope){
  2376. var data = this.snapshot || this.data;
  2377. return data.filterBy(fn, scope||this);
  2378. },
  2379. /**
  2380. * Finds the index of the first matching Record in this store by a specific field value.
  2381. * @param {String} fieldName The name of the Record field to test.
  2382. * @param {String/RegExp} value Either a string that the field value
  2383. * should begin with, or a RegExp to test against the field.
  2384. * @param {Number} startIndex (optional) The index to start searching at
  2385. * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
  2386. * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
  2387. * @return {Number} The matched index or -1
  2388. */
  2389. find : function(property, value, start, anyMatch, caseSensitive){
  2390. var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
  2391. return fn ? this.data.findIndexBy(fn, null, start) : -1;
  2392. },
  2393. /**
  2394. * Finds the index of the first matching Record in this store by a specific field value.
  2395. * @param {String} fieldName The name of the Record field to test.
  2396. * @param {Mixed} value The value to match the field against.
  2397. * @param {Number} startIndex (optional) The index to start searching at
  2398. * @return {Number} The matched index or -1
  2399. */
  2400. findExact: function(property, value, start){
  2401. return this.data.findIndexBy(function(rec){
  2402. return rec.get(property) === value;
  2403. }, this, start);
  2404. },
  2405. /**
  2406. * Find the index of the first matching Record in this Store by a function.
  2407. * If the function returns <tt>true</tt> it is considered a match.
  2408. * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
  2409. * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
  2410. * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
  2411. * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
  2412. * </ul>
  2413. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
  2414. * @param {Number} startIndex (optional) The index to start searching at
  2415. * @return {Number} The matched index or -1
  2416. */
  2417. findBy : function(fn, scope, start){
  2418. return this.data.findIndexBy(fn, scope, start);
  2419. },
  2420. /**
  2421. * Collects unique values for a particular dataIndex from this store.
  2422. * @param {String} dataIndex The property to collect
  2423. * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
  2424. * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
  2425. * @return {Array} An array of the unique values
  2426. **/
  2427. collect : function(dataIndex, allowNull, bypassFilter){
  2428. var d = (bypassFilter === true && this.snapshot) ?
  2429. this.snapshot.items : this.data.items;
  2430. var v, sv, r = [], l = {};
  2431. for(var i = 0, len = d.length; i < len; i++){
  2432. v = d[i].data[dataIndex];
  2433. sv = String(v);
  2434. if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
  2435. l[sv] = true;
  2436. r[r.length] = v;
  2437. }
  2438. }
  2439. return r;
  2440. },
  2441. // private
  2442. afterEdit : function(record){
  2443. if(this.modified.indexOf(record) == -1){
  2444. this.modified.push(record);
  2445. }
  2446. this.fireEvent('update', this, record, Ext.data.Record.EDIT);
  2447. },
  2448. // private
  2449. afterReject : function(record){
  2450. this.modified.remove(record);
  2451. this.fireEvent('update', this, record, Ext.data.Record.REJECT);
  2452. },
  2453. // private
  2454. afterCommit : function(record){
  2455. this.modified.remove(record);
  2456. this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
  2457. },
  2458. /**
  2459. * Commit all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
  2460. * subscribe to the Store's {@link #update update event}, and perform updating when the third parameter is
  2461. * Ext.data.Record.COMMIT.
  2462. */
  2463. commitChanges : function(){
  2464. var m = this.modified.slice(0);
  2465. this.modified = [];
  2466. for(var i = 0, len = m.length; i < len; i++){
  2467. m[i].commit();
  2468. }
  2469. },
  2470. /**
  2471. * {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
  2472. */
  2473. rejectChanges : function(){
  2474. var m = this.modified.slice(0);
  2475. this.modified = [];
  2476. for(var i = 0, len = m.length; i < len; i++){
  2477. m[i].reject();
  2478. }
  2479. var m = this.removed.slice(0).reverse();
  2480. this.removed = [];
  2481. for(var i = 0, len = m.length; i < len; i++){
  2482. this.insert(m[i].lastIndex||0, m[i]);
  2483. m[i].reject();
  2484. }
  2485. },
  2486. // private
  2487. onMetaChange : function(meta){
  2488. this.recordType = this.reader.recordType;
  2489. this.fields = this.recordType.prototype.fields;
  2490. delete this.snapshot;
  2491. if(this.reader.meta.sortInfo){
  2492. this.sortInfo = this.reader.meta.sortInfo;
  2493. }else if(this.sortInfo && !this.fields.get(this.sortInfo.field)){
  2494. delete this.sortInfo;
  2495. }
  2496. if(this.writer){
  2497. this.writer.meta = this.reader.meta;
  2498. }
  2499. this.modified = [];
  2500. this.fireEvent('metachange', this, this.reader.meta);
  2501. },
  2502. // private
  2503. findInsertIndex : function(record){
  2504. this.suspendEvents();
  2505. var data = this.data.clone();
  2506. this.data.add(record);
  2507. this.applySort();
  2508. var index = this.data.indexOf(record);
  2509. this.data = data;
  2510. this.resumeEvents();
  2511. return index;
  2512. },
  2513. /**
  2514. * Set the value for a property name in this store's {@link #baseParams}. Usage:</p><pre><code>
  2515. myStore.setBaseParam('foo', {bar:3});
  2516. </code></pre>
  2517. * @param {String} name Name of the property to assign
  2518. * @param {Mixed} value Value to assign the <tt>name</tt>d property
  2519. **/
  2520. setBaseParam : function (name, value){
  2521. this.baseParams = this.baseParams || {};
  2522. this.baseParams[name] = value;
  2523. }
  2524. });
  2525. Ext.reg('store', Ext.data.Store);
  2526. /**
  2527. * @class Ext.data.Store.Error
  2528. * @extends Ext.Error
  2529. * Store Error extension.
  2530. * @param {String} name
  2531. */
  2532. Ext.data.Store.Error = Ext.extend(Ext.Error, {
  2533. name: 'Ext.data.Store'
  2534. });
  2535. Ext.apply(Ext.data.Store.Error.prototype, {
  2536. lang: {
  2537. 'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
  2538. }
  2539. });
  2540. /**
  2541. * @class Ext.data.Field
  2542. * <p>This class encapsulates the field definition information specified in the field definition objects
  2543. * passed to {@link Ext.data.Record#create}.</p>
  2544. * <p>Developers do not need to instantiate this class. Instances are created by {@link Ext.data.Record.create}
  2545. * and cached in the {@link Ext.data.Record#fields fields} property of the created Record constructor's <b>prototype.</b></p>
  2546. */
  2547. Ext.data.Field = Ext.extend(Object, {
  2548. constructor : function(config){
  2549. if(Ext.isString(config)){
  2550. config = {name: config};
  2551. }
  2552. Ext.apply(this, config);
  2553. var types = Ext.data.Types,
  2554. st = this.sortType,
  2555. t;
  2556. if(this.type){
  2557. if(Ext.isString(this.type)){
  2558. this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
  2559. }
  2560. }else{
  2561. this.type = types.AUTO;
  2562. }
  2563. // named sortTypes are supported, here we look them up
  2564. if(Ext.isString(st)){
  2565. this.sortType = Ext.data.SortTypes[st];
  2566. }else if(Ext.isEmpty(st)){
  2567. this.sortType = this.type.sortType;
  2568. }
  2569. if(!this.convert){
  2570. this.convert = this.type.convert;
  2571. }
  2572. },
  2573. /**
  2574. * @cfg {String} name
  2575. * The name by which the field is referenced within the Record. This is referenced by, for example,
  2576. * the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.ColumnModel}.
  2577. * <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
  2578. * definition may consist of just a String for the field name.</p>
  2579. */
  2580. /**
  2581. * @cfg {Mixed} type
  2582. * (Optional) The data type for automatic conversion from received data to the <i>stored</i> value if <code>{@link Ext.data.Field#convert convert}</code>
  2583. * has not been specified. This may be specified as a string value. Possible values are
  2584. * <div class="mdetail-params"><ul>
  2585. * <li>auto (Default, implies no conversion)</li>
  2586. * <li>string</li>
  2587. * <li>int</li>
  2588. * <li>float</li>
  2589. * <li>boolean</li>
  2590. * <li>date</li></ul></div>
  2591. * <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
  2592. * <p>Developers may create their own application-specific data types by defining new members of the
  2593. * {@link Ext.data.Types} class.</p>
  2594. */
  2595. /**
  2596. * @cfg {Function} convert
  2597. * (Optional) A function which converts the value provided by the Reader into an object that will be stored
  2598. * in the Record. It is passed the following parameters:<div class="mdetail-params"><ul>
  2599. * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
  2600. * the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
  2601. * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
  2602. * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
  2603. * ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
  2604. * </ul></div>
  2605. * <pre><code>
  2606. // example of convert function
  2607. function fullName(v, record){
  2608. return record.name.last + ', ' + record.name.first;
  2609. }
  2610. function location(v, record){
  2611. return !record.city ? '' : (record.city + ', ' + record.state);
  2612. }
  2613. var Dude = Ext.data.Record.create([
  2614. {name: 'fullname', convert: fullName},
  2615. {name: 'firstname', mapping: 'name.first'},
  2616. {name: 'lastname', mapping: 'name.last'},
  2617. {name: 'city', defaultValue: 'homeless'},
  2618. 'state',
  2619. {name: 'location', convert: location}
  2620. ]);
  2621. // create the data store
  2622. var store = new Ext.data.Store({
  2623. reader: new Ext.data.JsonReader(
  2624. {
  2625. idProperty: 'key',
  2626. root: 'daRoot',
  2627. totalProperty: 'total'
  2628. },
  2629. Dude // recordType
  2630. )
  2631. });
  2632. var myData = [
  2633. { key: 1,
  2634. name: { first: 'Fat', last: 'Albert' }
  2635. // notice no city, state provided in data object
  2636. },
  2637. { key: 2,
  2638. name: { first: 'Barney', last: 'Rubble' },
  2639. city: 'Bedrock', state: 'Stoneridge'
  2640. },
  2641. { key: 3,
  2642. name: { first: 'Cliff', last: 'Claven' },
  2643. city: 'Boston', state: 'MA'
  2644. }
  2645. ];
  2646. * </code></pre>
  2647. */
  2648. /**
  2649. * @cfg {String} dateFormat
  2650. * <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
  2651. * <p>A format string for the {@link Date#parseDate Date.parseDate} function, or "timestamp" if the
  2652. * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
  2653. * javascript millisecond timestamp. See {@link Date}</p>
  2654. */
  2655. dateFormat: null,
  2656. /**
  2657. * @cfg {Mixed} defaultValue
  2658. * (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
  2659. * when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
  2660. * object (i.e. undefined). (defaults to "")
  2661. */
  2662. defaultValue: "",
  2663. /**
  2664. * @cfg {String/Number} mapping
  2665. * <p>(Optional) A path expression for use by the {@link Ext.data.DataReader} implementation
  2666. * that is creating the {@link Ext.data.Record Record} to extract the Field value from the data object.
  2667. * If the path expression is the same as the field name, the mapping may be omitted.</p>
  2668. * <p>The form of the mapping expression depends on the Reader being used.</p>
  2669. * <div class="mdetail-params"><ul>
  2670. * <li>{@link Ext.data.JsonReader}<div class="sub-desc">The mapping is a string containing the javascript
  2671. * expression to reference the data from an element of the data item's {@link Ext.data.JsonReader#root root} Array. Defaults to the field name.</div></li>
  2672. * <li>{@link Ext.data.XmlReader}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
  2673. * item relative to the DOM element that represents the {@link Ext.data.XmlReader#record record}. Defaults to the field name.</div></li>
  2674. * <li>{@link Ext.data.ArrayReader}<div class="sub-desc">The mapping is a number indicating the Array index
  2675. * of the field's value. Defaults to the field specification's Array position.</div></li>
  2676. * </ul></div>
  2677. * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
  2678. * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
  2679. * return the desired data.</p>
  2680. */
  2681. mapping: null,
  2682. /**
  2683. * @cfg {Function} sortType
  2684. * (Optional) A function which converts a Field's value to a comparable value in order to ensure
  2685. * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
  2686. * sort example:<pre><code>
  2687. // current sort after sort we want
  2688. // +-+------+ +-+------+
  2689. // |1|First | |1|First |
  2690. // |2|Last | |3|Second|
  2691. // |3|Second| |2|Last |
  2692. // +-+------+ +-+------+
  2693. sortType: function(value) {
  2694. switch (value.toLowerCase()) // native toLowerCase():
  2695. {
  2696. case 'first': return 1;
  2697. case 'second': return 2;
  2698. default: return 3;
  2699. }
  2700. }
  2701. * </code></pre>
  2702. */
  2703. sortType : null,
  2704. /**
  2705. * @cfg {String} sortDir
  2706. * (Optional) Initial direction to sort (<code>"ASC"</code> or <code>"DESC"</code>). Defaults to
  2707. * <code>"ASC"</code>.
  2708. */
  2709. sortDir : "ASC",
  2710. /**
  2711. * @cfg {Boolean} allowBlank
  2712. * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to <code>true</code>.
  2713. * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid}
  2714. * to evaluate to <code>false</code>.
  2715. */
  2716. allowBlank : true
  2717. });
  2718. /**
  2719. * @class Ext.data.DataReader
  2720. * Abstract base class for reading structured data from a data source and converting
  2721. * it into an object containing {@link Ext.data.Record} objects and metadata for use
  2722. * by an {@link Ext.data.Store}. This class is intended to be extended and should not
  2723. * be created directly. For existing implementations, see {@link Ext.data.ArrayReader},
  2724. * {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}.
  2725. * @constructor Create a new DataReader
  2726. * @param {Object} meta Metadata configuration options (implementation-specific).
  2727. * @param {Array/Object} recordType
  2728. * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
  2729. * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
  2730. * constructor created using {@link Ext.data.Record#create}.</p>
  2731. */
  2732. Ext.data.DataReader = function(meta, recordType){
  2733. /**
  2734. * This DataReader's configured metadata as passed to the constructor.
  2735. * @type Mixed
  2736. * @property meta
  2737. */
  2738. this.meta = meta;
  2739. /**
  2740. * @cfg {Array/Object} fields
  2741. * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
  2742. * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
  2743. * constructor created from {@link Ext.data.Record#create}.</p>
  2744. */
  2745. this.recordType = Ext.isArray(recordType) ?
  2746. Ext.data.Record.create(recordType) : recordType;
  2747. // if recordType defined make sure extraction functions are defined
  2748. if (this.recordType){
  2749. this.buildExtractors();
  2750. }
  2751. };
  2752. Ext.data.DataReader.prototype = {
  2753. /**
  2754. * @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message.
  2755. */
  2756. /**
  2757. * Abstract method created in extension's buildExtractors impl.
  2758. */
  2759. getTotal: Ext.emptyFn,
  2760. /**
  2761. * Abstract method created in extension's buildExtractors impl.
  2762. */
  2763. getRoot: Ext.emptyFn,
  2764. /**
  2765. * Abstract method created in extension's buildExtractors impl.
  2766. */
  2767. getMessage: Ext.emptyFn,
  2768. /**
  2769. * Abstract method created in extension's buildExtractors impl.
  2770. */
  2771. getSuccess: Ext.emptyFn,
  2772. /**
  2773. * Abstract method created in extension's buildExtractors impl.
  2774. */
  2775. getId: Ext.emptyFn,
  2776. /**
  2777. * Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
  2778. */
  2779. buildExtractors : Ext.emptyFn,
  2780. /**
  2781. * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
  2782. */
  2783. extractValues : Ext.emptyFn,
  2784. /**
  2785. * Used for un-phantoming a record after a successful database insert. Sets the records pk along with new data from server.
  2786. * You <b>must</b> return at least the database pk using the idProperty defined in your DataReader configuration. The incoming
  2787. * data from server will be merged with the data in the local record.
  2788. * In addition, you <b>must</b> return record-data from the server in the same order received.
  2789. * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed.
  2790. * @param {Record/Record[]} record The phantom record to be realized.
  2791. * @param {Object/Object[]} data The new record data to apply. Must include the primary-key from database defined in idProperty field.
  2792. */
  2793. realize: function(rs, data){
  2794. if (Ext.isArray(rs)) {
  2795. for (var i = rs.length - 1; i >= 0; i--) {
  2796. // recurse
  2797. if (Ext.isArray(data)) {
  2798. this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
  2799. }
  2800. else {
  2801. // weird...rs is an array but data isn't?? recurse but just send in the whole invalid data object.
  2802. // the else clause below will detect !this.isData and throw exception.
  2803. this.realize(rs.splice(i,1).shift(), data);
  2804. }
  2805. }
  2806. }
  2807. else {
  2808. // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
  2809. if (Ext.isArray(data) && data.length == 1) {
  2810. data = data.shift();
  2811. }
  2812. if (!this.isData(data)) {
  2813. // TODO: Let exception-handler choose to commit or not rather than blindly rs.commit() here.
  2814. //rs.commit();
  2815. throw new Ext.data.DataReader.Error('realize', rs);
  2816. }
  2817. rs.phantom = false; // <-- That's what it's all about
  2818. rs._phid = rs.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
  2819. rs.id = this.getId(data);
  2820. rs.data = data;
  2821. rs.commit();
  2822. }
  2823. },
  2824. /**
  2825. * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.
  2826. * If returning data from multiple-records after a batch-update, you <b>must</b> return record-data from the server in
  2827. * the same order received. Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be
  2828. * suppressed as the record receives fresh new data-hash
  2829. * @param {Record/Record[]} rs
  2830. * @param {Object/Object[]} data
  2831. */
  2832. update : function(rs, data) {
  2833. if (Ext.isArray(rs)) {
  2834. for (var i=rs.length-1; i >= 0; i--) {
  2835. if (Ext.isArray(data)) {
  2836. this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
  2837. }
  2838. else {
  2839. // weird...rs is an array but data isn't?? recurse but just send in the whole data object.
  2840. // the else clause below will detect !this.isData and throw exception.
  2841. this.update(rs.splice(i,1).shift(), data);
  2842. }
  2843. }
  2844. }
  2845. else {
  2846. // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
  2847. if (Ext.isArray(data) && data.length == 1) {
  2848. data = data.shift();
  2849. }
  2850. if (this.isData(data)) {
  2851. rs.data = Ext.apply(rs.data, data);
  2852. }
  2853. rs.commit();
  2854. }
  2855. },
  2856. /**
  2857. * returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
  2858. * @param {Object[]/Object} data-root from server response
  2859. * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record
  2860. * @private
  2861. */
  2862. extractData : function(root, returnRecords) {
  2863. // A bit ugly this, too bad the Record's raw data couldn't be saved in a common property named "raw" or something.
  2864. var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node';
  2865. var rs = [];
  2866. // Had to add Check for XmlReader, #isData returns true if root is an Xml-object. Want to check in order to re-factor
  2867. // #extractData into DataReader base, since the implementations are almost identical for JsonReader, XmlReader
  2868. if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) {
  2869. root = [root];
  2870. }
  2871. var f = this.recordType.prototype.fields,
  2872. fi = f.items,
  2873. fl = f.length,
  2874. rs = [];
  2875. if (returnRecords === true) {
  2876. var Record = this.recordType;
  2877. for (var i = 0; i < root.length; i++) {
  2878. var n = root[i];
  2879. var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
  2880. record[rawName] = n; // <-- There's implementation of ugly bit, setting the raw record-data.
  2881. rs.push(record);
  2882. }
  2883. }
  2884. else {
  2885. for (var i = 0; i < root.length; i++) {
  2886. var data = this.extractValues(root[i], fi, fl);
  2887. data[this.meta.idProperty] = this.getId(root[i]);
  2888. rs.push(data);
  2889. }
  2890. }
  2891. return rs;
  2892. },
  2893. /**
  2894. * Returns true if the supplied data-hash <b>looks</b> and quacks like data. Checks to see if it has a key
  2895. * corresponding to idProperty defined in your DataReader config containing non-empty pk.
  2896. * @param {Object} data
  2897. * @return {Boolean}
  2898. */
  2899. isData : function(data) {
  2900. return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;
  2901. },
  2902. // private function a store will createSequence upon
  2903. onMetaChange : function(meta){
  2904. delete this.ef;
  2905. this.meta = meta;
  2906. this.recordType = Ext.data.Record.create(meta.fields);
  2907. this.buildExtractors();
  2908. }
  2909. };
  2910. /**
  2911. * @class Ext.data.DataReader.Error
  2912. * @extends Ext.Error
  2913. * General error class for Ext.data.DataReader
  2914. */
  2915. Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
  2916. constructor : function(message, arg) {
  2917. this.arg = arg;
  2918. Ext.Error.call(this, message);
  2919. },
  2920. name: 'Ext.data.DataReader'
  2921. });
  2922. Ext.apply(Ext.data.DataReader.Error.prototype, {
  2923. lang : {
  2924. 'update': "#update received invalid data from server. Please see docs for DataReader#update and review your DataReader configuration.",
  2925. 'realize': "#realize was called with invalid remote-data. Please see the docs for DataReader#realize and review your DataReader configuration.",
  2926. 'invalid-response': "#readResponse received an invalid response from the server."
  2927. }
  2928. });
  2929. /**
  2930. * @class Ext.data.DataWriter
  2931. * <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
  2932. * an Ext.data.Store and a server-side framework. A Writer enabled Store will
  2933. * automatically manage the Ajax requests to perform CRUD actions on a Store.</p>
  2934. * <p>Ext.data.DataWriter is an abstract base class which is intended to be extended
  2935. * and should not be created directly. For existing implementations, see
  2936. * {@link Ext.data.JsonWriter}.</p>
  2937. * <p>Creating a writer is simple:</p>
  2938. * <pre><code>
  2939. var writer = new Ext.data.JsonWriter({
  2940. encode: false // &lt;--- false causes data to be printed to jsonData config-property of Ext.Ajax#reqeust
  2941. });
  2942. * </code></pre>
  2943. * * <p>Same old JsonReader as Ext-2.x:</p>
  2944. * <pre><code>
  2945. var reader = new Ext.data.JsonReader({idProperty: 'id'}, [{name: 'first'}, {name: 'last'}, {name: 'email'}]);
  2946. * </code></pre>
  2947. *
  2948. * <p>The proxy for a writer enabled store can be configured with a simple <code>url</code>:</p>
  2949. * <pre><code>
  2950. // Create a standard HttpProxy instance.
  2951. var proxy = new Ext.data.HttpProxy({
  2952. url: 'app.php/users' // &lt;--- Supports "provides"-type urls, such as '/users.json', '/products.xml' (Hello Rails/Merb)
  2953. });
  2954. * </code></pre>
  2955. * <p>For finer grained control, the proxy may also be configured with an <code>API</code>:</p>
  2956. * <pre><code>
  2957. // Maximum flexibility with the API-configuration
  2958. var proxy = new Ext.data.HttpProxy({
  2959. api: {
  2960. read : 'app.php/users/read',
  2961. create : 'app.php/users/create',
  2962. update : 'app.php/users/update',
  2963. destroy : { // &lt;--- Supports object-syntax as well
  2964. url: 'app.php/users/destroy',
  2965. method: "DELETE"
  2966. }
  2967. }
  2968. });
  2969. * </code></pre>
  2970. * <p>Pulling it all together into a Writer-enabled Store:</p>
  2971. * <pre><code>
  2972. var store = new Ext.data.Store({
  2973. proxy: proxy,
  2974. reader: reader,
  2975. writer: writer,
  2976. autoLoad: true,
  2977. autoSave: true // -- Cell-level updates.
  2978. });
  2979. * </code></pre>
  2980. * <p>Initiating write-actions <b>automatically</b>, using the existing Ext2.0 Store/Record API:</p>
  2981. * <pre><code>
  2982. var rec = store.getAt(0);
  2983. rec.set('email', 'foo@bar.com'); // &lt;--- Immediately initiates an UPDATE action through configured proxy.
  2984. store.remove(rec); // &lt;---- Immediately initiates a DESTROY action through configured proxy.
  2985. * </code></pre>
  2986. * <p>For <b>record/batch</b> updates, use the Store-configuration {@link Ext.data.Store#autoSave autoSave:false}</p>
  2987. * <pre><code>
  2988. var store = new Ext.data.Store({
  2989. proxy: proxy,
  2990. reader: reader,
  2991. writer: writer,
  2992. autoLoad: true,
  2993. autoSave: false // -- disable cell-updates
  2994. });
  2995. var urec = store.getAt(0);
  2996. urec.set('email', 'foo@bar.com');
  2997. var drec = store.getAt(1);
  2998. store.remove(drec);
  2999. // Push the button!
  3000. store.save();
  3001. * </code></pre>
  3002. * @constructor Create a new DataWriter
  3003. * @param {Object} meta Metadata configuration options (implementation-specific)
  3004. * @param {Object} recordType Either an Array of field definition objects as specified
  3005. * in {@link Ext.data.Record#create}, or an {@link Ext.data.Record} object created
  3006. * using {@link Ext.data.Record#create}.
  3007. */
  3008. Ext.data.DataWriter = function(config){
  3009. Ext.apply(this, config);
  3010. };
  3011. Ext.data.DataWriter.prototype = {
  3012. /**
  3013. * @cfg {Boolean} writeAllFields
  3014. * <tt>false</tt> by default. Set <tt>true</tt> to have DataWriter return ALL fields of a modified
  3015. * record -- not just those that changed.
  3016. * <tt>false</tt> to have DataWriter only request modified fields from a record.
  3017. */
  3018. writeAllFields : false,
  3019. /**
  3020. * @cfg {Boolean} listful
  3021. * <tt>false</tt> by default. Set <tt>true</tt> to have the DataWriter <b>always</b> write HTTP params as a list,
  3022. * even when acting upon a single record.
  3023. */
  3024. listful : false, // <-- listful is actually not used internally here in DataWriter. @see Ext.data.Store#execute.
  3025. /**
  3026. * Compiles a Store recordset into a data-format defined by an extension such as {@link Ext.data.JsonWriter} or {@link Ext.data.XmlWriter} in preparation for a {@link Ext.data.Api#actions server-write action}. The first two params are similar similar in nature to {@link Ext#apply},
  3027. * Where the first parameter is the <i>receiver</i> of paramaters and the second, baseParams, <i>the source</i>.
  3028. * @param {Object} params The request-params receiver.
  3029. * @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}. The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
  3030. * @param {String} action [{@link Ext.data.Api#actions create|update|destroy}]
  3031. * @param {Record/Record[]} rs The recordset to write, the subject(s) of the write action.
  3032. */
  3033. apply : function(params, baseParams, action, rs) {
  3034. var data = [],
  3035. renderer = action + 'Record';
  3036. // TODO implement @cfg listful here
  3037. if (Ext.isArray(rs)) {
  3038. Ext.each(rs, function(rec){
  3039. data.push(this[renderer](rec));
  3040. }, this);
  3041. }
  3042. else if (rs instanceof Ext.data.Record) {
  3043. data = this[renderer](rs);
  3044. }
  3045. this.render(params, baseParams, data);
  3046. },
  3047. /**
  3048. * abstract method meant to be overridden by all DataWriter extensions. It's the extension's job to apply the "data" to the "params".
  3049. * The data-object provided to render is populated with data according to the meta-info defined in the user's DataReader config,
  3050. * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
  3051. * @param {Record[]} rs Store recordset
  3052. * @param {Object} params Http params to be sent to server.
  3053. * @param {Object} data object populated according to DataReader meta-data.
  3054. */
  3055. render : Ext.emptyFn,
  3056. /**
  3057. * @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses
  3058. * (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord}
  3059. */
  3060. updateRecord : Ext.emptyFn,
  3061. /**
  3062. * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses
  3063. * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord})
  3064. */
  3065. createRecord : Ext.emptyFn,
  3066. /**
  3067. * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses
  3068. * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord})
  3069. */
  3070. destroyRecord : Ext.emptyFn,
  3071. /**
  3072. * Converts a Record to a hash, taking into account the state of the Ext.data.Record along with configuration properties
  3073. * related to its rendering, such as {@link #writeAllFields}, {@link Ext.data.Record#phantom phantom}, {@link Ext.data.Record#getChanges getChanges} and
  3074. * {@link Ext.data.DataReader#idProperty idProperty}
  3075. * @param {Ext.data.Record} rec The Record from which to create a hash.
  3076. * @param {Object} config <b>NOT YET IMPLEMENTED</b>. Will implement an exlude/only configuration for fine-control over which fields do/don't get rendered.
  3077. * @return {Object}
  3078. * @protected
  3079. * TODO Implement excludes/only configuration with 2nd param?
  3080. */
  3081. toHash : function(rec, config) {
  3082. var map = rec.fields.map,
  3083. data = {},
  3084. raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
  3085. m;
  3086. Ext.iterate(raw, function(prop, value){
  3087. if((m = map[prop])){
  3088. data[m.mapping ? m.mapping : m.name] = value;
  3089. }
  3090. });
  3091. // we don't want to write Ext auto-generated id to hash. Careful not to remove it on Models not having auto-increment pk though.
  3092. // We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty.
  3093. // we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix.
  3094. if (rec.phantom) {
  3095. if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
  3096. delete data[this.meta.idProperty];
  3097. }
  3098. } else {
  3099. data[this.meta.idProperty] = rec.id
  3100. }
  3101. return data;
  3102. },
  3103. /**
  3104. * Converts a {@link Ext.data.DataWriter#toHash Hashed} {@link Ext.data.Record} to fields-array array suitable
  3105. * for encoding to xml via XTemplate, eg:
  3106. <code><pre>&lt;tpl for=".">&lt;{name}>{value}&lt;/{name}&lt;/tpl></pre></code>
  3107. * eg, <b>non-phantom</b>:
  3108. <code><pre>{id: 1, first: 'foo', last: 'bar'} --> [{name: 'id', value: 1}, {name: 'first', value: 'foo'}, {name: 'last', value: 'bar'}]</pre></code>
  3109. * {@link Ext.data.Record#phantom Phantom} records will have had their idProperty omitted in {@link #toHash} if determined to be auto-generated.
  3110. * Non AUTOINCREMENT pks should have been protected.
  3111. * @param {Hash} data Hashed by Ext.data.DataWriter#toHash
  3112. * @return {[Object]} Array of attribute-objects.
  3113. * @protected
  3114. */
  3115. toArray : function(data) {
  3116. var fields = [];
  3117. Ext.iterate(data, function(k, v) {fields.push({name: k, value: v});},this);
  3118. return fields;
  3119. }
  3120. };/**
  3121. * @class Ext.data.DataProxy
  3122. * @extends Ext.util.Observable
  3123. * <p>Abstract base class for implementations which provide retrieval of unformatted data objects.
  3124. * This class is intended to be extended and should not be created directly. For existing implementations,
  3125. * see {@link Ext.data.DirectProxy}, {@link Ext.data.HttpProxy}, {@link Ext.data.ScriptTagProxy} and
  3126. * {@link Ext.data.MemoryProxy}.</p>
  3127. * <p>DataProxy implementations are usually used in conjunction with an implementation of {@link Ext.data.DataReader}
  3128. * (of the appropriate type which knows how to parse the data object) to provide a block of
  3129. * {@link Ext.data.Records} to an {@link Ext.data.Store}.</p>
  3130. * <p>The parameter to a DataProxy constructor may be an {@link Ext.data.Connection} or can also be the
  3131. * config object to an {@link Ext.data.Connection}.</p>
  3132. * <p>Custom implementations must implement either the <code><b>doRequest</b></code> method (preferred) or the
  3133. * <code>load</code> method (deprecated). See
  3134. * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#doRequest doRequest} or
  3135. * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#load load} for additional details.</p>
  3136. * <p><b><u>Example 1</u></b></p>
  3137. * <pre><code>
  3138. proxy: new Ext.data.ScriptTagProxy({
  3139. {@link Ext.data.Connection#url url}: 'http://extjs.com/forum/topics-remote.php'
  3140. }),
  3141. * </code></pre>
  3142. * <p><b><u>Example 2</u></b></p>
  3143. * <pre><code>
  3144. proxy : new Ext.data.HttpProxy({
  3145. {@link Ext.data.Connection#method method}: 'GET',
  3146. {@link Ext.data.HttpProxy#prettyUrls prettyUrls}: false,
  3147. {@link Ext.data.Connection#url url}: 'local/default.php', // see options parameter for {@link Ext.Ajax#request}
  3148. {@link #api}: {
  3149. // all actions except the following will use above url
  3150. create : 'local/new.php',
  3151. update : 'local/update.php'
  3152. }
  3153. }),
  3154. * </code></pre>
  3155. * <p>And <b>new in Ext version 3</b>, attach centralized event-listeners upon the DataProxy class itself! This is a great place
  3156. * to implement a <i>messaging system</i> to centralize your application's user-feedback and error-handling.</p>
  3157. * <pre><code>
  3158. // Listen to all "beforewrite" event fired by all proxies.
  3159. Ext.data.DataProxy.on('beforewrite', function(proxy, action) {
  3160. console.log('beforewrite: ', action);
  3161. });
  3162. // Listen to "write" event fired by all proxies
  3163. Ext.data.DataProxy.on('write', function(proxy, action, data, res, rs) {
  3164. console.info('write: ', action);
  3165. });
  3166. // Listen to "exception" event fired by all proxies
  3167. Ext.data.DataProxy.on('exception', function(proxy, type, action) {
  3168. console.error(type + action + ' exception);
  3169. });
  3170. * </code></pre>
  3171. * <b>Note:</b> These three events are all fired with the signature of the corresponding <i>DataProxy instance</i> event {@link #beforewrite beforewrite}, {@link #write write} and {@link #exception exception}.
  3172. */
  3173. Ext.data.DataProxy = function(conn){
  3174. // make sure we have a config object here to support ux proxies.
  3175. // All proxies should now send config into superclass constructor.
  3176. conn = conn || {};
  3177. // This line caused a bug when people use custom Connection object having its own request method.
  3178. // http://extjs.com/forum/showthread.php?t=67194. Have to set DataProxy config
  3179. //Ext.applyIf(this, conn);
  3180. this.api = conn.api;
  3181. this.url = conn.url;
  3182. this.restful = conn.restful;
  3183. this.listeners = conn.listeners;
  3184. // deprecated
  3185. this.prettyUrls = conn.prettyUrls;
  3186. /**
  3187. * @cfg {Object} api
  3188. * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
  3189. * Defaults to:<pre><code>
  3190. api: {
  3191. read : undefined,
  3192. create : undefined,
  3193. update : undefined,
  3194. destroy : undefined
  3195. }
  3196. * </code></pre>
  3197. * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
  3198. * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
  3199. * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>
  3200. * <p>For example:</p>
  3201. * <pre><code>
  3202. api: {
  3203. load : '/controller/load',
  3204. create : '/controller/new', // Server MUST return idProperty of new record
  3205. save : '/controller/update',
  3206. destroy : '/controller/destroy_action'
  3207. }
  3208. // Alternatively, one can use the object-form to specify each API-action
  3209. api: {
  3210. load: {url: 'read.php', method: 'GET'},
  3211. create: 'create.php',
  3212. destroy: 'destroy.php',
  3213. save: 'update.php'
  3214. }
  3215. * </code></pre>
  3216. * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
  3217. * will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>
  3218. * <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API
  3219. * property should be modified before the action is requested using the corresponding before
  3220. * action event. For example to modify the URL associated with the load action:
  3221. * <pre><code>
  3222. // modify the url for the action
  3223. myStore.on({
  3224. beforeload: {
  3225. fn: function (store, options) {
  3226. // use <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> to change the URL for *just* this request.
  3227. store.proxy.setUrl('changed1.php');
  3228. // set optional second parameter to true to make this URL change
  3229. // permanent, applying this URL for all subsequent requests.
  3230. store.proxy.setUrl('changed1.php', true);
  3231. // Altering the proxy API should be done using the public
  3232. // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt>.
  3233. store.proxy.setApi('read', 'changed2.php');
  3234. // Or set the entire API with a config-object.
  3235. // When using the config-object option, you must redefine the <b>entire</b>
  3236. // API -- not just a specific action of it.
  3237. store.proxy.setApi({
  3238. read : 'changed_read.php',
  3239. create : 'changed_create.php',
  3240. update : 'changed_update.php',
  3241. destroy : 'changed_destroy.php'
  3242. });
  3243. }
  3244. }
  3245. });
  3246. * </code></pre>
  3247. * </p>
  3248. */
  3249. this.addEvents(
  3250. /**
  3251. * @event exception
  3252. * <p>Fires if an exception occurs in the Proxy during a remote request. This event is relayed
  3253. * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},
  3254. * so any Store instance may observe this event.</p>
  3255. * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
  3256. * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>
  3257. * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
  3258. * <p>This event can be fired for one of two reasons:</p>
  3259. * <div class="mdetail-params"><ul>
  3260. * <li>remote-request <b>failed</b> : <div class="sub-desc">
  3261. * The server did not return status === 200.
  3262. * </div></li>
  3263. * <li>remote-request <b>succeeded</b> : <div class="sub-desc">
  3264. * The remote-request succeeded but the reader could not read the response.
  3265. * This means the server returned data, but the configured Reader threw an
  3266. * error while reading the response. In this case, this event will be
  3267. * raised and the caught error will be passed along into this event.
  3268. * </div></li>
  3269. * </ul></div>
  3270. * <br><p>This event fires with two different contexts based upon the 2nd
  3271. * parameter <tt>type [remote|response]</tt>. The first four parameters
  3272. * are identical between the two contexts -- only the final two parameters
  3273. * differ.</p>
  3274. * @param {DataProxy} this The proxy that sent the request
  3275. * @param {String} type
  3276. * <p>The value of this parameter will be either <tt>'response'</tt> or <tt>'remote'</tt>.</p>
  3277. * <div class="mdetail-params"><ul>
  3278. * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
  3279. * <p>An <b>invalid</b> response from the server was returned: either 404,
  3280. * 500 or the response meta-data does not match that defined in the DataReader
  3281. * (e.g.: root, idProperty, successProperty).</p>
  3282. * </div></li>
  3283. * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
  3284. * <p>A <b>valid</b> response was returned from the server having
  3285. * successProperty === false. This response might contain an error-message
  3286. * sent from the server. For example, the user may have failed
  3287. * authentication/authorization or a database validation error occurred.</p>
  3288. * </div></li>
  3289. * </ul></div>
  3290. * @param {String} action Name of the action (see {@link Ext.data.Api#actions}.
  3291. * @param {Object} options The options for the action that were specified in the {@link #request}.
  3292. * @param {Object} response
  3293. * <p>The value of this parameter depends on the value of the <code>type</code> parameter:</p>
  3294. * <div class="mdetail-params"><ul>
  3295. * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
  3296. * <p>The raw browser response object (e.g.: XMLHttpRequest)</p>
  3297. * </div></li>
  3298. * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
  3299. * <p>The decoded response object sent from the server.</p>
  3300. * </div></li>
  3301. * </ul></div>
  3302. * @param {Mixed} arg
  3303. * <p>The type and value of this parameter depends on the value of the <code>type</code> parameter:</p>
  3304. * <div class="mdetail-params"><ul>
  3305. * <li><b><tt>'response'</tt></b> : Error<div class="sub-desc">
  3306. * <p>The JavaScript Error object caught if the configured Reader could not read the data.
  3307. * If the remote request returns success===false, this parameter will be null.</p>
  3308. * </div></li>
  3309. * <li><b><tt>'remote'</tt></b> : Record/Record[]<div class="sub-desc">
  3310. * <p>This parameter will only exist if the <tt>action</tt> was a <b>write</b> action
  3311. * (Ext.data.Api.actions.create|update|destroy).</p>
  3312. * </div></li>
  3313. * </ul></div>
  3314. */
  3315. 'exception',
  3316. /**
  3317. * @event beforeload
  3318. * Fires before a request to retrieve a data object.
  3319. * @param {DataProxy} this The proxy for the request
  3320. * @param {Object} params The params object passed to the {@link #request} function
  3321. */
  3322. 'beforeload',
  3323. /**
  3324. * @event load
  3325. * Fires before the load method's callback is called.
  3326. * @param {DataProxy} this The proxy for the request
  3327. * @param {Object} o The request transaction object
  3328. * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
  3329. */
  3330. 'load',
  3331. /**
  3332. * @event loadexception
  3333. * <p>This event is <b>deprecated</b>. The signature of the loadexception event
  3334. * varies depending on the proxy, use the catch-all {@link #exception} event instead.
  3335. * This event will fire in addition to the {@link #exception} event.</p>
  3336. * @param {misc} misc See {@link #exception}.
  3337. * @deprecated
  3338. */
  3339. 'loadexception',
  3340. /**
  3341. * @event beforewrite
  3342. * <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>
  3343. * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
  3344. * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>
  3345. * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
  3346. * @param {DataProxy} this The proxy for the request
  3347. * @param {String} action [Ext.data.Api.actions.create|update|destroy]
  3348. * @param {Record/Record[]} rs The Record(s) to create|update|destroy.
  3349. * @param {Object} params The request <code>params</code> object. Edit <code>params</code> to add parameters to the request.
  3350. */
  3351. 'beforewrite',
  3352. /**
  3353. * @event write
  3354. * <p>Fires before the request-callback is called</p>
  3355. * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
  3356. * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>
  3357. * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
  3358. * @param {DataProxy} this The proxy that sent the request
  3359. * @param {String} action [Ext.data.Api.actions.create|upate|destroy]
  3360. * @param {Object} data The data object extracted from the server-response
  3361. * @param {Object} response The decoded response from server
  3362. * @param {Record/Record[]} rs The Record(s) from Store
  3363. * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
  3364. */
  3365. 'write'
  3366. );
  3367. Ext.data.DataProxy.superclass.constructor.call(this);
  3368. // Prepare the proxy api. Ensures all API-actions are defined with the Object-form.
  3369. try {
  3370. Ext.data.Api.prepare(this);
  3371. } catch (e) {
  3372. if (e instanceof Ext.data.Api.Error) {
  3373. e.toConsole();
  3374. }
  3375. }
  3376. // relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening
  3377. Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);
  3378. };
  3379. Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
  3380. /**
  3381. * @cfg {Boolean} restful
  3382. * <p>Defaults to <tt>false</tt>. Set to <tt>true</tt> to operate in a RESTful manner.</p>
  3383. * <br><p> Note: this parameter will automatically be set to <tt>true</tt> if the
  3384. * {@link Ext.data.Store} it is plugged into is set to <code>restful: true</code>. If the
  3385. * Store is RESTful, there is no need to set this option on the proxy.</p>
  3386. * <br><p>RESTful implementations enable the serverside framework to automatically route
  3387. * actions sent to one url based upon the HTTP method, for example:
  3388. * <pre><code>
  3389. store: new Ext.data.Store({
  3390. restful: true,
  3391. proxy: new Ext.data.HttpProxy({url:'/users'}); // all requests sent to /users
  3392. ...
  3393. )}
  3394. * </code></pre>
  3395. * If there is no <code>{@link #api}</code> specified in the configuration of the proxy,
  3396. * all requests will be marshalled to a single RESTful url (/users) so the serverside
  3397. * framework can inspect the HTTP Method and act accordingly:
  3398. * <pre>
  3399. <u>Method</u> <u>url</u> <u>action</u>
  3400. POST /users create
  3401. GET /users read
  3402. PUT /users/23 update
  3403. DESTROY /users/23 delete
  3404. * </pre></p>
  3405. * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's
  3406. * {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails,
  3407. * Merb and Django) support segment based urls where the segments in the URL follow the
  3408. * Model-View-Controller approach:<pre><code>
  3409. * someSite.com/controller/action/id
  3410. * </code></pre>
  3411. * Where the segments in the url are typically:<div class="mdetail-params"><ul>
  3412. * <li>The first segment : represents the controller class that should be invoked.</li>
  3413. * <li>The second segment : represents the class function, or method, that should be called.</li>
  3414. * <li>The third segment : represents the ID (a variable typically passed to the method).</li>
  3415. * </ul></div></p>
  3416. * <br><p>Refer to <code>{@link Ext.data.DataProxy#api}</code> for additional information.</p>
  3417. */
  3418. restful: false,
  3419. /**
  3420. * <p>Redefines the Proxy's API or a single action of an API. Can be called with two method signatures.</p>
  3421. * <p>If called with an object as the only parameter, the object should redefine the <b>entire</b> API, e.g.:</p><pre><code>
  3422. proxy.setApi({
  3423. read : '/users/read',
  3424. create : '/users/create',
  3425. update : '/users/update',
  3426. destroy : '/users/destroy'
  3427. });
  3428. </code></pre>
  3429. * <p>If called with two parameters, the first parameter should be a string specifying the API action to
  3430. * redefine and the second parameter should be the URL (or function if using DirectProxy) to call for that action, e.g.:</p><pre><code>
  3431. proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url');
  3432. </code></pre>
  3433. * @param {String/Object} api An API specification object, or the name of an action.
  3434. * @param {String/Function} url The URL (or function if using DirectProxy) to call for the action.
  3435. */
  3436. setApi : function() {
  3437. if (arguments.length == 1) {
  3438. var valid = Ext.data.Api.isValid(arguments[0]);
  3439. if (valid === true) {
  3440. this.api = arguments[0];
  3441. }
  3442. else {
  3443. throw new Ext.data.Api.Error('invalid', valid);
  3444. }
  3445. }
  3446. else if (arguments.length == 2) {
  3447. if (!Ext.data.Api.isAction(arguments[0])) {
  3448. throw new Ext.data.Api.Error('invalid', arguments[0]);
  3449. }
  3450. this.api[arguments[0]] = arguments[1];
  3451. }
  3452. Ext.data.Api.prepare(this);
  3453. },
  3454. /**
  3455. * Returns true if the specified action is defined as a unique action in the api-config.
  3456. * request. If all API-actions are routed to unique urls, the xaction parameter is unecessary. However, if no api is defined
  3457. * and all Proxy actions are routed to DataProxy#url, the server-side will require the xaction parameter to perform a switch to
  3458. * the corresponding code for CRUD action.
  3459. * @param {String [Ext.data.Api.CREATE|READ|UPDATE|DESTROY]} action
  3460. * @return {Boolean}
  3461. */
  3462. isApiAction : function(action) {
  3463. return (this.api[action]) ? true : false;
  3464. },
  3465. /**
  3466. * All proxy actions are executed through this method. Automatically fires the "before" + action event
  3467. * @param {String} action Name of the action
  3468. * @param {Ext.data.Record/Ext.data.Record[]/null} rs Will be null when action is 'load'
  3469. * @param {Object} params
  3470. * @param {Ext.data.DataReader} reader
  3471. * @param {Function} callback
  3472. * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the Proxy object.
  3473. * @param {Object} options Any options specified for the action (e.g. see {@link Ext.data.Store#load}.
  3474. */
  3475. request : function(action, rs, params, reader, callback, scope, options) {
  3476. if (!this.api[action] && !this.load) {
  3477. throw new Ext.data.DataProxy.Error('action-undefined', action);
  3478. }
  3479. params = params || {};
  3480. if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
  3481. this.doRequest.apply(this, arguments);
  3482. }
  3483. else {
  3484. callback.call(scope || this, null, options, false);
  3485. }
  3486. },
  3487. /**
  3488. * <b>Deprecated</b> load method using old method signature. See {@doRequest} for preferred method.
  3489. * @deprecated
  3490. * @param {Object} params
  3491. * @param {Object} reader
  3492. * @param {Object} callback
  3493. * @param {Object} scope
  3494. * @param {Object} arg
  3495. */
  3496. load : null,
  3497. /**
  3498. * @cfg {Function} doRequest Abstract method that should be implemented in all subclasses. <b>Note:</b> Should only be used by custom-proxy developers.
  3499. * (e.g.: {@link Ext.data.HttpProxy#doRequest HttpProxy.doRequest},
  3500. * {@link Ext.data.DirectProxy#doRequest DirectProxy.doRequest}).
  3501. */
  3502. doRequest : function(action, rs, params, reader, callback, scope, options) {
  3503. // default implementation of doRequest for backwards compatibility with 2.0 proxies.
  3504. // If we're executing here, the action is probably "load".
  3505. // Call with the pre-3.0 method signature.
  3506. this.load(params, reader, callback, scope, options);
  3507. },
  3508. /**
  3509. * @cfg {Function} onRead Abstract method that should be implemented in all subclasses. <b>Note:</b> Should only be used by custom-proxy developers. Callback for read {@link Ext.data.Api#actions action}.
  3510. * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
  3511. * @param {Object} o The request transaction object
  3512. * @param {Object} res The server response
  3513. * @fires loadexception (deprecated)
  3514. * @fires exception
  3515. * @fires load
  3516. * @protected
  3517. */
  3518. onRead : Ext.emptyFn,
  3519. /**
  3520. * @cfg {Function} onWrite Abstract method that should be implemented in all subclasses. <b>Note:</b> Should only be used by custom-proxy developers. Callback for <i>create, update and destroy</i> {@link Ext.data.Api#actions actions}.
  3521. * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
  3522. * @param {Object} trans The request transaction object
  3523. * @param {Object} res The server response
  3524. * @fires exception
  3525. * @fires write
  3526. * @protected
  3527. */
  3528. onWrite : Ext.emptyFn,
  3529. /**
  3530. * buildUrl
  3531. * Sets the appropriate url based upon the action being executed. If restful is true, and only a single record is being acted upon,
  3532. * url will be built Rails-style, as in "/controller/action/32". restful will aply iff the supplied record is an
  3533. * instance of Ext.data.Record rather than an Array of them.
  3534. * @param {String} action The api action being executed [read|create|update|destroy]
  3535. * @param {Ext.data.Record/Ext.data.Record[]} record The record or Array of Records being acted upon.
  3536. * @return {String} url
  3537. * @private
  3538. */
  3539. buildUrl : function(action, record) {
  3540. record = record || null;
  3541. // conn.url gets nullified after each request. If it's NOT null here, that means the user must have intervened with a call
  3542. // to DataProxy#setUrl or DataProxy#setApi and changed it before the request was executed. If that's the case, use conn.url,
  3543. // otherwise, build the url from the api or this.url.
  3544. var url = (this.conn && this.conn.url) ? this.conn.url : (this.api[action]) ? this.api[action].url : this.url;
  3545. if (!url) {
  3546. throw new Ext.data.Api.Error('invalid-url', action);
  3547. }
  3548. // look for urls having "provides" suffix used in some MVC frameworks like Rails/Merb and others. The provides suffice informs
  3549. // the server what data-format the client is dealing with and returns data in the same format (eg: application/json, application/xml, etc)
  3550. // e.g.: /users.json, /users.xml, etc.
  3551. // with restful routes, we need urls like:
  3552. // PUT /users/1.json
  3553. // DELETE /users/1.json
  3554. var provides = null;
  3555. var m = url.match(/(.*)(\.json|\.xml|\.html)$/);
  3556. if (m) {
  3557. provides = m[2]; // eg ".json"
  3558. url = m[1]; // eg: "/users"
  3559. }
  3560. // prettyUrls is deprectated in favor of restful-config
  3561. if ((this.restful === true || this.prettyUrls === true) && record instanceof Ext.data.Record && !record.phantom) {
  3562. url += '/' + record.id;
  3563. }
  3564. return (provides === null) ? url : url + provides;
  3565. },
  3566. /**
  3567. * Destroys the proxy by purging any event listeners and cancelling any active requests.
  3568. */
  3569. destroy: function(){
  3570. this.purgeListeners();
  3571. }
  3572. });
  3573. // Apply the Observable prototype to the DataProxy class so that proxy instances can relay their
  3574. // events to the class. Allows for centralized listening of all proxy instances upon the DataProxy class.
  3575. Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);
  3576. Ext.util.Observable.call(Ext.data.DataProxy);
  3577. /**
  3578. * @class Ext.data.DataProxy.Error
  3579. * @extends Ext.Error
  3580. * DataProxy Error extension.
  3581. * constructor
  3582. * @param {String} message Message describing the error.
  3583. * @param {Record/Record[]} arg
  3584. */
  3585. Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
  3586. constructor : function(message, arg) {
  3587. this.arg = arg;
  3588. Ext.Error.call(this, message);
  3589. },
  3590. name: 'Ext.data.DataProxy'
  3591. });
  3592. Ext.apply(Ext.data.DataProxy.Error.prototype, {
  3593. lang: {
  3594. 'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function. Please review your Proxy url/api-configuration.",
  3595. 'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
  3596. }
  3597. });
  3598. /**
  3599. * @class Ext.data.Request
  3600. * A simple Request class used internally to the data package to provide more generalized remote-requests
  3601. * to a DataProxy.
  3602. * TODO Not yet implemented. Implement in Ext.data.Store#execute
  3603. */
  3604. Ext.data.Request = function(params) {
  3605. Ext.apply(this, params);
  3606. };
  3607. Ext.data.Request.prototype = {
  3608. /**
  3609. * @cfg {String} action
  3610. */
  3611. action : undefined,
  3612. /**
  3613. * @cfg {Ext.data.Record[]/Ext.data.Record} rs The Store recordset associated with the request.
  3614. */
  3615. rs : undefined,
  3616. /**
  3617. * @cfg {Object} params HTTP request params
  3618. */
  3619. params: undefined,
  3620. /**
  3621. * @cfg {Function} callback The function to call when request is complete
  3622. */
  3623. callback : Ext.emptyFn,
  3624. /**
  3625. * @cfg {Object} scope The scope of the callback funtion
  3626. */
  3627. scope : undefined,
  3628. /**
  3629. * @cfg {Ext.data.DataReader} reader The DataReader instance which will parse the received response
  3630. */
  3631. reader : undefined
  3632. };
  3633. /**
  3634. * @class Ext.data.Response
  3635. * A generic response class to normalize response-handling internally to the framework.
  3636. */
  3637. Ext.data.Response = function(params) {
  3638. Ext.apply(this, params);
  3639. };
  3640. Ext.data.Response.prototype = {
  3641. /**
  3642. * @cfg {String} action {@link Ext.data.Api#actions}
  3643. */
  3644. action: undefined,
  3645. /**
  3646. * @cfg {Boolean} success
  3647. */
  3648. success : undefined,
  3649. /**
  3650. * @cfg {String} message
  3651. */
  3652. message : undefined,
  3653. /**
  3654. * @cfg {Array/Object} data
  3655. */
  3656. data: undefined,
  3657. /**
  3658. * @cfg {Object} raw The raw response returned from server-code
  3659. */
  3660. raw: undefined,
  3661. /**
  3662. * @cfg {Ext.data.Record/Ext.data.Record[]} records related to the Request action
  3663. */
  3664. records: undefined
  3665. };
  3666. /**
  3667. * @class Ext.data.ScriptTagProxy
  3668. * @extends Ext.data.DataProxy
  3669. * An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
  3670. * other than the originating domain of the running page.<br>
  3671. * <p>
  3672. * <b>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
  3673. * of the running page, you must use this class, rather than HttpProxy.</b><br>
  3674. * <p>
  3675. * The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
  3676. * source code because it is used as the source inside a &lt;script> tag.<br>
  3677. * <p>
  3678. * In order for the browser to process the returned data, the server must wrap the data object
  3679. * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
  3680. * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
  3681. * depending on whether the callback name was passed:
  3682. * <p>
  3683. * <pre><code>
  3684. boolean scriptTag = false;
  3685. String cb = request.getParameter("callback");
  3686. if (cb != null) {
  3687. scriptTag = true;
  3688. response.setContentType("text/javascript");
  3689. } else {
  3690. response.setContentType("application/x-json");
  3691. }
  3692. Writer out = response.getWriter();
  3693. if (scriptTag) {
  3694. out.write(cb + "(");
  3695. }
  3696. out.print(dataBlock.toJsonString());
  3697. if (scriptTag) {
  3698. out.write(");");
  3699. }
  3700. </code></pre>
  3701. * <p>Below is a PHP example to do the same thing:</p><pre><code>
  3702. $callback = $_REQUEST['callback'];
  3703. // Create the output object.
  3704. $output = array('a' => 'Apple', 'b' => 'Banana');
  3705. //start output
  3706. if ($callback) {
  3707. header('Content-Type: text/javascript');
  3708. echo $callback . '(' . json_encode($output) . ');';
  3709. } else {
  3710. header('Content-Type: application/x-json');
  3711. echo json_encode($output);
  3712. }
  3713. </code></pre>
  3714. * <p>Below is the ASP.Net code to do the same thing:</p><pre><code>
  3715. String jsonString = "{success: true}";
  3716. String cb = Request.Params.Get("callback");
  3717. String responseString = "";
  3718. if (!String.IsNullOrEmpty(cb)) {
  3719. responseString = cb + "(" + jsonString + ")";
  3720. } else {
  3721. responseString = jsonString;
  3722. }
  3723. Response.Write(responseString);
  3724. </code></pre>
  3725. *
  3726. * @constructor
  3727. * @param {Object} config A configuration object.
  3728. */
  3729. Ext.data.ScriptTagProxy = function(config){
  3730. Ext.apply(this, config);
  3731. Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
  3732. this.head = document.getElementsByTagName("head")[0];
  3733. /**
  3734. * @event loadexception
  3735. * <b>Deprecated</b> in favor of 'exception' event.
  3736. * Fires if an exception occurs in the Proxy during data loading. This event can be fired for one of two reasons:
  3737. * <ul><li><b>The load call timed out.</b> This means the load callback did not execute within the time limit
  3738. * specified by {@link #timeout}. In this case, this event will be raised and the
  3739. * fourth parameter (read error) will be null.</li>
  3740. * <li><b>The load succeeded but the reader could not read the response.</b> This means the server returned
  3741. * data, but the configured Reader threw an error while reading the data. In this case, this event will be
  3742. * raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
  3743. * Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
  3744. * on any Store instance.
  3745. * @param {Object} this
  3746. * @param {Object} options The loading options that were specified (see {@link #load} for details). If the load
  3747. * call timed out, this parameter will be null.
  3748. * @param {Object} arg The callback's arg object passed to the {@link #load} function
  3749. * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
  3750. * If the remote request returns success: false, this parameter will be null.
  3751. */
  3752. };
  3753. Ext.data.ScriptTagProxy.TRANS_ID = 1000;
  3754. Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
  3755. /**
  3756. * @cfg {String} url The URL from which to request the data object.
  3757. */
  3758. /**
  3759. * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
  3760. */
  3761. timeout : 30000,
  3762. /**
  3763. * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
  3764. * the server the name of the callback function set up by the load call to process the returned data object.
  3765. * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
  3766. * javascript output which calls this named function passing the data object as its only parameter.
  3767. */
  3768. callbackParam : "callback",
  3769. /**
  3770. * @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
  3771. * name to the request.
  3772. */
  3773. nocache : true,
  3774. /**
  3775. * HttpProxy implementation of DataProxy#doRequest
  3776. * @param {String} action
  3777. * @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
  3778. * @param {Object} params An object containing properties which are to be used as HTTP parameters
  3779. * for the request to the remote server.
  3780. * @param {Ext.data.DataReader} reader The Reader object which converts the data
  3781. * object into a block of Ext.data.Records.
  3782. * @param {Function} callback The function into which to pass the block of Ext.data.Records.
  3783. * The function must be passed <ul>
  3784. * <li>The Record block object</li>
  3785. * <li>The "arg" argument from the load function</li>
  3786. * <li>A boolean success indicator</li>
  3787. * </ul>
  3788. * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
  3789. * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
  3790. */
  3791. doRequest : function(action, rs, params, reader, callback, scope, arg) {
  3792. var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
  3793. var url = this.buildUrl(action, rs);
  3794. if (!url) {
  3795. throw new Ext.data.Api.Error('invalid-url', url);
  3796. }
  3797. url = Ext.urlAppend(url, p);
  3798. if(this.nocache){
  3799. url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
  3800. }
  3801. var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
  3802. var trans = {
  3803. id : transId,
  3804. action: action,
  3805. cb : "stcCallback"+transId,
  3806. scriptId : "stcScript"+transId,
  3807. params : params,
  3808. arg : arg,
  3809. url : url,
  3810. callback : callback,
  3811. scope : scope,
  3812. reader : reader
  3813. };
  3814. window[trans.cb] = this.createCallback(action, rs, trans);
  3815. url += String.format("&{0}={1}", this.callbackParam, trans.cb);
  3816. if(this.autoAbort !== false){
  3817. this.abort();
  3818. }
  3819. trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
  3820. var script = document.createElement("script");
  3821. script.setAttribute("src", url);
  3822. script.setAttribute("type", "text/javascript");
  3823. script.setAttribute("id", trans.scriptId);
  3824. this.head.appendChild(script);
  3825. this.trans = trans;
  3826. },
  3827. // @private createCallback
  3828. createCallback : function(action, rs, trans) {
  3829. var self = this;
  3830. return function(res) {
  3831. self.trans = false;
  3832. self.destroyTrans(trans, true);
  3833. if (action === Ext.data.Api.actions.read) {
  3834. self.onRead.call(self, action, trans, res);
  3835. } else {
  3836. self.onWrite.call(self, action, trans, res, rs);
  3837. }
  3838. };
  3839. },
  3840. /**
  3841. * Callback for read actions
  3842. * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
  3843. * @param {Object} trans The request transaction object
  3844. * @param {Object} res The server response
  3845. * @protected
  3846. */
  3847. onRead : function(action, trans, res) {
  3848. var result;
  3849. try {
  3850. result = trans.reader.readRecords(res);
  3851. }catch(e){
  3852. // @deprecated: fire loadexception
  3853. this.fireEvent("loadexception", this, trans, res, e);
  3854. this.fireEvent('exception', this, 'response', action, trans, res, e);
  3855. trans.callback.call(trans.scope||window, null, trans.arg, false);
  3856. return;
  3857. }
  3858. if (result.success === false) {
  3859. // @deprecated: fire old loadexception for backwards-compat.
  3860. this.fireEvent('loadexception', this, trans, res);
  3861. this.fireEvent('exception', this, 'remote', action, trans, res, null);
  3862. } else {
  3863. this.fireEvent("load", this, res, trans.arg);
  3864. }
  3865. trans.callback.call(trans.scope||window, result, trans.arg, result.success);
  3866. },
  3867. /**
  3868. * Callback for write actions
  3869. * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
  3870. * @param {Object} trans The request transaction object
  3871. * @param {Object} res The server response
  3872. * @protected
  3873. */
  3874. onWrite : function(action, trans, response, rs) {
  3875. var reader = trans.reader;
  3876. try {
  3877. // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
  3878. var res = reader.readResponse(action, response);
  3879. } catch (e) {
  3880. this.fireEvent('exception', this, 'response', action, trans, res, e);
  3881. trans.callback.call(trans.scope||window, null, res, false);
  3882. return;
  3883. }
  3884. if(!res.success === true){
  3885. this.fireEvent('exception', this, 'remote', action, trans, res, rs);
  3886. trans.callback.call(trans.scope||window, null, res, false);
  3887. return;
  3888. }
  3889. this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
  3890. trans.callback.call(trans.scope||window, res.data, res, true);
  3891. },
  3892. // private
  3893. isLoading : function(){
  3894. return this.trans ? true : false;
  3895. },
  3896. /**
  3897. * Abort the current server request.
  3898. */
  3899. abort : function(){
  3900. if(this.isLoading()){
  3901. this.destroyTrans(this.trans);
  3902. }
  3903. },
  3904. // private
  3905. destroyTrans : function(trans, isLoaded){
  3906. this.head.removeChild(document.getElementById(trans.scriptId));
  3907. clearTimeout(trans.timeoutId);
  3908. if(isLoaded){
  3909. window[trans.cb] = undefined;
  3910. try{
  3911. delete window[trans.cb];
  3912. }catch(e){}
  3913. }else{
  3914. // if hasn't been loaded, wait for load to remove it to prevent script error
  3915. window[trans.cb] = function(){
  3916. window[trans.cb] = undefined;
  3917. try{
  3918. delete window[trans.cb];
  3919. }catch(e){}
  3920. };
  3921. }
  3922. },
  3923. // private
  3924. handleFailure : function(trans){
  3925. this.trans = false;
  3926. this.destroyTrans(trans, false);
  3927. if (trans.action === Ext.data.Api.actions.read) {
  3928. // @deprecated firing loadexception
  3929. this.fireEvent("loadexception", this, null, trans.arg);
  3930. }
  3931. this.fireEvent('exception', this, 'response', trans.action, {
  3932. response: null,
  3933. options: trans.arg
  3934. });
  3935. trans.callback.call(trans.scope||window, null, trans.arg, false);
  3936. },
  3937. // inherit docs
  3938. destroy: function(){
  3939. this.abort();
  3940. Ext.data.ScriptTagProxy.superclass.destroy.call(this);
  3941. }
  3942. });/**
  3943. * @class Ext.data.HttpProxy
  3944. * @extends Ext.data.DataProxy
  3945. * <p>An implementation of {@link Ext.data.DataProxy} that processes data requests within the same
  3946. * domain of the originating page.</p>
  3947. * <p><b>Note</b>: this class cannot be used to retrieve data from a domain other
  3948. * than the domain from which the running page was served. For cross-domain requests, use a
  3949. * {@link Ext.data.ScriptTagProxy ScriptTagProxy}.</p>
  3950. * <p>Be aware that to enable the browser to parse an XML document, the server must set
  3951. * the Content-Type header in the HTTP response to "<tt>text/xml</tt>".</p>
  3952. * @constructor
  3953. * @param {Object} conn
  3954. * An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}.
  3955. * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
  3956. * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
  3957. * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
  3958. * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
  3959. * used to pass parameters known at instantiation time.</p>
  3960. * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
  3961. * the request.</p>
  3962. */
  3963. Ext.data.HttpProxy = function(conn){
  3964. Ext.data.HttpProxy.superclass.constructor.call(this, conn);
  3965. /**
  3966. * The Connection object (Or options parameter to {@link Ext.Ajax#request}) which this HttpProxy
  3967. * uses to make requests to the server. Properties of this object may be changed dynamically to
  3968. * change the way data is requested.
  3969. * @property
  3970. */
  3971. this.conn = conn;
  3972. // nullify the connection url. The url param has been copied to 'this' above. The connection
  3973. // url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the
  3974. // connection url during beforeaction events (ie: beforeload, beforewrite, etc).
  3975. // Url is always re-defined during doRequest.
  3976. this.conn.url = null;
  3977. this.useAjax = !conn || !conn.events;
  3978. // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]
  3979. var actions = Ext.data.Api.actions;
  3980. this.activeRequest = {};
  3981. for (var verb in actions) {
  3982. this.activeRequest[actions[verb]] = undefined;
  3983. }
  3984. };
  3985. Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
  3986. /**
  3987. * Return the {@link Ext.data.Connection} object being used by this Proxy.
  3988. * @return {Connection} The Connection object. This object may be used to subscribe to events on
  3989. * a finer-grained basis than the DataProxy events.
  3990. */
  3991. getConnection : function() {
  3992. return this.useAjax ? Ext.Ajax : this.conn;
  3993. },
  3994. /**
  3995. * Used for overriding the url used for a single request. Designed to be called during a beforeaction event. Calling setUrl
  3996. * will override any urls set via the api configuration parameter. Set the optional parameter makePermanent to set the url for
  3997. * all subsequent requests. If not set to makePermanent, the next request will use the same url or api configuration defined
  3998. * in the initial proxy configuration.
  3999. * @param {String} url
  4000. * @param {Boolean} makePermanent (Optional) [false]
  4001. *
  4002. * (e.g.: beforeload, beforesave, etc).
  4003. */
  4004. setUrl : function(url, makePermanent) {
  4005. this.conn.url = url;
  4006. if (makePermanent === true) {
  4007. this.url = url;
  4008. this.api = null;
  4009. Ext.data.Api.prepare(this);
  4010. }
  4011. },
  4012. /**
  4013. * HttpProxy implementation of DataProxy#doRequest
  4014. * @param {String} action The crud action type (create, read, update, destroy)
  4015. * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
  4016. * @param {Object} params An object containing properties which are to be used as HTTP parameters
  4017. * for the request to the remote server.
  4018. * @param {Ext.data.DataReader} reader The Reader object which converts the data
  4019. * object into a block of Ext.data.Records.
  4020. * @param {Function} callback
  4021. * <div class="sub-desc"><p>A function to be called after the request.
  4022. * The <tt>callback</tt> is passed the following arguments:<ul>
  4023. * <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
  4024. * <li><tt>options</tt>: Options object from the action request</li>
  4025. * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
  4026. * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
  4027. * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
  4028. * @protected
  4029. */
  4030. doRequest : function(action, rs, params, reader, cb, scope, arg) {
  4031. var o = {
  4032. method: (this.api[action]) ? this.api[action]['method'] : undefined,
  4033. request: {
  4034. callback : cb,
  4035. scope : scope,
  4036. arg : arg
  4037. },
  4038. reader: reader,
  4039. callback : this.createCallback(action, rs),
  4040. scope: this
  4041. };
  4042. // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).
  4043. // Use std HTTP params otherwise.
  4044. if (params.jsonData) {
  4045. o.jsonData = params.jsonData;
  4046. } else if (params.xmlData) {
  4047. o.xmlData = params.xmlData;
  4048. } else {
  4049. o.params = params || {};
  4050. }
  4051. // Set the connection url. If this.conn.url is not null here,
  4052. // the user must have overridden the url during a beforewrite/beforeload event-handler.
  4053. // this.conn.url is nullified after each request.
  4054. this.conn.url = this.buildUrl(action, rs);
  4055. if(this.useAjax){
  4056. Ext.applyIf(o, this.conn);
  4057. // If a currently running request is found for this action, abort it.
  4058. if (this.activeRequest[action]) {
  4059. ////
  4060. // Disabled aborting activeRequest while implementing REST. activeRequest[action] will have to become an array
  4061. // TODO ideas anyone?
  4062. //
  4063. //Ext.Ajax.abort(this.activeRequest[action]);
  4064. }
  4065. this.activeRequest[action] = Ext.Ajax.request(o);
  4066. }else{
  4067. this.conn.request(o);
  4068. }
  4069. // request is sent, nullify the connection url in preparation for the next request
  4070. this.conn.url = null;
  4071. },
  4072. /**
  4073. * Returns a callback function for a request. Note a special case is made for the
  4074. * read action vs all the others.
  4075. * @param {String} action [create|update|delete|load]
  4076. * @param {Ext.data.Record[]} rs The Store-recordset being acted upon
  4077. * @private
  4078. */
  4079. createCallback : function(action, rs) {
  4080. return function(o, success, response) {
  4081. this.activeRequest[action] = undefined;
  4082. if (!success) {
  4083. if (action === Ext.data.Api.actions.read) {
  4084. // @deprecated: fire loadexception for backwards compat.
  4085. // TODO remove
  4086. this.fireEvent('loadexception', this, o, response);
  4087. }
  4088. this.fireEvent('exception', this, 'response', action, o, response);
  4089. o.request.callback.call(o.request.scope, null, o.request.arg, false);
  4090. return;
  4091. }
  4092. if (action === Ext.data.Api.actions.read) {
  4093. this.onRead(action, o, response);
  4094. } else {
  4095. this.onWrite(action, o, response, rs);
  4096. }
  4097. };
  4098. },
  4099. /**
  4100. * Callback for read action
  4101. * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
  4102. * @param {Object} o The request transaction object
  4103. * @param {Object} res The server response
  4104. * @fires loadexception (deprecated)
  4105. * @fires exception
  4106. * @fires load
  4107. * @protected
  4108. */
  4109. onRead : function(action, o, response) {
  4110. var result;
  4111. try {
  4112. result = o.reader.read(response);
  4113. }catch(e){
  4114. // @deprecated: fire old loadexception for backwards-compat.
  4115. // TODO remove
  4116. this.fireEvent('loadexception', this, o, response, e);
  4117. this.fireEvent('exception', this, 'response', action, o, response, e);
  4118. o.request.callback.call(o.request.scope, null, o.request.arg, false);
  4119. return;
  4120. }
  4121. if (result.success === false) {
  4122. // @deprecated: fire old loadexception for backwards-compat.
  4123. // TODO remove
  4124. this.fireEvent('loadexception', this, o, response);
  4125. // Get DataReader read-back a response-object to pass along to exception event
  4126. var res = o.reader.readResponse(action, response);
  4127. this.fireEvent('exception', this, 'remote', action, o, res, null);
  4128. }
  4129. else {
  4130. this.fireEvent('load', this, o, o.request.arg);
  4131. }
  4132. // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
  4133. // the calls to request.callback(...) in each will have to be made identical.
  4134. // NOTE reader.readResponse does not currently return Ext.data.Response
  4135. o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
  4136. },
  4137. /**
  4138. * Callback for write actions
  4139. * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
  4140. * @param {Object} trans The request transaction object
  4141. * @param {Object} res The server response
  4142. * @fires exception
  4143. * @fires write
  4144. * @protected
  4145. */
  4146. onWrite : function(action, o, response, rs) {
  4147. var reader = o.reader;
  4148. var res;
  4149. try {
  4150. res = reader.readResponse(action, response);
  4151. } catch (e) {
  4152. this.fireEvent('exception', this, 'response', action, o, response, e);
  4153. o.request.callback.call(o.request.scope, null, o.request.arg, false);
  4154. return;
  4155. }
  4156. if (res.success === true) {
  4157. this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
  4158. } else {
  4159. this.fireEvent('exception', this, 'remote', action, o, res, rs);
  4160. }
  4161. // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
  4162. // the calls to request.callback(...) in each will have to be made similar.
  4163. // NOTE reader.readResponse does not currently return Ext.data.Response
  4164. o.request.callback.call(o.request.scope, res.data, res, res.success);
  4165. },
  4166. // inherit docs
  4167. destroy: function(){
  4168. if(!this.useAjax){
  4169. this.conn.abort();
  4170. }else if(this.activeRequest){
  4171. var actions = Ext.data.Api.actions;
  4172. for (var verb in actions) {
  4173. if(this.activeRequest[actions[verb]]){
  4174. Ext.Ajax.abort(this.activeRequest[actions[verb]]);
  4175. }
  4176. }
  4177. }
  4178. Ext.data.HttpProxy.superclass.destroy.call(this);
  4179. }
  4180. });/**
  4181. * @class Ext.data.MemoryProxy
  4182. * @extends Ext.data.DataProxy
  4183. * An implementation of Ext.data.DataProxy that simply passes the data specified in its constructor
  4184. * to the Reader when its load method is called.
  4185. * @constructor
  4186. * @param {Object} data The data object which the Reader uses to construct a block of Ext.data.Records.
  4187. */
  4188. Ext.data.MemoryProxy = function(data){
  4189. // Must define a dummy api with "read" action to satisfy DataProxy#doRequest and Ext.data.Api#prepare *before* calling super
  4190. var api = {};
  4191. api[Ext.data.Api.actions.read] = true;
  4192. Ext.data.MemoryProxy.superclass.constructor.call(this, {
  4193. api: api
  4194. });
  4195. this.data = data;
  4196. };
  4197. Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
  4198. /**
  4199. * @event loadexception
  4200. * Fires if an exception occurs in the Proxy during data loading. Note that this event is also relayed
  4201. * through {@link Ext.data.Store}, so you can listen for it directly on any Store instance.
  4202. * @param {Object} this
  4203. * @param {Object} arg The callback's arg object passed to the {@link #load} function
  4204. * @param {Object} null This parameter does not apply and will always be null for MemoryProxy
  4205. * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data
  4206. */
  4207. /**
  4208. * MemoryProxy implementation of DataProxy#doRequest
  4209. * @param {String} action
  4210. * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
  4211. * @param {Object} params An object containing properties which are to be used as HTTP parameters
  4212. * for the request to the remote server.
  4213. * @param {Ext.data.DataReader} reader The Reader object which converts the data
  4214. * object into a block of Ext.data.Records.
  4215. * @param {Function} callback The function into which to pass the block of Ext.data.Records.
  4216. * The function must be passed <ul>
  4217. * <li>The Record block object</li>
  4218. * <li>The "arg" argument from the load function</li>
  4219. * <li>A boolean success indicator</li>
  4220. * </ul>
  4221. * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
  4222. * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
  4223. */
  4224. doRequest : function(action, rs, params, reader, callback, scope, arg) {
  4225. // No implementation for CRUD in MemoryProxy. Assumes all actions are 'load'
  4226. params = params || {};
  4227. var result;
  4228. try {
  4229. result = reader.readRecords(this.data);
  4230. }catch(e){
  4231. // @deprecated loadexception
  4232. this.fireEvent("loadexception", this, null, arg, e);
  4233. this.fireEvent('exception', this, 'response', action, arg, null, e);
  4234. callback.call(scope, null, arg, false);
  4235. return;
  4236. }
  4237. callback.call(scope, result, arg, true);
  4238. }
  4239. });/**
  4240. * @class Ext.data.Types
  4241. * <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
  4242. * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
  4243. * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
  4244. * of this class.</p>
  4245. * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
  4246. * each type definition must contain three properties:</p>
  4247. * <div class="mdetail-params"><ul>
  4248. * <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
  4249. * to be stored in the Field. The function is passed the collowing parameters:
  4250. * <div class="mdetail-params"><ul>
  4251. * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
  4252. * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
  4253. * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
  4254. * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
  4255. * ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
  4256. * </ul></div></div></li>
  4257. * <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
  4258. * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
  4259. * </ul></div>
  4260. * <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
  4261. * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
  4262. *<pre><code>
  4263. // Add a new Field data type which stores a VELatLong object in the Record.
  4264. Ext.data.Types.VELATLONG = {
  4265. convert: function(v, data) {
  4266. return new VELatLong(data.lat, data.long);
  4267. },
  4268. sortType: function(v) {
  4269. return v.Latitude; // When sorting, order by latitude
  4270. },
  4271. type: 'VELatLong'
  4272. };
  4273. </code></pre>
  4274. * <p>Then, when declaring a Record, use <pre><code>
  4275. var types = Ext.data.Types; // allow shorthand type access
  4276. UnitRecord = Ext.data.Record.create([
  4277. { name: 'unitName', mapping: 'UnitName' },
  4278. { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
  4279. { name: 'latitude', mapping: 'lat', type: types.FLOAT },
  4280. { name: 'latitude', mapping: 'lat', type: types.FLOAT },
  4281. { name: 'position', type: types.VELATLONG }
  4282. ]);
  4283. </code></pre>
  4284. * @singleton
  4285. */
  4286. Ext.data.Types = new function(){
  4287. var st = Ext.data.SortTypes;
  4288. Ext.apply(this, {
  4289. /**
  4290. * @type Regexp
  4291. * @property stripRe
  4292. * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
  4293. * This should be overridden for localization.
  4294. */
  4295. stripRe: /[\$,%]/g,
  4296. /**
  4297. * @type Object.
  4298. * @property AUTO
  4299. * This data type means that no conversion is applied to the raw data before it is placed into a Record.
  4300. */
  4301. AUTO: {
  4302. convert: function(v){ return v; },
  4303. sortType: st.none,
  4304. type: 'auto'
  4305. },
  4306. /**
  4307. * @type Object.
  4308. * @property STRING
  4309. * This data type means that the raw data is converted into a String before it is placed into a Record.
  4310. */
  4311. STRING: {
  4312. convert: function(v){ return (v === undefined || v === null) ? '' : String(v); },
  4313. sortType: st.asUCString,
  4314. type: 'string'
  4315. },
  4316. /**
  4317. * @type Object.
  4318. * @property INT
  4319. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  4320. * <p>The synonym <code>INTEGER</code> is equivalent.</p>
  4321. */
  4322. INT: {
  4323. convert: function(v){
  4324. return v !== undefined && v !== null && v !== '' ?
  4325. parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : 0;
  4326. },
  4327. sortType: st.none,
  4328. type: 'int'
  4329. },
  4330. /**
  4331. * @type Object.
  4332. * @property FLOAT
  4333. * This data type means that the raw data is converted into a number before it is placed into a Record.
  4334. * <p>The synonym <code>NUMBER</code> is equivalent.</p>
  4335. */
  4336. FLOAT: {
  4337. convert: function(v){
  4338. return v !== undefined && v !== null && v !== '' ?
  4339. parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : 0;
  4340. },
  4341. sortType: st.none,
  4342. type: 'float'
  4343. },
  4344. /**
  4345. * @type Object.
  4346. * @property BOOL
  4347. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  4348. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  4349. * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
  4350. */
  4351. BOOL: {
  4352. convert: function(v){ return v === true || v === 'true' || v == 1; },
  4353. sortType: st.none,
  4354. type: 'bool'
  4355. },
  4356. /**
  4357. * @type Object.
  4358. * @property DATE
  4359. * This data type means that the raw data is converted into a Date before it is placed into a Record.
  4360. * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
  4361. * being applied.
  4362. */
  4363. DATE: {
  4364. convert: function(v){
  4365. var df = this.dateFormat;
  4366. if(!v){
  4367. return null;
  4368. }
  4369. if(Ext.isDate(v)){
  4370. return v;
  4371. }
  4372. if(df){
  4373. if(df == 'timestamp'){
  4374. return new Date(v*1000);
  4375. }
  4376. if(df == 'time'){
  4377. return new Date(parseInt(v, 10));
  4378. }
  4379. return Date.parseDate(v, df);
  4380. }
  4381. var parsed = Date.parse(v);
  4382. return parsed ? new Date(parsed) : null;
  4383. },
  4384. sortType: st.asDate,
  4385. type: 'date'
  4386. }
  4387. });
  4388. Ext.apply(this, {
  4389. /**
  4390. * @type Object.
  4391. * @property BOOLEAN
  4392. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  4393. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  4394. * <p>The synonym <code>BOOL</code> is equivalent.</p>
  4395. */
  4396. BOOLEAN: this.BOOL,
  4397. /**
  4398. * @type Object.
  4399. * @property INTEGER
  4400. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  4401. * <p>The synonym <code>INT</code> is equivalent.</p>
  4402. */
  4403. INTEGER: this.INT,
  4404. /**
  4405. * @type Object.
  4406. * @property NUMBER
  4407. * This data type means that the raw data is converted into a number before it is placed into a Record.
  4408. * <p>The synonym <code>FLOAT</code> is equivalent.</p>
  4409. */
  4410. NUMBER: this.FLOAT
  4411. });
  4412. };