Ext.namespace('Zarafa.core.data'); /** * @class Zarafa.core.data.NoSyncStore * @extends Ext.util.Observable * * The {@link Zarafa.core.data.NoSyncStore NoSyncStore} represents the collection of * {@link Ext.data.Record records}. It offers the same interface * as {@link Zarafa.core.data.Store Store} without any CRUD operations being * send to the server. This implies that the {@link Zarafa.core.data.NoSyncStore NoSyncStore} * will only work for working on {@link Ext.data.Record records} offline. */ Zarafa.core.data.NoSyncStore = Ext.extend(Ext.util.Observable, { /** * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the * {@link Ext.data.DataReader Reader}. Read-only. * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects, * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p> * <p>This property may be used to create new Records of the type held in this Store * @property recordType * @type Function */ recordType : undefined, /** * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s * for the {@link Ext.data.Record Records} stored in this Store. Read-only. * @property fields * @type Ext.util.MixedCollection */ fields : undefined, /** * True if this store is currently sorted by more than one field/direction combination. * @property * @type Boolean */ hasMultiSort: false, /** * Object containing the current sorting information. * @property * @type Object */ sortToggle : undefined, /** * @cfg {String} sortField * (optional) Initial column on which to sort. */ sortField : undefined, /** * @cfg {String} sortDir * (Optional) Initial direction to sort (<code>"ASC"</code> or <code>"DESC"</code>). Defaults to * <code>"ASC"</code>. */ sortDir : 'ASC', /** * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's load operation. * Note that for local sorting, the direction property is case-sensitive. */ sortInfo : undefined, /** * @constructor * @param config Configuration structure */ constructor : function(config) { config = config || {}; Ext.apply(this, config); // If the recordType is provided, we can obtain the fields. if (this.recordType) { this.fields = this.recordType.prototype.fields; } this.addEvents( /** * @event add * Fires when Records have been {@link #add}ed to the Store * @param {Store} this * @param {Ext.data.Record[]} records The array of Records added * @param {Number} index The index at which the record(s) were added */ 'add', /** * @event remove * Fires when a Record has been {@link #remove}d from the Store * @param {Store} this * @param {Ext.data.Record} record The Record that was removed * @param {Number} index The index at which the record was removed */ 'remove', /** * @event update * Fires when a Record has been updated * @param {Store} this * @param {Ext.data.Record} record The Record that was updated * @param {String} operation The update operation being performed. Value may be one of: * <pre><code> Ext.data.Record.EDIT Ext.data.Record.REJECT Ext.data.Record.COMMIT * </code></pre> */ 'update', /** * @event clear * Fires when the data cache has been cleared. * @param {Store} this * @param {Record[]} The records that were cleared. */ 'clear' ); this.sortToggle = {}; if (this.sortField){ this.setDefaultSort(this.sortField, this.sortDir); } else if(this.sortInfo) { this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction); } Zarafa.core.data.NoSyncStore.superclass.constructor.call(this, config); this.initEvents(); this.initData(); }, /** * Initialize data structures in which the {@link Ext.data.Record records} are stored. * @private */ initData : function() { this.data = new Ext.util.MixedCollection(false); this.data.getKey = function(o){ return o.id; }; this.removed = []; this.modified = []; }, /** * Initialize events which can be raised by the {@link Zarafa.core.data.NoSyncStore NoSyncStore} * @private */ initEvents : function() { this.on({ scope: this, add: this.createRecords, remove: this.destroyRecord, clear: this.onClear }); }, /** * Destroys the store. */ destroy : function() { if (!this.isDestroyed) { this.clearData(); this.data = null; this.purgeListeners(); this.isDestroyed = true; } }, /** * Add Records to the Store and fires the {@link #add} event. * See also <code>{@link #insert}</code>. * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects * to add to the cache. * @param {Boolean} silent [false] Defaults to <tt>false</tt>. * Set <tt>true</tt> to not fire add event. */ add : function(records, silent) { records = [].concat(records); if(records.length < 1) { return; } for (var i = 0, len = records.length; i < len; i++) { records[i].join(this); } var index = this.data.length; this.data.addAll(records); if(this.snapshot){ this.snapshot.addAll(records); } if (silent !== true) { this.fireEvent('add', this, records, index); } }, /** * Remove Records from the Store and fires the {@link #remove} event. * @param {Ext.data.Record/Ext.data.Record[]} record The record object or array of records to remove from the cache. * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire remove event. */ remove : function(record, silent) { if (Array.isArray(record)) { Ext.each(record, function(r){ this.remove(r, silent); }, this); } var index = this.data.indexOf(record); if(this.snapshot){ this.snapshot.remove(record); } if (index > -1) { record.join(null); this.data.removeAt(index); this.modified.remove(record); if (silent !== true) { this.fireEvent('remove', this, record, index); } } }, /** * Remove a Record from the Store at the specified index. Fires the {@link #remove} event. * @param {Number} index The index of the record to remove. * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire remove event. */ removeAt : function(index, silent) { this.remove(this.getAt(index), silent); }, /** * Remove all Records from the Store and fires the {@link #clear} event. * @param {Boolean} silent [false] Defaults to <tt>false</tt>. Set <tt>true</tt> to not fire clear event. */ removeAll : function(silent) { var items = []; this.each(function(rec){ items.push(rec); }); this.clearData(); if(this.snapshot){ this.snapshot.clear(); } this.modified = []; this.removed = []; if (silent !== true) { this.fireEvent('clear', this, items); } }, /** * Remove all Records for which the callback returns true * from the Store and fires the {@link #remove} event. * * @param {Function} callback The callback function which is used to determine * if a record must be removed. Function must accept a {@link Ext.data.Record} * as argument. * @param {Object} scope The scope which must be used for the callback function */ removeIf : function(callback, scope) { this.each(function(record) { if (callback.call(scope || this, record)) { this.remove(record); } }, this); }, /** * Inserts Records into the Store at the given index and fires the {@link #add} event. * See also <code>{@link #add}</code>. * @param {Number} index The start index at which to insert the passed Records. * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache. */ insert : function(index, records) { records = [].concat(records); for (var i = 0, len = records.length; i < len; i++) { this.data.insert(index, records[i]); records[i].join(this); } if(this.snapshot){ this.snapshot.addAll(records); } this.fireEvent('add', this, records, index); }, /** * Get the index within the cache of the passed Record. * @param {Ext.data.Record} record The Ext.data.Record object to find. * @return {Number} The index of the passed Record. Returns -1 if not found. */ indexOf : function(record) { return this.data.indexOf(record); }, /** * Get the index within the cache of the Record with the passed id. * @param {String} id The id of the Record to find. * @return {Number} The index of the Record. Returns -1 if not found. */ indexOfId : function(id) { return this.data.indexOfKey(id); }, /** * Get the Record at the specified index. * @param {Number} index The index of the Record to find. * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found. */ getAt : function(index) { return this.data.itemAt(index); }, /** * Returns a range of Records between specified indices. * @param {Number} startIndex (optional) The starting index (defaults to 0) * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store) * @return {Ext.data.Record[]} An array of Records */ getRange : function(start, end) { return this.data.getRange(start, end); }, /** * Gets the number of cached records * @return {Number} The number of records */ getCount : function() { return this.data.length; }, /** * Gets the total number of records in the dataset. * @return {Number} The number of records */ getTotalCount : function() { return this.getCount(); }, /** * 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); }, /** * Gets all {@link Ext.data.Record records} modified since the last commit. * <b>Note</b>: deleted records are not included. * See also {@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; }, /** * Gets all {@link Ext.data.Record records} removed since the last commit. * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} which have * been marked as removed. */ getRemovedRecords : function() { return this.removed; }, /** * Called by {@link Ext.data.Record} * @param {Ext.data.Record} The record which has been edited. * @private */ afterEdit : function(record) { if(this.modified.indexOf(record) === -1) { this.modified.push(record); } this.fireEvent('update', this, record, Ext.data.Record.EDIT); }, /** * Called by {@link Ext.data.Record} * @param {Ext.data.Record} The record which has been rejected. * @private */ afterReject : function(record) { this.modified.remove(record); this.fireEvent('update', this, record, Ext.data.Record.REJECT); }, /** * Called by {@link Ext.data.Record} * @param {Ext.data.Record} The record which has been committed. * @private */ afterCommit : function(record) { this.modified.remove(record); this.fireEvent('update', this, record, Ext.data.Record.COMMIT); }, /** * 'Commit' outstanding changes. Since {@link Zarafa.core.data.NoSyncStore NoSyncStore} * has no commit capability, changes are not actually sent, but are only cleared. */ commitChanges : function() { var m = this.modified.slice(0); for(var i = 0, len = m.length; i < len; i++){ var mi = m[i]; // Committing means unphantoming. mi.phantom = false; mi.commit(); } this.modified = []; this.removed = []; }, /** * Clear the data within this store * @private */ clearData: function() { this.data.clear(); }, /** * Should not be used directly. Store#add will call this automatically * @param {Object} store * @param {Object} rs * @param {Object} index * @private */ createRecords : function(store, rs, index) { for (var i = 0, len = rs.length; i < len; i++) { if (rs[i].phantom && rs[i].isValid()) { rs[i].markDirty(); // <-- Mark new records dirty this.modified.push(rs[i]); // <-- add to modified } } }, /** * Destroys a record or records. Should not be used directly. It's called by Store#remove automatically * @param {Store} store * @param {Ext.data.Record/Ext.data.Record[]} record * @param {Number} index * @private */ destroyRecord : function(store, record, index) { if (this.modified.indexOf(record) !== -1) { this.modified.remove(record); } if (!record.phantom) { this.removed.push(record); } }, /** * Clears all records. Show not be used directly. It's called by Store#removeAll automatically * @param {Store} store * @param {Ext.data.Record/Ext.data/Record[]} records * @private */ onClear: function(store, records) { Ext.each(records, function(rec, index) { this.destroyRecord(this, rec, index); }, this); }, /** * Returns an object describing the current sort state of this Store. * @return {Object} The sort state of the Store. An object with two properties: * field : String The name of the field by which the Records are sorted. * direction : String The sort order, 'ASC' or 'DESC' (case-sensitive). * * Added for grid support with store, grid's store needs sortinfo. * * See <tt>{@link #sortInfo}</tt> for additional details. */ getSortState : Ext.data.Store.prototype.getSortState, /** * Invokes sortData if we have sortInfo to sort on and are not sorting remotely * @private */ applySort : Ext.data.Store.prototype.applySort, /** * 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 * @private */ sortData : Ext.data.Store.prototype.sortData, /** * 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 * @private */ createSortFunction : Ext.data.Store.prototype.createSortFunction, /** * 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 : Ext.data.Store.prototype.setDefaultSort, /** * 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 : Ext.data.Store.prototype.sort, /** * 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 : Ext.data.Store.prototype.singleSort, /** * 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 : Ext.data.Store.prototype.multiSort, /** * Sums the value of property for each record between start and end and returns the result * @param {String} property A field in each record * @param {Number} start (optional) The record index to start at (defaults to 0) * @param {Number} end (optional) The last record index to include (defaults to length - 1) * @return {Number} The sum */ sum : Ext.data.Store.prototype.sum, /** * Returns a filter function used to test a the given property's value. Defers most of the work to * Ext.util.MixedCollection's createValueMatcher function * @param {String} property The property to create the filter function for * @param {String/RegExp} value The string/regex to compare the property value to * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false) * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false) * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true. * @private */ createFilterFn : Ext.data.Store.prototype.createFilterFn, /** * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child) * @param {Array} el An array of elements to filter * @param {String} selector The simple selector to test * @param {Boolean} nonMatches If true, it returns the elements that DON'T match * the selector instead of the ones that match * @return {Array} An Array of DOM elements which match the selector. If there are * no matches, and empty Array is returned. */ filter : Ext.data.Store.prototype.filter, /** * Filter by a function. Returns a <i>new</i> collection that has been filtered. * The passed function will be called with each object in the collection. * If the function returns true, the value is included otherwise it is filtered. * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key) * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection. * @return {MixedCollection} The new filtered collection */ filterBy : Ext.data.Store.prototype.filterBy, /** * Query the records by a specified property. * @param {String} field A field on your records * @param {String/RegExp} value Either a string that the field * should begin with, or a RegExp to test against the field. * @param {Boolean} anyMatch (optional) True to match any part not just the beginning * @param {Boolean} caseSensitive (optional) True for case sensitive comparison * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records */ query : Ext.data.Store.prototype.query, /** * Query the cached records in this Store using a filtering function. The specified function * will be called with each record in this Store. If the function returns <tt>true</tt> the record is * included in the results. * @param {Function} fn The function to be called. It will be passed the following parameters:<ul> * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record} * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li> * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li> * </ul> * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store. * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records **/ queryBy : Ext.data.Store.prototype.queryBy, /** * Finds the index of the first matching Record in this store by a specific field value. * @param {String} fieldName The name of the Record field to test. * @param {String/RegExp} value Either a string that the field value * should begin with, or a RegExp to test against the field. * @param {Number} startIndex (optional) The index to start searching at * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning * @param {Boolean} caseSensitive (optional) True for case sensitive comparison * @return {Number} The matched index or -1 */ find : Ext.data.Store.prototype.find, /** * Finds the index of the first matching Record in this store by a specific field value. * @param {String} fieldName The name of the Record field to test. * @param {Mixed} value The value to match the field against. * @param {Number} startIndex (optional) The index to start searching at * @return {Number} The matched index or -1 */ findExact : Ext.data.Store.prototype.findExact, /** * Find the index of the first matching Record in this Store by a function. * If the function returns <tt>true</tt> it is considered a match. * @param {Function} fn The function to be called. It will be passed the following parameters:<ul> * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record} * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li> * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li> * </ul> * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store. * @param {Number} startIndex (optional) The index to start searching at * @return {Number} The matched index or -1 */ findBy : Ext.data.Store.prototype.findBy, /** * Get the Record with the specified id. * @param {String} id The id of the Record to find. * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found. */ getById : Ext.data.Store.prototype.getById, /** * Revert to a view of the Record cache with no filtering applied. * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the * {@link #datachanged} event. */ clearFilter : Ext.data.Store.prototype.clearFilter, /** * Returns true if this store is currently filtered * @return {Boolean} */ isFiltered : Ext.data.Store.prototype.isFiltered, /** * Collects unique values for a particular dataIndex from this store. * @param {String} dataIndex The property to collect * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered * @return {Array} An array of the unique values **/ collect : Ext.data.Store.prototype.collect, /** * When store's reader provides new metadata (fields) this function is called. * @param {Object} meta The JSON metadata * @private */ onMetaChange : Ext.data.Store.prototype.onMetaChange });