- }else if(!rs[i].isValid()){ // <-- while we're here, splice-off any !isValid real records
- rs.splice(i,1);
- }
- }
- // If we have valid phantoms, create them...
- if(phantoms.length){
- queue.push(['create', phantoms]);
- }
-
- // UPDATE: And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
- if(rs.length){
- queue.push(['update', rs]);
- }
- }
- len = queue.length;
- if(len){
- batch = ++this.batchCounter;
- for(var i = 0; i < len; ++i){
- trans = queue[i];
- data[trans[0]] = trans[1];
- }
- if(this.fireEvent('beforesave', this, data) !== false){
- for(var i = 0; i < len; ++i){
- trans = queue[i];
- this.doTransaction(trans[0], trans[1], batch);
- }
- return batch;
- }
- }
- return -1;
- },
-
- // private. Simply wraps call to Store#execute in try/catch. Defers to Store#handleException on error. Loops if batch: false
- doTransaction : function(action, rs, batch) {
- function transaction(records) {
- try{
- this.execute(action, records, undefined, batch);
- }catch (e){
- this.handleException(e);
- }
- }
- if(this.batch === false){
- for(var i = 0, len = rs.length; i < len; i++){
- transaction.call(this, rs[i]);
- }
- }else{
- transaction.call(this, rs);
- }
- },
-
- // private
- addToBatch : function(batch){
- var b = this.batches,
- key = this.batchKey + batch,
- o = b[key];
-
- if(!o){
- b[key] = o = {
- id: batch,
- count: 0,
- data: {}
- };
- }
- ++o.count;
- },
-
- removeFromBatch : function(batch, action, data){
- var b = this.batches,
- key = this.batchKey + batch,
- o = b[key],
- data,
- arr;
-
-
- if(o){
- arr = o.data[action] || [];
- o.data[action] = arr.concat(data);
- if(o.count === 1){
- data = o.data;
- delete b[key];
- this.fireEvent('save', this, batch, data);
- }else{
- --o.count;
- }
- }
- },
-
- // @private callback-handler for remote CRUD actions
- // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
- createCallback : function(action, rs, batch) {
- var actions = Ext.data.Api.actions;
- return (action == 'read') ? this.loadRecords : function(data, response, success) {
- // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
- this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data));
- // If success === false here, exception will have been called in DataProxy
- if (success === true) {
- this.fireEvent('write', this, action, data, response, rs);
- }
- this.removeFromBatch(batch, action, data);
- };
- },
-
- // Clears records from modified array after an exception event.
- // NOTE: records are left marked dirty. Do we want to commit them even though they were not updated/realized?
- // TODO remove this method?
- clearModified : function(rs) {
- if (Ext.isArray(rs)) {
- for (var n=rs.length-1;n>=0;n--) {
- this.modified.splice(this.modified.indexOf(rs[n]), 1);
- }
- } else {
- this.modified.splice(this.modified.indexOf(rs), 1);
- }
- },
-
- // remap record ids in MixedCollection after records have been realized. @see Store#onCreateRecords, @see DataReader#realize
- reMap : function(record) {
- if (Ext.isArray(record)) {
- for (var i = 0, len = record.length; i < len; i++) {
- this.reMap(record[i]);
- }
- } else {
- delete this.data.map[record._phid];
- this.data.map[record.id] = record;
- var index = this.data.keys.indexOf(record._phid);
- this.data.keys.splice(index, 1, record.id);
- delete record._phid;
- }
- },
-
- // @protected onCreateRecord proxy callback for create action
- onCreateRecords : function(success, rs, data) {
- if (success === true) {
- try {
- this.reader.realize(rs, data);
- this.reMap(rs);
- }
- catch (e) {
- this.handleException(e);
- if (Ext.isArray(rs)) {
- // Recurse to run back into the try {}. DataReader#realize splices-off the rs until empty.
- this.onCreateRecords(success, rs, data);
- }
- }
- }
- },
-
- // @protected, onUpdateRecords proxy callback for update action
- onUpdateRecords : function(success, rs, data) {
- if (success === true) {
- try {
- this.reader.update(rs, data);
- } catch (e) {
- this.handleException(e);
- if (Ext.isArray(rs)) {
- // Recurse to run back into the try {}. DataReader#update splices-off the rs until empty.
- this.onUpdateRecords(success, rs, data);
- }
- }
- }
- },
-
- // @protected onDestroyRecords proxy callback for destroy action
- onDestroyRecords : function(success, rs, data) {
- // splice each rec out of this.removed
- rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs);
- for (var i=0,len=rs.length;i<len;i++) {
- this.removed.splice(this.removed.indexOf(rs[i]), 1);
- }
- if (success === false) {
- // put records back into store if remote destroy fails.
- // @TODO: Might want to let developer decide.
- for (i=rs.length-1;i>=0;i--) {
- this.insert(rs[i].lastIndex, rs[i]); // <-- lastIndex set in Store#destroyRecord
- }
- }
- },
-
- // protected handleException. Possibly temporary until Ext framework has an exception-handler.
- handleException : function(e) {
- // @see core/Error.js
- Ext.handleError(e);
- },
-
- <div id="method-Ext.data.Store-reload"></div>/**
- * <p>Reloads the Record cache from the configured Proxy using the configured
- * {@link Ext.data.Reader Reader} and the options from the last load operation
- * performed.</p>
- * <p><b>Note</b>: see the Important note in {@link #load}.</p>
- * @param {Object} options <p>(optional) An <tt>Object</tt> containing
- * {@link #load loading options} which may override the {@link #lastOptions options}
- * used in the last {@link #load} operation. See {@link #load} for details
- * (defaults to <tt>null</tt>, in which case the {@link #lastOptions} are
- * used).</p>
- * <br><p>To add new params to the existing params:</p><pre><code>
-lastOptions = myStore.lastOptions;
-Ext.apply(lastOptions.params, {
- myNewParam: true
-});
-myStore.reload(lastOptions);
- * </code></pre>
- */
- reload : function(options){
- this.load(Ext.applyIf(options||{}, this.lastOptions));
- },
-
- // private
- // Called as a callback by the Reader during a load operation.
- loadRecords : function(o, options, success){
- if (this.isDestroyed === true) {
- return;
- }
- if(!o || success === false){
- if(success !== false){
- this.fireEvent('load', this, [], options);
- }
- if(options.callback){
- options.callback.call(options.scope || this, [], options, false, o);
- }
- return;
- }
- var r = o.records, t = o.totalRecords || r.length;
- if(!options || options.add !== true){
- if(this.pruneModifiedRecords){
- this.modified = [];
- }
- for(var i = 0, len = r.length; i < len; i++){
- r[i].join(this);
- }
- if(this.snapshot){
- this.data = this.snapshot;
- delete this.snapshot;
- }
- this.clearData();
- this.data.addAll(r);
- this.totalLength = t;
- this.applySort();
- this.fireEvent('datachanged', this);
- }else{
- this.totalLength = Math.max(t, this.data.length+r.length);
- this.add(r);
- }
- this.fireEvent('load', this, r, options);
- if(options.callback){
- options.callback.call(options.scope || this, r, options, true);
- }
- },
-
- <div id="method-Ext.data.Store-loadData"></div>/**
- * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
- * which understands the format of the data must have been configured in the constructor.
- * @param {Object} data The data block from which to read the Records. The format of the data expected
- * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
- * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
- * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
- * the existing cache.
- * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
- * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
- * new, unique ids will be added.
- */
- loadData : function(o, append){
- var r = this.reader.readRecords(o);
- this.loadRecords(r, {add: append}, true);
- },
-
- <div id="method-Ext.data.Store-getCount"></div>/**
- * Gets the number of cached records.
- * <p>If using paging, this may not be the total size of the dataset. If the data object
- * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
- * the dataset size. <b>Note</b>: see the Important note in {@link #load}.</p>
- * @return {Number} The number of Records in the Store's cache.
- */
- getCount : function(){
- return this.data.length || 0;
- },
-
- <div id="method-Ext.data.Store-getTotalCount"></div>/**
- * Gets the total number of records in the dataset as returned by the server.
- * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
- * must contain the dataset size. For remote data sources, the value for this property
- * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
- * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
- * <b>Note</b>: see the Important note in {@link #load}.</p>
- * @return {Number} The number of Records as specified in the data object passed to the Reader
- * by the Proxy.
- * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
- */
- getTotalCount : function(){
- return this.totalLength || 0;
- },
-
- <div id="method-Ext.data.Store-getSortState"></div>/**
- * Returns an object describing the current sort state of this Store.
- * @return {Object} The sort state of the Store. An object with two properties:<ul>
- * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
- * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
- * </ul>
- * See <tt>{@link #sortInfo}</tt> for additional details.
- */
- getSortState : function(){
- return this.sortInfo;
- },
-
- /**
- * @private
- * Invokes sortData if we have sortInfo to sort on and are not sorting remotely
- */
- applySort : function(){
- if ((this.sortInfo || this.multiSortInfo) && !this.remoteSort) {
- this.sortData();
- }
- },
-
- /**
- * @private
- * Performs the actual sorting of data. This checks to see if we currently have a multi sort or not. It applies
- * each sorter field/direction pair in turn by building an OR'ed master sorting function and running it against
- * the full dataset
- */
- sortData : function() {
- var sortInfo = this.hasMultiSort ? this.multiSortInfo : this.sortInfo,
- direction = sortInfo.direction || "ASC",
- sorters = sortInfo.sorters,
- sortFns = [];
-
- //if we just have a single sorter, pretend it's the first in an array
- if (!this.hasMultiSort) {
- sorters = [{direction: direction, field: sortInfo.field}];
- }
-
- //create a sorter function for each sorter field/direction combo
- for (var i=0, j = sorters.length; i < j; i++) {
- sortFns.push(this.createSortFunction(sorters[i].field, sorters[i].direction));
- }
-
- if (sortFns.length == 0) {
- return;
- }
-
- //the direction modifier is multiplied with the result of the sorting functions to provide overall sort direction
- //(as opposed to direction per field)
- var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
-
- //create a function which ORs each sorter together to enable multi-sort
- var fn = function(r1, r2) {
- var result = sortFns[0].call(this, r1, r2);
-
- //if we have more than one sorter, OR any additional sorter functions together
- if (sortFns.length > 1) {
- for (var i=1, j = sortFns.length; i < j; i++) {
- result = result || sortFns[i].call(this, r1, r2);
- }
- }
-
- return directionModifier * result;
- };
-
- //sort the data
- this.data.sort(direction, fn);
- if (this.snapshot && this.snapshot != this.data) {
- this.snapshot.sort(direction, fn);
- }
- },
-
- /**
- * @private
- * Creates and returns a function which sorts an array by the given field and direction
- * @param {String} field The field to create the sorter for
- * @param {String} direction The direction to sort by (defaults to "ASC")
- * @return {Function} A function which sorts by the field/direction combination provided
- */
- createSortFunction: function(field, direction) {
- direction = direction || "ASC";
- var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
-
- var sortType = this.fields.get(field).sortType;
-
- //create a comparison function. Takes 2 records, returns 1 if record 1 is greater,
- //-1 if record 2 is greater or 0 if they are equal
- return function(r1, r2) {
- var v1 = sortType(r1.data[field]),
- v2 = sortType(r2.data[field]);
-
- return directionModifier * (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
- };
- },
-
- <div id="method-Ext.data.Store-setDefaultSort"></div>/**
- * Sets the default sort column and order to be used by the next {@link #load} operation.
- * @param {String} fieldName The name of the field to sort by.
- * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
- */
- setDefaultSort : function(field, dir) {
- dir = dir ? dir.toUpperCase() : 'ASC';
- this.sortInfo = {field: field, direction: dir};
- this.sortToggle[field] = dir;
- },
-
- <div id="method-Ext.data.Store-sort"></div>/**
- * Sort the Records.
- * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
- * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
- * This function accepts two call signatures - pass in a field name as the first argument to sort on a single
- * field, or pass in an array of sort configuration objects to sort by multiple fields.
- * Single sort example:
- * store.sort('name', 'ASC');
- * Multi sort example:
- * store.sort([
- * {
- * field : 'name',
- * direction: 'ASC'
- * },
- * {
- * field : 'salary',
- * direction: 'DESC'
- * }
- * ], 'ASC');
- * In this second form, the sort configs are applied in order, with later sorters sorting within earlier sorters' results.
- * For example, if two records with the same name are present they will also be sorted by salary if given the sort configs
- * above. Any number of sort configs can be added.
- * @param {String/Array} fieldName The name of the field to sort by, or an array of ordered sort configs
- * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
- */
- sort : function(fieldName, dir) {
- if (Ext.isArray(arguments[0])) {
- return this.multiSort.call(this, fieldName, dir);
- } else {
- return this.singleSort(fieldName, dir);
- }
- },
-
- <div id="method-Ext.data.Store-singleSort"></div>/**
- * Sorts the store contents by a single field and direction. This is called internally by {@link sort} and would
- * not usually be called manually
- * @param {String} fieldName The name of the field to sort by.
- * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
- */
- singleSort: function(fieldName, dir) {
- var field = this.fields.get(fieldName);
- if (!field) return false;
-
- var name = field.name,
- sortInfo = this.sortInfo || null,
- sortToggle = this.sortToggle ? this.sortToggle[name] : null;
-
- if (!dir) {
- if (sortInfo && sortInfo.field == name) { // toggle sort dir
- dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
- } else {
- dir = field.sortDir;
- }
- }
-
- this.sortToggle[name] = dir;
- this.sortInfo = {field: name, direction: dir};
- this.hasMultiSort = false;
-
- if (this.remoteSort) {
- if (!this.load(this.lastOptions)) {
- if (sortToggle) {
- this.sortToggle[name] = sortToggle;
- }
- if (sortInfo) {
- this.sortInfo = sortInfo;
- }
- }
- } else {
- this.applySort();
- this.fireEvent('datachanged', this);
- }
- },
-
- <div id="method-Ext.data.Store-multiSort"></div>/**
- * Sorts the contents of this store by multiple field/direction sorters. This is called internally by {@link sort}
- * and would not usually be called manually.
- * Multi sorting only currently applies to local datasets - multiple sort data is not currently sent to a proxy
- * if remoteSort is used.
- * @param {Array} sorters Array of sorter objects (field and direction)
- * @param {String} direction Overall direction to sort the ordered results by (defaults to "ASC")
- */
- multiSort: function(sorters, direction) {
- this.hasMultiSort = true;
- direction = direction || "ASC";
-
- //toggle sort direction
- if (this.multiSortInfo && direction == this.multiSortInfo.direction) {
- direction = direction.toggle("ASC", "DESC");
- }
-
- <div id="prop-Ext.data.Store-multiSortInfo"></div>/**
- * Object containing overall sort direction and an ordered array of sorter configs used when sorting on multiple fields
- * @property multiSortInfo
- * @type Object
- */
- this.multiSortInfo = {
- sorters : sorters,
- direction: direction
- };
-
- if (this.remoteSort) {
- this.singleSort(sorters[0].field, sorters[0].direction);
-
- } else {
- this.applySort();
- this.fireEvent('datachanged', this);
- }
- },
-
- <div id="method-Ext.data.Store-each"></div>/**
- * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
- * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
- * Returning <tt>false</tt> aborts and exits the iteration.
- * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
- * Defaults to the current {@link Ext.data.Record Record} in the iteration.
- */
- each : function(fn, scope){
- this.data.each(fn, scope);
- },
-
- <div id="method-Ext.data.Store-getModifiedRecords"></div>/**
- * Gets all {@link Ext.data.Record records} modified since the last commit. Modified records are
- * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
- * included. See also <tt>{@link #pruneModifiedRecords}</tt> and
- * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
- * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
- * modifications. To obtain modified fields within a modified record see
- *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
- */
- getModifiedRecords : function(){
- return this.modified;
- },