X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6e39d509471fe9b4e2660e0d1631b350d0c66f40..b37ceabb82336ee82757cd32efe353cfab8ec267:/docs/source/Store.html diff --git a/docs/source/Store.html b/docs/source/Store.html index f10831e5..67c8632e 100644 --- a/docs/source/Store.html +++ b/docs/source/Store.html @@ -1,12 +1,18 @@ - -
- -/** + + + ++ \ No newline at end of fileThe source code + + + + +/*! + * Ext JS Library 3.2.2 + * Copyright(c) 2006-2010 Ext JS, Inc. + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** * @class Ext.data.Store * @extends Ext.util.Observable *- +The Store class encapsulates a client side cache of {@link Ext.data.Record Record} @@ -260,6 +266,18 @@ sortInfo: { dir : 'dir' }, +
/** + * @property {Boolean} isDestroyed + * True if the store has been destroyed already. Read only + */ + isDestroyed: false, + + /** + * @property {Boolean} hasMultiSort + * True if this store is currently sorted by more than one field/direction combination. + */ + hasMultiSort: false, + // private batchKey : '_ext_batch_', @@ -268,13 +286,7 @@ sortInfo: { this.data.getKey = function(o){ return o.id; }; - /** - * See the{@link #baseParams corresponding configuration option}
- * for a description of this property. - * To modify this property see{@link #setBaseParam}
. - * @property - */ - this.baseParams = {}; + // temporary removed-records cache this.removed = []; @@ -286,6 +298,14 @@ sortInfo: { Ext.apply(this, config); + /** + * See the{@link #baseParams corresponding configuration option}
+ * for a description of this property. + * To modify this property see{@link #setBaseParam}
. + * @property + */ + this.baseParams = Ext.isObject(this.baseParams) ? this.baseParams : {}; + this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames); if((this.url || this.api) && !this.proxy){ @@ -439,7 +459,7 @@ sortInfo: { * @event clear * Fires when the data cache has been cleared. * @param {Store} this - * @param {Record[]} The records that were cleared. + * @param {Record[]} records The records that were cleared. */ 'clear', /** @@ -483,7 +503,7 @@ sortInfo: { * @event beforewrite * @param {Ext.data.Store} store * @param {String} action [Ext.data.Api.actions.create|update|destroy] - * @param {Record/Array[Record]} rs + * @param {Record/Record[]} rs The Record(s) being written. * @param {Object} options The loading options that were specified. Editoptions.params
to add Http parameters to the request. (see {@link #save} for details) * @param {Object} arg The callback's arg object passed to the {@link #request} function */ @@ -571,8 +591,8 @@ sortInfo: { * @private */ buildWriter : function(config) { - var klass = undefined; - type = (config.format || 'json').toLowerCase(); + var klass = undefined, + type = (config.format || 'json').toLowerCase(); switch (type) { case 'json': klass = Ext.data.JsonWriter; @@ -645,6 +665,7 @@ sortInfo: { Ext.each(record, function(r){ this.remove(r); }, this); + return; } var index = this.data.indexOf(record); if(index > -1){ @@ -810,11 +831,11 @@ sortInfo: { * false, the load call will abort and will return false; otherwise will return true. */ load : function(options) { - options = options || {}; + options = Ext.apply({}, options); this.storeOptions(options); if(this.sortInfo && this.remoteSort){ var pn = this.paramNames; - options.params = options.params || {}; + options.params = Ext.apply({}, options.params); options.params[pn.sort] = this.sortInfo.field; options.params[pn.dir] = this.sortInfo.direction; } @@ -860,9 +881,9 @@ sortInfo: { }, /** - * Destroys a record or records. Should not be used directly. It's called by Store#remove if a Writer is set. - * @param {Store} this - * @param {Ext.data.Record/Ext.data.Record[]} + * Destroys a Record. Should not be used directly. It's called by Store#remove if a Writer is set. + * @param {Store} store this + * @param {Ext.data.Record} record * @param {Number} index * @private */ @@ -911,8 +932,8 @@ sortInfo: { var doRequest = true; if (action === 'read') { - Ext.applyIf(options.params, this.baseParams); doRequest = this.fireEvent('beforeload', this, options); + Ext.applyIf(options.params, this.baseParams); } else { // if Writer is configured as listful, force single-record rs to be [{}] instead of {} @@ -1046,7 +1067,7 @@ sortInfo: { id: batch, count: 0, data: {} - } + }; } ++o.count; }, @@ -1288,34 +1309,96 @@ myStore.reload(lastOptions); return this.sortInfo; }, - // private + /** + * @private + * Invokes sortData if we have sortInfo to sort on and are not sorting remotely + */ applySort : function(){ - if(this.sortInfo && !this.remoteSort){ - var s = this.sortInfo, f = s.field; - this.sortData(f, s.direction); + if ((this.sortInfo || this.multiSortInfo) && !this.remoteSort) { + this.sortData(); } }, - // private - sortData : function(f, direction){ - direction = direction || 'ASC'; - var st = this.fields.get(f).sortType; - var fn = function(r1, r2){ - var v1 = st(r1.data[f]), v2 = st(r2.data[f]); - return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0); + /** + * @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){ + 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)); + }; + }, + /** * 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 'ASC') */ - setDefaultSort : function(field, dir){ + setDefaultSort : function(field, dir) { dir = dir ? dir.toUpperCase() : 'ASC'; this.sortInfo = {field: field, direction: dir}; this.sortToggle[field] = dir; @@ -1325,38 +1408,109 @@ myStore.reload(lastOptions); * 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}. - * @param {String} fieldName The name of the field to sort by. + * 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 'ASC') */ - sort : function(fieldName, dir){ - var f = this.fields.get(fieldName); - if(!f){ - return false; + sort : function(fieldName, dir) { + if (Ext.isArray(arguments[0])) { + return this.multiSort.call(this, fieldName, dir); + } else { + return this.singleSort(fieldName, dir); } - if(!dir){ - if(this.sortInfo && this.sortInfo.field == f.name){ // toggle sort dir - dir = (this.sortToggle[f.name] || 'ASC').toggle('ASC', 'DESC'); - }else{ - dir = f.sortDir; + }, + + /** + * 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 'ASC') + */ + 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; } } - var st = (this.sortToggle) ? this.sortToggle[f.name] : null; - var si = (this.sortInfo) ? this.sortInfo : null; - this.sortToggle[f.name] = dir; - this.sortInfo = {field: f.name, direction: dir}; - if(!this.remoteSort){ - this.applySort(); - this.fireEvent('datachanged', this); - }else{ + this.sortToggle[name] = dir; + this.sortInfo = {field: name, direction: dir}; + this.hasMultiSort = false; + + if (this.remoteSort) { if (!this.load(this.lastOptions)) { - if (st) { - this.sortToggle[f.name] = st; + if (sortToggle) { + this.sortToggle[name] = sortToggle; } - if (si) { - this.sortInfo = si; + if (sortInfo) { + this.sortInfo = sortInfo; } } + } else { + this.applySort(); + this.fireEvent('datachanged', this); + } + }, + + /** + * 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"); + } + + /** + * 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); } }, @@ -1384,17 +1538,6 @@ myStore.reload(lastOptions); return this.modified; }, - // private - createFilterFn : function(property, value, anyMatch, caseSensitive){ - if(Ext.isEmpty(value, false)){ - return false; - } - value = this.data.createValueMatcher(value, anyMatch, caseSensitive); - return function(r){ - return value.test(r.data[property]); - }; - }, - /** * Sums the value of property for each {@link Ext.data.Record record} between start * and end and returns the result. @@ -1414,16 +1557,109 @@ myStore.reload(lastOptions); 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; + }; + }, + /** - * Filter the {@link Ext.data.Record records} by a specified property. - * @param {String} field A field on your records + * 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: + *+ * @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) true to match any part not just the beginning * @param {Boolean} caseSensitive (optional) true 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){ - var fn = this.createFilterFn(property, value, anyMatch, caseSensitive); + filter : function(property, value, anyMatch, caseSensitive, exactMatch){ + //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}); + } + + var fn = this.createMultipleFilterFn(filters); + } else { + //classic single property filter + var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch); + } + return fn ? this.filterBy(fn) : this.clearFilter(); }, @@ -1444,6 +1680,29 @@ myStore.reload(lastOptions); this.fireEvent('datachanged', this); }, + /** + * Revert to a view of the Record cache with no filtering applied. + * @param {Boolean} suppressEvent If true 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); + } + } + }, + + /** + * Returns true if this store is currently filtered + * @return {Boolean} + */ + isFiltered : function(){ + return !!this.snapshot && this.snapshot != this.data; + }, + /** * Query the records by a specified property. * @param {String} field A field on your records @@ -1541,29 +1800,6 @@ myStore.reload(lastOptions); return r; }, - /** - * Revert to a view of the Record cache with no filtering applied. - * @param {Boolean} suppressEvent If true 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); - } - } - }, - - /** - * Returns true if this store is currently filtered - * @return {Boolean} - */ - isFiltered : function(){ - return this.snapshot && this.snapshot != this.data; - }, - // private afterEdit : function(record){ if(this.modified.indexOf(record) == -1){ @@ -1672,6 +1908,6 @@ Ext.apply(Ext.data.Store.Error.prototype, { 'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.' } }); -+ * 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 + * } + * ]); + *