- }
- }
- },
-
- // @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){
- var i;
-
- 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(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{
- var toAdd = [],
- rec,
- cnt = 0;
- for(i = 0, len = r.length; i < len; ++i){
- rec = r[i];
- if(this.indexOfId(rec.id) > -1){
- this.doUpdate(rec);
- }else{
- toAdd.push(rec);
- ++cnt;
- }
- }
- this.totalLength = Math.max(t, this.data.length + cnt);
- this.add(toAdd);
- }
- 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);
- }
- return true;
- },
-
- <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;
- },
-
- <div id="method-Ext.data.Store-sum"></div>/**
- * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
- * and <tt>end</tt> and returns the result.
- * @param {String} property A field in each record
- * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
- * @param {Number} end (optional) The last record index to include (defaults to length - 1)
- * @return {Number} The sum
- */
- sum : function(property, start, end){
- var rs = this.data.items, v = 0;
- start = start || 0;
- end = (end || end === 0) ? end : rs.length-1;
-
- for(var i = start; i <= end; i++){
- v += (rs[i].data[property] || 0);
- }
- return v;
- },
-
- /**
- * @private
- * 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.
- */
- createFilterFn : function(property, value, anyMatch, caseSensitive, exactMatch){
- if(Ext.isEmpty(value, false)){
- return false;
- }
- value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
- return function(r) {
- return value.test(r.data[property]);
- };
- },
-
- /**
- * @private
- * Given an array of filter functions (each with optional scope), constructs and returns a single function that returns
- * the result of all of the filters ANDed together
- * @param {Array} filters The array of filter objects (each object should contain an 'fn' and optional scope)
- * @return {Function} The multiple filter function
- */
- createMultipleFilterFn: function(filters) {
- return function(record) {
- var isMatch = true;
-
- for (var i=0, j = filters.length; i < j; i++) {
- var filter = filters[i],
- fn = filter.fn,
- scope = filter.scope;
-
- isMatch = isMatch && fn.call(scope, record);
- }
-
- return isMatch;
- };
- },
-
- <div id="method-Ext.data.Store-filter"></div>/**
- * Filter the {@link Ext.data.Record records} by a specified property. Alternatively, pass an array of filter
- * options to filter by more than one property.
- * Single filter example:
- * store.filter('name', 'Ed', true, true); //finds all records containing the substring 'Ed'
- * Multiple filter example:
- * <pre><code>
- * store.filter([
- * {
- * property : 'name',
- * value : 'Ed',
- * anyMatch : true, //optional, defaults to true
- * caseSensitive: true //optional, defaults to true
- * },
- *
- * //filter functions can also be passed
- * {
- * fn : function(record) {
- * return record.get('age') == 24
- * },
- * scope: this
- * }
- * ]);
- * </code></pre>
- * @param {String|Array} field A field on your records, or an array containing multiple filter options
- * @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) <tt>true</tt> to match any part not just the beginning
- * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
- * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
- */
- filter : function(property, value, anyMatch, caseSensitive, exactMatch){
- var fn;
- //we can accept an array of filter objects, or a single filter object - normalize them here
- if (Ext.isObject(property)) {
- property = [property];
- }
-
- if (Ext.isArray(property)) {
- var filters = [];
-
- //normalize the filters passed into an array of filter functions
- for (var i=0, j = property.length; i < j; i++) {
- var filter = property[i],
- func = filter.fn,
- scope = filter.scope || this;
-
- //if we weren't given a filter function, construct one now
- if (!Ext.isFunction(func)) {
- func = this.createFilterFn(filter.property, filter.value, filter.anyMatch, filter.caseSensitive, filter.exactMatch);
- }
-
- filters.push({fn: func, scope: scope});
- }
-
- fn = this.createMultipleFilterFn(filters);
- } else {
- //classic single property filter
- fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
- }
-
- return fn ? this.filterBy(fn) : this.clearFilter();
- },
-
- <div id="method-Ext.data.Store-filterBy"></div>/**
- * Filter by a function. The specified function will be called for each
- * Record in this Store. If the function returns <tt>true</tt> the Record is included,
- * otherwise it is filtered out.
- * @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.
- */
- filterBy : function(fn, scope){
- this.snapshot = this.snapshot || this.data;
- this.data = this.queryBy(fn, scope || this);
- this.fireEvent('datachanged', this);
- },
-
- <div id="method-Ext.data.Store-clearFilter"></div>/**
- * 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 : function(suppressEvent){
- if(this.isFiltered()){
- this.data = this.snapshot;
- delete this.snapshot;
- if(suppressEvent !== true){
- this.fireEvent('datachanged', this);
- }