1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>The source code</title>
- <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
- <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
- <style type="text/css">
- .highlight { display: block; background-color: #ddd; }
- </style>
- <script type="text/javascript">
- function highlight() {
- document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
- }
- </script>
- </head>
- <body onload="prettyPrint(); highlight();">
- <pre class="prettyprint lang-js"><span id='Ext-data-Store'>/**
- </span> * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load data via a
- * {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting}, {@link #filter filtering}
- * and querying the {@link Ext.data.Model model} instances contained within it.
- *
- * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
- *
- * // Set up a {@link Ext.data.Model model} to use in our Store
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * fields: [
- * {name: 'firstName', type: 'string'},
- * {name: 'lastName', type: 'string'},
- * {name: 'age', type: 'int'},
- * {name: 'eyeColor', type: 'string'}
- * ]
- * });
- *
- * var myStore = Ext.create('Ext.data.Store', {
- * model: 'User',
- * proxy: {
- * type: 'ajax',
- * url: '/users.json',
- * reader: {
- * type: 'json',
- * root: 'users'
- * }
- * },
- * autoLoad: true
- * });
- *
- * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy to use a
- * {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object - {@link
- * Ext.data.reader.Json see the docs on JsonReader} for details.
- *
- * ## Inline data
- *
- * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #cfg-data} into
- * Model instances:
- *
- * Ext.create('Ext.data.Store', {
- * model: 'User',
- * data : [
- * {firstName: 'Ed', lastName: 'Spencer'},
- * {firstName: 'Tommy', lastName: 'Maintz'},
- * {firstName: 'Aaron', lastName: 'Conran'},
- * {firstName: 'Jamie', lastName: 'Avins'}
- * ]
- * });
- *
- * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't
- * need to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode
- * the data structure, use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory
- * MemoryProxy} docs for an example).
- *
- * Additional data can also be loaded locally using {@link #method-add}.
- *
- * ## Dynamic Loading
- *
- * Stores can be dynamically updated by calling the {@link #method-load} method:
- *
- * store.load({
- * params: {
- * group: 3,
- * type: 'user'
- * },
- * callback: function(records, operation, success) {
- * // do something after the load finishes
- * },
- * scope: this
- * });
- *
- * Here a bunch of arbitrary parameters is passed along with the load request and a callback function is set
- * up to do something after the loading is over.
- *
- * ## Loading Nested Data
- *
- * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
- * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load
- * a nested dataset and allow the Reader to automatically populate the associated models. Below is a brief example, see
- * the {@link Ext.data.reader.Reader} intro docs for a full explanation:
- *
- * var store = Ext.create('Ext.data.Store', {
- * autoLoad: true,
- * model: "User",
- * proxy: {
- * type: 'ajax',
- * url: 'users.json',
- * reader: {
- * type: 'json',
- * root: 'users'
- * }
- * }
- * });
- *
- * Which would consume a response like this:
- *
- * {
- * "users": [{
- * "id": 1,
- * "name": "Ed",
- * "orders": [{
- * "id": 10,
- * "total": 10.76,
- * "status": "invoiced"
- * },{
- * "id": 11,
- * "total": 13.45,
- * "status": "shipped"
- * }]
- * }]
- * }
- *
- * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
- *
- * ## Filtering and Sorting
- *
- * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and
- * {@link #cfg-filters} are held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage.
- * Usually it is sufficient to either just specify sorters and filters in the Store configuration or call {@link #sort}
- * or {@link #filter}:
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * sorters: [{
- * property: 'age',
- * direction: 'DESC'
- * }, {
- * property: 'firstName',
- * direction: 'ASC'
- * }],
- *
- * filters: [{
- * property: 'firstName',
- * value: /Ed/
- * }]
- * });
- *
- * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By
- * default, sorting and filtering are both performed locally by the Store - see {@link #remoteSort} and
- * {@link #remoteFilter} to allow the server to perform these operations instead.
- *
- * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter
- * to the Store and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all
- * existing filters). Note that by default {@link #sortOnFilter} is set to true, which means that your sorters are
- * automatically reapplied if using local sorting.
- *
- * store.filter('eyeColor', 'Brown');
- *
- * Change the sorting at any time by calling {@link #sort}:
- *
- * store.sort('height', 'ASC');
- *
- * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no
- * arguments, the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new
- * ones, just add them to the MixedCollection:
- *
- * store.sorters.add(new Ext.util.Sorter({
- * property : 'shoeSize',
- * direction: 'ASC'
- * }));
- *
- * store.sort();
- *
- * ## Registering with StoreManager
- *
- * Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link
- * Ext.data.StoreManager StoreManager}. This makes it easy to reuse the same store in multiple views:
- *
- * //this store can be used several times
- * Ext.create('Ext.data.Store', {
- * model: 'User',
- * storeId: 'usersStore'
- * });
- *
- * new Ext.List({
- * store: 'usersStore',
- * //other config goes here
- * });
- *
- * new Ext.view.View({
- * store: 'usersStore',
- * //other config goes here
- * });
- *
- * ## Further Reading
- *
- * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
- * pieces and how they fit together, see:
- *
- * - {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
- * - {@link Ext.data.Model Model} - the core class in the data package
- * - {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
- *
- * @author Ed Spencer
- */
- Ext.define('Ext.data.Store', {
- extend: 'Ext.data.AbstractStore',
- alias: 'store.store',
- // Required classes must be loaded before the definition callback runs
- // The class definition callback creates a dummy Store which requires that
- // all the classes below have been loaded.
- requires: [
- 'Ext.data.StoreManager',
- 'Ext.data.Model',
- 'Ext.data.proxy.Ajax',
- 'Ext.data.proxy.Memory',
- 'Ext.data.reader.Json',
- 'Ext.data.writer.Json',
- 'Ext.util.LruCache'
- ],
- uses: [
- 'Ext.ModelManager',
- 'Ext.util.Grouper'
- ],
- remoteSort: false,
- remoteFilter: false,
- <span id='Ext-data-Store-cfg-remoteGroup'> /**
- </span> * @cfg {Boolean} remoteGroup
- * True if the grouping should apply on the server side, false if it is local only. If the
- * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
- * helper, automatically sending the grouping information to the server.
- */
- remoteGroup : false,
- <span id='Ext-data-Store-cfg-proxy'> /**
- </span> * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
- * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
- * see {@link #setProxy} for details.
- */
- <span id='Ext-data-Store-cfg-data'> /**
- </span> * @cfg {Object[]/Ext.data.Model[]} data
- * Array of Model instances or data objects to load locally. See "Inline data" above for details.
- */
- <span id='Ext-data-Store-cfg-groupField'> /**
- </span> * @cfg {String} groupField
- * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
- * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
- * level of grouping, and groups can be fetched via the {@link #getGroups} method.
- */
- groupField: undefined,
- <span id='Ext-data-Store-cfg-groupDir'> /**
- </span> * @cfg {String} groupDir
- * The direction in which sorting should be applied when grouping. Supported values are "ASC" and "DESC".
- */
- groupDir: "ASC",
- <span id='Ext-data-Store-cfg-trailingBufferZone'> /**
- </span> * @cfg {Number} trailingBufferZone
- * When {@link #buffered}, the number of extra records to keep cached on the trailing side of scrolling buffer
- * as scrolling proceeds. A larger number means fewer replenishments from the server.
- */
- trailingBufferZone: 25,
- <span id='Ext-data-Store-cfg-leadingBufferZone'> /**
- </span> * @cfg {Number} leadingBufferZone
- * When {@link #buffered}, the number of extra rows to keep cached on the leading side of scrolling buffer
- * as scrolling proceeds. A larger number means fewer replenishments from the server.
- */
- leadingBufferZone: 200,
- <span id='Ext-data-Store-cfg-pageSize'> /**
- </span> * @cfg {Number} pageSize
- * The number of records considered to form a 'page'. This is used to power the built-in
- * paging using the nextPage and previousPage functions when the grid is paged using a
- * {@link Ext.toolbar.Paging PagingScroller} Defaults to 25.
- *
- * If this Store is {@link #buffered}, pages are loaded into a page cache before the Store's
- * data is updated from the cache. The pageSize is the number of rows loaded into the cache in one request.
- * This will not affect the rendering of a buffered grid, but a larger page size will mean fewer loads.
- *
- * In a buffered grid, scrolling is monitored, and the page cache is kept primed with data ahead of the
- * direction of scroll to provide rapid access to data when scrolling causes it to be required. Several pages
- * in advance may be requested depending on various parameters.
- *
- * It is recommended to tune the {@link #pageSize}, {@link #trailingBufferZone} and
- * {@link #leadingBufferZone} configurations based upon the conditions pertaining in your deployed application.
- *
- * The provided SDK example `examples/grid/infinite-scroll-grid-tuner.html` can be used to experiment with
- * different settings including simulating Ajax latency.
- */
- pageSize: undefined,
- <span id='Ext-data-Store-property-currentPage'> /**
- </span> * @property {Number} currentPage
- * The page that the Store has most recently loaded (see {@link #loadPage})
- */
- currentPage: 1,
- <span id='Ext-data-Store-cfg-clearOnPageLoad'> /**
- </span> * @cfg {Boolean} clearOnPageLoad
- * True to empty the store when loading another page via {@link #loadPage},
- * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
- * large data sets to be loaded one page at a time but rendered all together.
- */
- clearOnPageLoad: true,
- <span id='Ext-data-Store-property-loading'> /**
- </span> * @property {Boolean} loading
- * True if the Store is currently loading via its Proxy
- * @private
- */
- loading: false,
- <span id='Ext-data-Store-cfg-sortOnFilter'> /**
- </span> * @cfg {Boolean} sortOnFilter
- * For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
- * causing the sorters to be reapplied after filtering. Defaults to true
- */
- sortOnFilter: true,
- <span id='Ext-data-Store-cfg-buffered'> /**
- </span> * @cfg {Boolean} buffered
- * Allows the Store to prefetch and cache in a **page cache**, pages of Records, and to then satisfy
- * loading requirements from this page cache.
- *
- * To use buffered Stores, initiate the process by loading the first page. The number of rows rendered are
- * determined automatically, and the range of pages needed to keep the cache primed for scrolling is
- * requested and cached.
- * Example:
- *
- * // Load page 1
- * myStore.loadPage(1);
- *
- * A {@link Ext.grid.PagingScroller PagingScroller} is instantiated which will monitor the scrolling in the grid, and
- * refresh the view's rows from the page cache as needed. It will also pull new data into the page
- * cache when scrolling of the view draws upon data near either end of the prefetched data.
- *
- * The margins which trigger view refreshing from the prefetched data are {@link Ext.grid.PagingScroller#numFromEdge},
- * {@link Ext.grid.PagingScroller#leadingBufferZone} and {@link Ext.grid.PagingScroller#trailingBufferZone}.
- *
- * The margins which trigger loading more data into the page cache are, {@link #leadingBufferZone} and
- * {@link #trailingBufferZone}.
- *
- * By defult, only 5 pages of data are cached in the page cache, with pages "scrolling" out of the buffer
- * as the view moves down through the dataset.
- * Setting this value to zero means that no pages are *ever* scrolled out of the page cache, and
- * that eventually the whole dataset may become present in the page cache. This is sometimes desirable
- * as long as datasets do not reach astronomical proportions.
- *
- * Selection state may be maintained across page boundaries by configuring the SelectionModel not to discard
- * records from its collection when those Records cycle out of the Store's primary collection. This is done
- * by configuring the SelectionModel like this:
- *
- * selModel: {
- * pruneRemoved: false
- * }
- *
- */
- buffered: false,
- <span id='Ext-data-Store-cfg-purgePageCount'> /**
- </span> * @cfg {Number} purgePageCount
- * *Valid only when used with a {@link Ext.data.Store#buffered buffered} Store.*
- *
- * The number of pages *additional to the required buffered range* to keep in the prefetch cache before purging least recently used records.
- *
- * For example, if the height of the view area and the configured {@link #trailingBufferZone} and {@link #leadingBufferZone} require that there
- * are three pages in the cache, then a `purgePageCount` of 5 ensures that up to 8 pages can be in the page cache any any one time.
- *
- * A value of 0 indicates to never purge the prefetched data.
- */
- purgePageCount: 5,
- <span id='Ext-data-Store-cfg-clearRemovedOnLoad'> /**
- </span> * @cfg {Boolean} [clearRemovedOnLoad=true]
- * True to clear anything in the {@link #removed} record collection when the store loads.
- */
- clearRemovedOnLoad: true,
- defaultPageSize: 25,
- // Private. Used as parameter to loadRecords
- addRecordsOptions: {
- addRecords: true
- },
- statics: {
- recordIdFn: function(record) {
- return record.internalId;
- },
- recordIndexFn: function(record) {
- return record.index;
- }
- },
- onClassExtended: function(cls, data, hooks) {
- var model = data.model,
- onBeforeClassCreated;
- if (typeof model == 'string') {
- onBeforeClassCreated = hooks.onBeforeCreated;
- hooks.onBeforeCreated = function() {
- var me = this,
- args = arguments;
- Ext.require(model, function() {
- onBeforeClassCreated.apply(me, args);
- });
- };
- }
- },
- <span id='Ext-data-Store-method-constructor'> /**
- </span> * Creates the store.
- * @param {Object} [config] Config object
- */
- constructor: function(config) {
- // Clone the config so we don't modify the original config object
- config = Ext.Object.merge({}, config);
- var me = this,
- groupers = config.groupers || me.groupers,
- groupField = config.groupField || me.groupField,
- proxy,
- data;
- <span id='Ext-data-Store-event-beforeprefetch'> /**
- </span> * @event beforeprefetch
- * Fires before a prefetch occurs. Return false to cancel.
- * @param {Ext.data.Store} this
- * @param {Ext.data.Operation} operation The associated operation
- */
- <span id='Ext-data-Store-event-groupchange'> /**
- </span> * @event groupchange
- * Fired whenever the grouping in the grid changes
- * @param {Ext.data.Store} store The store
- * @param {Ext.util.Grouper[]} groupers The array of grouper objects
- */
- <span id='Ext-data-Store-event-prefetch'> /**
- </span> * @event prefetch
- * Fires whenever records have been prefetched
- * @param {Ext.data.Store} this
- * @param {Ext.data.Model[]} records An array of records.
- * @param {Boolean} successful True if the operation was successful.
- * @param {Ext.data.Operation} operation The associated operation
- */
- data = config.data || me.data;
- <span id='Ext-data-Store-property-data'> /**
- </span> * @property {Ext.util.MixedCollection} data
- * The MixedCollection that holds this store's local cache of records.
- */
- me.data = new Ext.util.MixedCollection(false, Ext.data.Store.recordIdFn);
- if (data) {
- me.inlineData = data;
- delete config.data;
- }
- if (!groupers && groupField) {
- groupers = [{
- property : groupField,
- direction: config.groupDir || me.groupDir
- }];
- }
- delete config.groupers;
- <span id='Ext-data-Store-property-groupers'> /**
- </span> * @property {Ext.util.MixedCollection} groupers
- * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store.
- */
- me.groupers = new Ext.util.MixedCollection();
- me.groupers.addAll(me.decodeGroupers(groupers));
- this.callParent([config]);
- // don't use *config* anymore from here on... use *me* instead...
- if (me.buffered) {
- <span id='Ext-data-Store-property-pageMap'> /**
- </span> * @property {Ext.data.Store.PageMap} pageMap
- * Internal PageMap instance.
- * @private
- */
- me.pageMap = new me.PageMap({
- pageSize: me.pageSize,
- maxSize: me.purgePageCount,
- listeners: {
- // Whenever PageMap gets cleared, it means we re no longer interested in
- // any outstanding page prefetches, so cancel tham all
- clear: me.cancelAllPrefetches,
- scope: me
- }
- });
- me.pageRequests = {};
- me.sortOnLoad = false;
- me.filterOnLoad = false;
- }
- // Only sort by group fields if we are doing local grouping
- if (me.remoteGroup) {
- me.remoteSort = true;
- }
- if (me.groupers.items.length && !me.remoteGroup) {
- me.sort(me.groupers.items, 'prepend', false);
- }
- proxy = me.proxy;
- data = me.inlineData;
- // Page size for non-buffered Store defaults to 25
- // For a buffered Store, the default page size is taken from the initial call to prefetch.
- if (!me.buffered && !me.pageSize) {
- me.pageSize = me.defaultPageSize;
- }
- if (data) {
- if (proxy instanceof Ext.data.proxy.Memory) {
- proxy.data = data;
- me.read();
- } else {
- me.add.apply(me, [data]);
- }
- me.sort();
- delete me.inlineData;
- } else if (me.autoLoad) {
- Ext.defer(me.load, 10, me, [ typeof me.autoLoad === 'object' ? me.autoLoad : undefined ]);
- // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
- // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
- }
- },
- // private override
- // After destroying the Store, clear the page prefetch cache
- destroyStore: function() {
- this.callParent(arguments);
- // Release cached pages.
- // Will also cancel outstanding prefetch requests, and cause a generation change
- // so that incoming prefetch data will be ignored.
- if (this.pageMap) {
- this.pageMap.clear();
- }
- },
- onBeforeSort: function() {
- var groupers = this.groupers;
- if (groupers.getCount() > 0) {
- this.sort(groupers.items, 'prepend', false);
- }
- },
- <span id='Ext-data-Store-method-decodeGroupers'> /**
- </span> * @private
- * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
- * @param {Object[]} groupers The groupers array
- * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
- */
- decodeGroupers: function(groupers) {
- if (!Ext.isArray(groupers)) {
- if (groupers === undefined) {
- groupers = [];
- } else {
- groupers = [groupers];
- }
- }
- var length = groupers.length,
- Grouper = Ext.util.Grouper,
- config, i, result = [];
- for (i = 0; i < length; i++) {
- config = groupers[i];
- if (!(config instanceof Grouper)) {
- if (Ext.isString(config)) {
- config = {
- property: config
- };
- }
- config = Ext.apply({
- root : 'data',
- direction: "ASC"
- }, config);
- //support for 3.x style sorters where a function can be defined as 'fn'
- if (config.fn) {
- config.sorterFn = config.fn;
- }
- //support a function to be passed as a sorter definition
- if (typeof config == 'function') {
- config = {
- sorterFn: config
- };
- }
- // return resulting Groupers in a separate array so as not to mutate passed in data objects.
- result.push(new Grouper(config));
- } else {
- result.push(config);
- }
- }
- return result;
- },
- <span id='Ext-data-Store-method-group'> /**
- </span> * Groups data inside the store.
- * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's
- * configured {@link Ext.data.Model Model}, or an Array of grouper configurations.
- * @param {String} [direction="ASC"] The overall direction to group the data by.
- */
- group: function(groupers, direction) {
- var me = this,
- hasNew = false,
- grouper,
- newGroupers;
- if (Ext.isArray(groupers)) {
- newGroupers = groupers;
- } else if (Ext.isObject(groupers)) {
- newGroupers = [groupers];
- } else if (Ext.isString(groupers)) {
- grouper = me.groupers.get(groupers);
- if (!grouper) {
- grouper = {
- property : groupers,
- direction: direction
- };
- newGroupers = [grouper];
- } else if (direction === undefined) {
- grouper.toggle();
- } else {
- grouper.setDirection(direction);
- }
- }
- if (newGroupers && newGroupers.length) {
- hasNew = true;
- newGroupers = me.decodeGroupers(newGroupers);
- me.groupers.clear();
- me.groupers.addAll(newGroupers);
- }
- if (me.remoteGroup) {
- if (me.buffered) {
- me.pageMap.clear();
- me.loadPage(1, { groupChange: true });
- } else {
- me.load({
- scope: me,
- callback: me.fireGroupChange
- });
- }
- } else {
- // need to explicitly force a sort if we have groupers
- me.sort(null, null, null, hasNew);
- me.fireGroupChange();
- }
- },
- <span id='Ext-data-Store-method-clearGrouping'> /**
- </span> * Clear any groupers in the store
- */
- clearGrouping: function() {
- var me = this,
- groupers = me.groupers.items,
- gLen = groupers.length,
- grouper, g;
- for (g = 0; g < gLen; g++) {
- grouper = groupers[g];
- me.sorters.remove(grouper);
- }
- me.groupers.clear();
- if (me.remoteGroup) {
- if (me.buffered) {
- me.pageMap.clear();
- me.loadPage(1, { groupChange: true });
- } else {
- me.load({
- scope: me,
- callback: me.fireGroupChange
- });
- }
- } else {
- me.sort();
- me.fireGroupChange();
- }
- },
- <span id='Ext-data-Store-method-isGrouped'> /**
- </span> * Checks if the store is currently grouped
- * @return {Boolean} True if the store is grouped.
- */
- isGrouped: function() {
- return this.groupers.getCount() > 0;
- },
- <span id='Ext-data-Store-method-fireGroupChange'> /**
- </span> * Fires the groupchange event. Abstracted out so we can use it
- * as a callback
- * @private
- */
- fireGroupChange: function() {
- this.fireEvent('groupchange', this, this.groupers);
- },
- <span id='Ext-data-Store-method-getGroups'> /**
- </span> * Returns an array containing the result of applying grouping to the records in this store.
- * See {@link #groupField}, {@link #groupDir} and {@link #getGroupString}. Example for a store
- * containing records with a color field:
- *
- * var myStore = Ext.create('Ext.data.Store', {
- * groupField: 'color',
- * groupDir : 'DESC'
- * });
- *
- * myStore.getGroups(); // returns:
- * [
- * {
- * name: 'yellow',
- * children: [
- * // all records where the color field is 'yellow'
- * ]
- * },
- * {
- * name: 'red',
- * children: [
- * // all records where the color field is 'red'
- * ]
- * }
- * ]
- *
- * Group contents are effected by filtering.
- *
- * @param {String} [groupName] Pass in an optional groupName argument to access a specific
- * group as defined by {@link #getGroupString}.
- * @return {Object/Object[]} The grouped data
- */
- getGroups: function(requestGroupString) {
- var records = this.data.items,
- length = records.length,
- groups = [],
- pointers = {},
- record,
- groupStr,
- group,
- i;
- for (i = 0; i < length; i++) {
- record = records[i];
- groupStr = this.getGroupString(record);
- group = pointers[groupStr];
- if (group === undefined) {
- group = {
- name: groupStr,
- children: []
- };
- groups.push(group);
- pointers[groupStr] = group;
- }
- group.children.push(record);
- }
- return requestGroupString ? pointers[requestGroupString] : groups;
- },
- <span id='Ext-data-Store-method-getGroupsForGrouper'> /**
- </span> * @private
- * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
- * matching a certain group.
- */
- getGroupsForGrouper: function(records, grouper) {
- var length = records.length,
- groups = [],
- oldValue,
- newValue,
- record,
- group,
- i;
- for (i = 0; i < length; i++) {
- record = records[i];
- newValue = grouper.getGroupString(record);
- if (newValue !== oldValue) {
- group = {
- name: newValue,
- grouper: grouper,
- records: []
- };
- groups.push(group);
- }
- group.records.push(record);
- oldValue = newValue;
- }
- return groups;
- },
- <span id='Ext-data-Store-method-getGroupsForGrouperIndex'> /**
- </span> * @private
- * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
- * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
- * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
- *
- * @param {Ext.data.Model[]} records The set or subset of records to group
- * @param {Number} grouperIndex The grouper index to retrieve
- * @return {Object[]} The grouped records
- */
- getGroupsForGrouperIndex: function(records, grouperIndex) {
- var me = this,
- groupers = me.groupers,
- grouper = groupers.getAt(grouperIndex),
- groups = me.getGroupsForGrouper(records, grouper),
- length = groups.length,
- i;
- if (grouperIndex + 1 < groupers.length) {
- for (i = 0; i < length; i++) {
- groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
- }
- }
- for (i = 0; i < length; i++) {
- groups[i].depth = grouperIndex;
- }
- return groups;
- },
- <span id='Ext-data-Store-method-getGroupData'> /**
- </span> * @private
- * Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
- * this case grouping by genre and then author in a fictional books dataset):
- *
- * [
- * {
- * name: 'Fantasy',
- * depth: 0,
- * records: [
- * //book1, book2, book3, book4
- * ],
- * children: [
- * {
- * name: 'Rowling',
- * depth: 1,
- * records: [
- * //book1, book2
- * ]
- * },
- * {
- * name: 'Tolkein',
- * depth: 1,
- * records: [
- * //book3, book4
- * ]
- * }
- * ]
- * }
- * ]
- *
- * @param {Boolean} [sort=true] True to call {@link #sort} before finding groups. Sorting is required to make grouping
- * function correctly so this should only be set to false if the Store is known to already be sorted correctly.
- * @return {Object[]} The group data
- */
- getGroupData: function(sort) {
- var me = this;
- if (sort !== false) {
- me.sort();
- }
- return me.getGroupsForGrouperIndex(me.data.items, 0);
- },
- <span id='Ext-data-Store-method-getGroupString'> /**
- </span> * Returns the string to group on for a given model instance. The default implementation of this method returns
- * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
- * group by the first letter of a model's 'name' field, use the following code:
- *
- * Ext.create('Ext.data.Store', {
- * groupDir: 'ASC',
- * getGroupString: function(instance) {
- * return instance.get('name')[0];
- * }
- * });
- *
- * @param {Ext.data.Model} instance The model instance
- * @return {String} The string to compare when forming groups
- */
- getGroupString: function(instance) {
- var group = this.groupers.first();
- if (group) {
- return instance.get(group.property);
- }
- return '';
- },
- <span id='Ext-data-Store-method-insert'> /**
- </span> * Inserts Model instances into the Store at the given index and fires the {@link #event-add} event.
- * See also {@link #method-add}.
- *
- * @param {Number} index The start index at which to insert the passed Records.
- * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the store.
- */
- insert: function(index, records) {
- var me = this,
- sync = false,
- i,
- record,
- len;
- records = [].concat(records);
- for (i = 0,len = records.length; i < len; i++) {
- record = me.createModel(records[i]);
- record.set(me.modelDefaults);
- // reassign the model in the array in case it wasn't created yet
- records[i] = record;
- me.data.insert(index + i, record);
- record.join(me);
- sync = sync || record.phantom === true;
- }
- if (me.snapshot) {
- me.snapshot.addAll(records);
- }
- if (me.requireSort) {
- // suspend events so the usual data changed events don't get fired.
- me.suspendEvents();
- me.sort();
- me.resumeEvents();
- }
- me.fireEvent('add', me, records, index);
- me.fireEvent('datachanged', me);
- if (me.autoSync && sync && !me.autoSyncSuspended) {
- me.sync();
- }
- },
- <span id='Ext-data-Store-method-add'> /**
- </span> * Adds Model instance to the Store. This method accepts either:
- *
- * - An array of Model instances or Model configuration objects.
- * - Any number of Model instance or Model configuration object arguments.
- *
- * The new Model instances will be added at the end of the existing collection.
- *
- * Sample usage:
- *
- * myStore.add({some: 'data'}, {some: 'other data'});
- *
- * Note that if this Store is sorted, the new Model instances will be inserted
- * at the correct point in the Store to maintain the sort order.
- *
- * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
- * or Model configuration objects, or variable number of Model instance or config arguments.
- * @return {Ext.data.Model[]} The model instances that were added
- */
- add: function(records) {
- //accept both a single-argument array of records, or any number of record arguments
- if (!Ext.isArray(records)) {
- records = Array.prototype.slice.apply(arguments);
- } else {
- // Create an array copy
- records = records.slice(0);
- }
- var me = this,
- i = 0,
- length = records.length,
- record,
- isSorted = !me.remoteSort && me.sorters && me.sorters.items.length;
- // If this Store is sorted, and they only passed one Record (99% or use cases)
- // then it's much more efficient to add it sorted than to append and then sort.
- if (isSorted && length === 1) {
- return [ me.addSorted(me.createModel(records[0])) ];
- }
- for (; i < length; i++) {
- record = me.createModel(records[i]);
- // reassign the model in the array in case it wasn't created yet
- records[i] = record;
- }
- // If this sort is sorted, set the flag used by the insert method to sort
- // before firing events.
- if (isSorted) {
- me.requireSort = true;
- }
- me.insert(me.data.length, records);
- delete me.requireSort;
- return records;
- },
- <span id='Ext-data-Store-method-addSorted'> /**
- </span> * (Local sort only) Inserts the passed Record into the Store at the index where it
- * should go based on the current sort information.
- *
- * @param {Ext.data.Record} record
- */
- addSorted: function(record) {
- var me = this,
- index = me.data.findInsertionIndex(record, me.generateComparator());
- me.insert(index, record);
- return record;
- },
- <span id='Ext-data-Store-method-createModel'> /**
- </span> * Converts a literal to a model, if it's not a model already
- * @private
- * @param {Ext.data.Model/Object} record The record to create
- * @return {Ext.data.Model}
- */
- createModel: function(record) {
- if (!record.isModel) {
- record = Ext.ModelManager.create(record, this.model);
- }
- return record;
- },
- <span id='Ext-data-Store-method-each'> /**
- </span> * Calls the specified function for each {@link Ext.data.Model record} in the store.
- *
- * When store is filtered, only loops over the filtered records.
- *
- * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
- * Returning `false` aborts and exits the iteration.
- * @param {Object} [scope] The scope (this reference) in which the function is executed.
- * Defaults to the current {@link Ext.data.Model record} in the iteration.
- */
- each: function(fn, scope) {
- var data = this.data.items,
- dLen = data.length,
- record, d;
- for (d = 0; d < dLen; d++) {
- record = data[d];
- if (fn.call(scope || record, record, d, dLen) === false) {
- break;
- }
- }
- },
- <span id='Ext-data-Store-method-remove'> /**
- </span> * Removes the given record from the Store, firing the 'remove' event for each instance that is removed,
- * plus a single 'datachanged' event after removal.
- *
- * @param {Ext.data.Model/Ext.data.Model[]} records Model instance or array of instances to remove.
- */
- remove: function(records, /* private */ isMove) {
- if (!Ext.isArray(records)) {
- records = [records];
- }
- /*
- * Pass the isMove parameter if we know we're going to be re-inserting this record
- */
- isMove = isMove === true;
- var me = this,
- sync = false,
- i = 0,
- length = records.length,
- isNotPhantom,
- index,
- record;
- for (; i < length; i++) {
- record = records[i];
- index = me.data.indexOf(record);
- if (me.snapshot) {
- me.snapshot.remove(record);
- }
- if (index > -1) {
- isNotPhantom = record.phantom !== true;
- // don't push phantom records onto removed
- if (!isMove && isNotPhantom) {
- // Store the index the record was removed from so that rejectChanges can re-insert at the correct place.
- // The record's index property won't do, as that is the index in the overall dataset when Store is buffered.
- record.removedFrom = index;
- me.removed.push(record);
- }
- record.unjoin(me);
- me.data.remove(record);
- sync = sync || isNotPhantom;
- me.fireEvent('remove', me, record, index);
- }
- }
- me.fireEvent('datachanged', me);
- if (!isMove && me.autoSync && sync && !me.autoSyncSuspended) {
- me.sync();
- }
- },
- <span id='Ext-data-Store-method-removeAt'> /**
- </span> * Removes the model instance at the given index
- * @param {Number} index The record index
- */
- removeAt: function(index) {
- var record = this.getAt(index);
- if (record) {
- this.remove(record);
- }
- },
- <span id='Ext-data-Store-method-load'> /**
- </span> * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
- * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
- * instances into the Store and calling an optional callback if required. Example usage:
- *
- * store.load({
- * scope: this,
- * callback: function(records, operation, success) {
- * // the {@link Ext.data.Operation operation} object
- * // contains all of the details of the load operation
- * console.log(records);
- * }
- * });
- *
- * If the callback scope does not need to be set, a function can simply be passed:
- *
- * store.load(function(records, operation, success) {
- * console.log('loaded records');
- * });
- *
- * @param {Object/Function} [options] config object, passed into the Ext.data.Operation object before loading.
- * Additionally `addRecords: true` can be specified to add these records to the existing records, default is
- * to remove the Store's existing records first.
- */
- load: function(options) {
- var me = this;
- options = options || {};
- if (typeof options == 'function') {
- options = {
- callback: options
- };
- }
- options.groupers = options.groupers || me.groupers.items;
- options.page = options.page || me.currentPage;
- options.start = (options.start !== undefined) ? options.start : (options.page - 1) * me.pageSize;
- options.limit = options.limit || me.pageSize;
- options.addRecords = options.addRecords || false;
- if (me.buffered) {
- return me.loadToPrefetch(options);
- }
- return me.callParent([options]);
- },
- reload: function(options) {
- var me = this,
- startIdx,
- endIdx,
- startPage,
- endPage,
- i,
- waitForReload,
- bufferZone,
- records;
- if (!options) {
- options = {};
- }
- // If buffered, we have to clear the page cache and then
- // cache the page range surrounding store's loaded range.
- if (me.buffered) {
- // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
- delete me.totalCount;
- waitForReload = function() {
- if (me.rangeCached(startIdx, endIdx)) {
- me.loading = false;
- me.pageMap.un('pageAdded', waitForReload);
- records = me.pageMap.getRange(startIdx, endIdx);
- me.loadRecords(records, {
- start: startIdx
- });
- me.fireEvent('load', me, records, true);
- }
- };
- bufferZone = Math.ceil((me.leadingBufferZone + me.trailingBufferZone) / 2);
- // Get our record index range in the dataset
- startIdx = options.start || me.getAt(0).index;
- endIdx = startIdx + (options.count || me.getCount()) - 1;
- // Calculate a page range which encompasses the Store's loaded range plus both buffer zones
- startPage = me.getPageFromRecordIndex(Math.max(startIdx - bufferZone, 0));
- endPage = me.getPageFromRecordIndex(endIdx + bufferZone);
- // Clear cache (with initial flag so that any listening PagingScroller does not reset to page 1).
- me.pageMap.clear(true);
- if (me.fireEvent('beforeload', me, options) !== false) {
- me.loading = true;
- // Recache the page range which encapsulates our visible records
- for (i = startPage; i <= endPage; i++) {
- me.prefetchPage(i, options);
- }
- // Wait for the requested range to become available in the page map
- // Load the range as soon as the whole range is available
- me.pageMap.on('pageAdded', waitForReload);
- }
- } else {
- return me.callParent(arguments);
- }
- },
- <span id='Ext-data-Store-method-onProxyLoad'> /**
- </span> * @private
- * Called internally when a Proxy has completed a load request
- */
- onProxyLoad: function(operation) {
- var me = this,
- resultSet = operation.getResultSet(),
- records = operation.getRecords(),
- successful = operation.wasSuccessful();
- if (resultSet) {
- me.totalCount = resultSet.total;
- }
- if (successful) {
- me.loadRecords(records, operation);
- }
- me.loading = false;
- if (me.hasListeners.load) {
- me.fireEvent('load', me, records, successful);
- }
- //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
- //People are definitely using this so can't deprecate safely until 2.x
- if (me.hasListeners.read) {
- me.fireEvent('read', me, records, successful);
- }
- //this is a callback that would have been passed to the 'read' function and is optional
- Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
- },
- //inherit docs
- getNewRecords: function() {
- return this.data.filterBy(this.filterNew).items;
- },
- //inherit docs
- getUpdatedRecords: function() {
- return this.data.filterBy(this.filterUpdated).items;
- },
- <span id='Ext-data-Store-method-filter'> /**
- </span> * Filters the loaded set of records by a given set of filters.
- *
- * By default, the passed filter(s) are *added* to the collection of filters being used to filter this Store.
- *
- * To remove existing filters before applying a new set of filters use
- *
- * // Clear the filter collection without updating the UI
- * store.clearFilter(true);
- *
- * see {@link #clearFilter}.
- *
- * Alternatively, if filters are configured with an `id`, then existing filters store may be *replaced* by new
- * filters having the same `id`.
- *
- * Filtering by single field:
- *
- * store.filter("email", /\.com$/);
- *
- * Using multiple filters:
- *
- * store.filter([
- * {property: "email", value: /\.com$/},
- * {filterFn: function(item) { return item.get("age") > 10; }}
- * ]);
- *
- * Using Ext.util.Filter instances instead of config objects
- * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
- *
- * store.filter([
- * Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
- * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
- * ]);
- *
- * When store is filtered, most of the methods for accessing store data will be working only
- * within the set of filtered records. Two notable exceptions are {@link #queryBy} and
- * {@link #getById}.
- *
- * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data.
- * These are stored internally on the store, but the filtering itself is done on the Store's
- * {@link Ext.util.MixedCollection MixedCollection}. See MixedCollection's
- * {@link Ext.util.MixedCollection#filter filter} method for filter syntax.
- * Alternatively, pass in a property string
- * @param {String} [value] value to filter by (only if using a property string as the first argument)
- */
- filter: function(filters, value) {
- if (Ext.isString(filters)) {
- filters = {
- property: filters,
- value: value
- };
- }
- var me = this,
- decoded = me.decodeFilters(filters),
- i = 0,
- doLocalSort = me.sorters.length && me.sortOnFilter && !me.remoteSort,
- length = decoded.length;
- for (; i < length; i++) {
- me.filters.replace(decoded[i]);
- }
- if (me.remoteFilter) {
- // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
- delete me.totalCount;
-
- // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
- // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
- // via the guaranteedrange event
- if (me.buffered) {
- me.pageMap.clear();
- me.loadPage(1);
- } else {
- // Reset to the first page, the filter is likely to produce a smaller data set
- me.currentPage = 1;
- //the load function will pick up the new filters and request the filtered data from the proxy
- me.load();
- }
- } else {
- <span id='Ext-data-Store-property-snapshot'> /**
- </span> * @property {Ext.util.MixedCollection} snapshot
- * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
- * records when a filter is removed or changed
- */
- if (me.filters.getCount()) {
- me.snapshot = me.snapshot || me.data.clone();
- me.data = me.data.filter(me.filters.items);
- if (doLocalSort) {
- me.sort();
- } else {
- // fire datachanged event if it hasn't already been fired by doSort
- me.fireEvent('datachanged', me);
- me.fireEvent('refresh', me);
- }
- }
- }
- },
- <span id='Ext-data-Store-method-clearFilter'> /**
- </span> * Reverts to a view of the Record cache with no filtering applied.
- * @param {Boolean} suppressEvent If `true` the filter is cleared silently.
- *
- * For a locally filtered Store, this means that the filter collection is cleared without firing the
- * {@link #datachanged} event.
- *
- * For a remotely filtered Store, this means that the filter collection is cleared, but the store
- * is not reloaded from the server.
- */
- clearFilter: function(suppressEvent) {
- var me = this;
- me.filters.clear();
- if (me.remoteFilter) {
- // In a buffered Store, the meaing of suppressEvent is to simply clear the filters collection
- if (suppressEvent) {
- return;
- }
- // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
- delete me.totalCount;
- // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
- // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
- // via the guaranteedrange event
- if (me.buffered) {
- me.pageMap.clear();
- me.loadPage(1);
- } else {
- // Reset to the first page, clearing a filter will destroy the context of the current dataset
- me.currentPage = 1;
- me.load();
- }
- } else if (me.isFiltered()) {
- me.data = me.snapshot.clone();
- delete me.snapshot;
- if (suppressEvent !== true) {
- me.fireEvent('datachanged', me);
- me.fireEvent('refresh', me);
- }
- }
- },
- <span id='Ext-data-Store-method-isFiltered'> /**
- </span> * Returns true if this store is currently filtered
- * @return {Boolean}
- */
- isFiltered: function() {
- var snapshot = this.snapshot;
- return !! snapshot && snapshot !== this.data;
- },
- <span id='Ext-data-Store-method-filterBy'> /**
- </span> * Filters by a function. The specified function will be called for each
- * Record in this Store. If the function returns `true` the Record is included,
- * otherwise it is filtered out.
- *
- * When store is filtered, most of the methods for accessing store data will be working only
- * within the set of filtered records. Two notable exceptions are {@link #queryBy} and
- * {@link #getById}.
- *
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
- * using {@link Ext.data.Model#get}.
- * @param {Object} fn.id The ID of the Record passed.
- * @param {Object} [scope] The scope (this reference) in which the function is executed.
- * Defaults to this Store.
- */
- filterBy: function(fn, scope) {
- var me = this;
- me.snapshot = me.snapshot || me.data.clone();
- me.data = me.queryBy(fn, scope || me);
- me.fireEvent('datachanged', me);
- me.fireEvent('refresh', me);
- },
- <span id='Ext-data-Store-method-queryBy'> /**
- </span> * Query all 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 `true` the record is
- * included in the results.
- *
- * This method is not effected by filtering, it will always look from all records inside the store
- * no matter if filter is applied or not.
- *
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
- * using {@link Ext.data.Model#get}.
- * @param {Object} fn.id The ID of the Record passed.
- * @param {Object} [scope] The scope (this reference) in which the function is executed
- * Defaults to this Store.
- * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
- */
- queryBy: function(fn, scope) {
- var me = this,
- data = me.snapshot || me.data;
- return data.filterBy(fn, scope || me);
- },
- <span id='Ext-data-Store-method-query'> /**
- </span> * Query all the cached records in this Store by name/value pair.
- * The parameters will be used to generated a filter function that is given
- * to the queryBy method.
- *
- * This method compliments queryBy by generating the query function automatically.
- *
- * @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=false] True if we don't care if the filter value is not the full value.
- * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
- * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
- * Ignored if anyMatch is true.
- * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
- */
- query: function(property, value, anyMatch, caseSensitive, exactMatch) {
- var me = this,
- queryFn = me.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch),
- results = me.queryBy(queryFn);
- //create an empty mixed collection for use if queryBy returns null
- if(!results) {
- results = new Ext.util.MixedCollection();
- }
- return results;
- },
- <span id='Ext-data-Store-method-loadData'> /**
- </span> * Loads an array of data straight into the Store.
- *
- * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
- * processed by a reader). If your data requires processing to decode the data structure, use a
- * {@link Ext.data.proxy.Memory MemoryProxy} instead.
- *
- * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
- * into model instances first.
- * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
- * to remove the old ones first.
- */
- loadData: function(data, append) {
- var me = this,
- model = me.model,
- length = data.length,
- newData = [],
- i,
- record;
- //make sure each data element is an Ext.data.Model instance
- for (i = 0; i < length; i++) {
- record = data[i];
- if (!(record.isModel)) {
- record = Ext.ModelManager.create(record, model);
- }
- newData.push(record);
- }
- me.loadRecords(newData, append ? me.addRecordsOptions : undefined);
- },
- <span id='Ext-data-Store-method-loadRawData'> /**
- </span> * Loads data via the bound Proxy's reader
- *
- * Use this method if you are attempting to load data and want to utilize the configured data reader.
- *
- * @param {Object[]} data The full JSON object you'd like to load into the Data store.
- * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
- * to remove the old ones first.
- */
- loadRawData : function(data, append) {
- var me = this,
- result = me.proxy.reader.read(data),
- records = result.records;
- if (result.success) {
- me.totalCount = result.total;
- me.loadRecords(records, append ? me.addRecordsOptions : undefined);
- me.fireEvent('load', me, records, true);
- }
- },
- <span id='Ext-data-Store-method-loadRecords'> /**
- </span> * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
- * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #method-add} instead
- * @param {Ext.data.Model[]} records The array of records to load
- * @param {Object} options
- * @param {Boolean} [options.addRecords=false] Pass `true` to add these records to the existing records, `false` to remove the Store's existing records first.
- * @param {Number} [options.start] Only used by buffered Stores. The index *within the overall dataset* of the first record in the array.
- */
- loadRecords: function(records, options) {
- var me = this,
- i = 0,
- length = records.length,
- start,
- addRecords,
- snapshot = me.snapshot;
- if (options) {
- start = options.start;
- addRecords = options.addRecords;
- }
- if (!addRecords) {
- delete me.snapshot;
- me.clearData(true);
- } else if (snapshot) {
- snapshot.addAll(records);
- }
- me.data.addAll(records);
- if (start !== undefined) {
- for (; i < length; i++) {
- records[i].index = start + i;
- records[i].join(me);
- }
- } else {
- for (; i < length; i++) {
- records[i].join(me);
- }
- }
- /*
- * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
- * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
- * datachanged event is fired by the call to this.add, above.
- */
- me.suspendEvents();
- if (me.filterOnLoad && !me.remoteFilter) {
- me.filter();
- }
- if (me.sortOnLoad && !me.remoteSort) {
- me.sort(undefined, undefined, undefined, true);
- }
- me.resumeEvents();
- me.fireEvent('datachanged', me);
- me.fireEvent('refresh', me);
- },
- // PAGING METHODS
- <span id='Ext-data-Store-method-loadPage'> /**
- </span> * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
- * load operation, passing in calculated 'start' and 'limit' params
- * @param {Number} page The number of the page to load
- * @param {Object} options See options for {@link #method-load}
- */
- loadPage: function(page, options) {
- var me = this;
- me.currentPage = page;
- // Copy options into a new object so as not to mutate passed in objects
- options = Ext.apply({
- page: page,
- start: (page - 1) * me.pageSize,
- limit: me.pageSize,
- addRecords: !me.clearOnPageLoad
- }, options);
- if (me.buffered) {
- return me.loadToPrefetch(options);
- }
- me.read(options);
- },
- <span id='Ext-data-Store-method-nextPage'> /**
- </span> * Loads the next 'page' in the current data set
- * @param {Object} options See options for {@link #method-load}
- */
- nextPage: function(options) {
- this.loadPage(this.currentPage + 1, options);
- },
- <span id='Ext-data-Store-method-previousPage'> /**
- </span> * Loads the previous 'page' in the current data set
- * @param {Object} options See options for {@link #method-load}
- */
- previousPage: function(options) {
- this.loadPage(this.currentPage - 1, options);
- },
- // private
- clearData: function(isLoad) {
- var me = this,
- records = me.data.items,
- i = records.length;
- while (i--) {
- records[i].unjoin(me);
- }
- me.data.clear();
- if (isLoad !== true || me.clearRemovedOnLoad) {
- me.removed.length = 0;
- }
- },
- loadToPrefetch: function(options) {
- var me = this,
- i,
- records,
- // Get the requested record index range in the dataset
- startIdx = options.start,
- endIdx = options.start + options.limit - 1,
- // The end index to load into the store's live record collection
- loadEndIdx = options.start + (me.viewSize || options.limit) - 1,
- // Calculate a page range which encompasses the requested range plus both buffer zones
- startPage = me.getPageFromRecordIndex(Math.max(startIdx - me.trailingBufferZone, 0)),
- endPage = me.getPageFromRecordIndex(endIdx + me.leadingBufferZone),
- // Wait for the viewable range to be available
- waitForRequestedRange = function() {
- if (me.rangeCached(startIdx, loadEndIdx)) {
- me.loading = false;
- records = me.pageMap.getRange(startIdx, loadEndIdx);
- me.pageMap.un('pageAdded', waitForRequestedRange);
- // If there is a listener for guranteedrange (PagingScroller uses this), then go through that event
- if (me.hasListeners.guaranteedrange) {
- me.guaranteeRange(startIdx, loadEndIdx, options.callback, options.scope);
- }
- // Otherwise load the records directly
- else {
- me.loadRecords(records, {
- start: startIdx
- });
- }
- me.fireEvent('load', me, records, true);
- if (options.groupChange) {
- me.fireGroupChange();
- }
- }
- };
- if (me.fireEvent('beforeload', me, options) !== false) {
- // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
- delete me.totalCount;
- me.loading = true;
- // Wait for the requested range to become available in the page map
- me.pageMap.on('pageAdded', waitForRequestedRange);
-
- // Load the first page in the range, which will give us the initial total count.
- // Once it is loaded, go ahead and prefetch any subsequent pages, if necessary.
- // The prefetchPage has a check to prevent us loading more than the totalCount,
- // so we don't want to blindly load up <n> pages where it isn't required.
- me.on('prefetch', function(){
- for (i = startPage + 1; i <= endPage; ++i) {
- me.prefetchPage(i, options);
- }
- }, null, {single: true});
-
- me.prefetchPage(startPage, options);
- }
- },
- // Buffering
- <span id='Ext-data-Store-method-prefetch'> /**
- </span> * Prefetches data into the store using its configured {@link #proxy}.
- * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
- * See {@link #method-load}
- */
- prefetch: function(options) {
- var me = this,
- pageSize = me.pageSize,
- proxy,
- operation;
- // Check pageSize has not been tampered with. That would break page caching
- if (pageSize) {
- if (me.lastPageSize && pageSize != me.lastPageSize) {
- Ext.error.raise("pageSize cannot be dynamically altered");
- }
- if (!me.pageMap.pageSize) {
- me.pageMap.pageSize = pageSize;
- }
- }
- // Allow first prefetch call to imply the required page size.
- else {
- me.pageSize = me.pageMap.pageSize = pageSize = options.limit;
- }
- // So that we can check for tampering next time through
- me.lastPageSize = pageSize;
- // Always get whole pages.
- if (!options.page) {
- options.page = me.getPageFromRecordIndex(options.start);
- options.start = (options.page - 1) * pageSize;
- options.limit = Math.ceil(options.limit / pageSize) * pageSize;
- }
- // Currently not requesting this page, then request it...
- if (!me.pageRequests[options.page]) {
- // Copy options into a new object so as not to mutate passed in objects
- options = Ext.apply({
- action : 'read',
- filters: me.filters.items,
- sorters: me.sorters.items,
- groupers: me.groupers.items,
- // Generation # of the page map to which the requested records belong.
- // If page map is cleared while this request is in flight, the generation will increment and the payload will be rejected
- generation: me.pageMap.generation
- }, options);
- operation = new Ext.data.Operation(options);
- if (me.fireEvent('beforeprefetch', me, operation) !== false) {
- me.loading = true;
- proxy = me.proxy;
- me.pageRequests[options.page] = proxy.read(operation, me.onProxyPrefetch, me);
- if (proxy.isSynchronous) {
- delete me.pageRequests[options.page];
- }
- }
- }
- return me;
- },
- <span id='Ext-data-Store-method-cancelAllPrefetches'> /**
- </span> * @private
- * Cancels all pending prefetch requests.
- *
- * This is called when the page map is cleared.
- *
- * Any requests which still make it through will be for the previous page map generation
- * (generation is incremented upon clear), and so will be rejected upon arrival.
- */
- cancelAllPrefetches: function() {
- var me = this,
- reqs = me.pageRequests,
- req,
- page;
- // If any requests return, we no longer respond to them.
- if (me.pageMap.events.pageadded) {
- me.pageMap.events.pageadded.clearListeners();
- }
- // Cancel all outstanding requests
- for (page in reqs) {
- if (reqs.hasOwnProperty(page)) {
- req = reqs[page];
- delete reqs[page];
- delete req.callback;
- }
- }
- },
- <span id='Ext-data-Store-method-prefetchPage'> /**
- </span> * Prefetches a page of data.
- * @param {Number} page The page to prefetch
- * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
- * See {@link #method-load}
- */
- prefetchPage: function(page, options) {
- var me = this,
- pageSize = me.pageSize || me.defaultPageSize,
- start = (page - 1) * me.pageSize,
- total = me.totalCount;
- // No more data to prefetch.
- if (total !== undefined && me.getCount() === total) {
- return;
- }
- // Copy options into a new object so as not to mutate passed in objects
- me.prefetch(Ext.applyIf({
- page : page,
- start : start,
- limit : pageSize
- }, options));
- },
- <span id='Ext-data-Store-method-onProxyPrefetch'> /**
- </span> * Called after the configured proxy completes a prefetch operation.
- * @private
- * @param {Ext.data.Operation} operation The operation that completed
- */
- onProxyPrefetch: function(operation) {
- var me = this,
- resultSet = operation.getResultSet(),
- records = operation.getRecords(),
- successful = operation.wasSuccessful(),
- page = operation.page;
- // Only cache the data if the operation was invoked for the current generation of the page map.
- // If the generation has changed since the request was fired off, it will have been cancelled.
- if (operation.generation === me.pageMap.generation) {
- if (resultSet) {
- me.totalCount = resultSet.total;
- me.fireEvent('totalcountchange', me.totalCount);
- }
- // Remove the loaded page from the outstanding pages hash
- if (page !== undefined) {
- delete me.pageRequests[page];
- }
- // Add the page into the page map.
- // pageAdded event may trigger the onGuaranteedRange
- if (successful) {
- me.cachePage(records, operation.page);
- }
- me.loading = false;
- me.fireEvent('prefetch', me, records, successful, operation);
- //this is a callback that would have been passed to the 'read' function and is optional
- Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
- }
- },
- <span id='Ext-data-Store-method-cachePage'> /**
- </span> * Caches the records in the prefetch and stripes them with their server-side
- * index.
- * @private
- * @param {Ext.data.Model[]} records The records to cache
- * @param {Ext.data.Operation} The associated operation
- */
- cachePage: function(records, page) {
- var me = this;
- if (!Ext.isDefined(me.totalCount)) {
- me.totalCount = records.length;
- me.fireEvent('totalcountchange', me.totalCount);
- }
- // Add the fetched page into the pageCache
- me.pageMap.addPage(page, records);
- },
- <span id='Ext-data-Store-method-rangeCached'> /**
- </span> * Determines if the passed range is available in the page cache.
- * @private
- * @param {Number} start The start index
- * @param {Number} end The end index in the range
- */
- rangeCached: function(start, end) {
- return this.pageMap && this.pageMap.hasRange(start, end);
- },
- <span id='Ext-data-Store-method-pageCached'> /**
- </span> * Determines if the passed page is available in the page cache.
- * @private
- * @param {Number} page The page to find in the page cache.
- */
- pageCached: function(page) {
- return this.pageMap && this.pageMap.hasPage(page);
- },
- <span id='Ext-data-Store-method-rangeSatisfied'> /**
- </span> * Determines if the passed range is available in the page cache.
- * @private
- * @deprecated 4.1.0 use {@link #rangeCached} instead
- * @param {Number} start The start index
- * @param {Number} end The end index in the range
- */
- rangeSatisfied: function(start, end) {
- return this.rangeCached(start, end);
- },
- <span id='Ext-data-Store-method-getPageFromRecordIndex'> /**
- </span> * Determines the page from a record index
- * @param {Number} index The record index
- * @return {Number} The page the record belongs to
- */
- getPageFromRecordIndex: function(index) {
- return Math.floor(index / this.pageSize) + 1;
- },
- <span id='Ext-data-Store-method-onGuaranteedRange'> /**
- </span> * Handles a guaranteed range being loaded
- * @private
- */
- onGuaranteedRange: function(options) {
- var me = this,
- totalCount = me.getTotalCount(),
- start = options.prefetchStart,
- end = ((totalCount - 1) < options.prefetchEnd) ? totalCount - 1 : options.prefetchEnd,
- range;
- end = Math.max(0, end);
- //<debug>
- if (start > end) {
- Ext.log({
- level: 'warn',
- msg: 'Start (' + start + ') was greater than end (' + end +
- ') for the range of records requested (' + start + '-' +
- options.prefetchEnd + ')' + (this.storeId ? ' from store "' + this.storeId + '"' : '')
- });
- }
- //</debug>
- range = me.pageMap.getRange(start, end);
- me.fireEvent('guaranteedrange', range, start, end);
- if (options.cb) {
- options.cb.call(options.scope || me, range, start, end);
- }
- },
- <span id='Ext-data-Store-method-prefetchRange'> /**
- </span> * Ensures that the specified range of rows is present in the cache.
- *
- * Converts the row range to a page range and then only load pages which are not already
- * present in the page cache.
- */
- prefetchRange: function(start, end) {
- var me = this,
- startPage, endPage, page;
- if (!me.rangeCached(start, end)) {
- startPage = me.getPageFromRecordIndex(start);
- endPage = me.getPageFromRecordIndex(end);
- // Ensure that the page cache's max size is correct.
- // Our purgePageCount is the number of additional pages *outside of the required range* which
- // may be kept in the cache. A purgePageCount of zero means unlimited.
- me.pageMap.maxSize = me.purgePageCount ? (endPage - startPage + 1) + me.purgePageCount : 0;
- // We have the range, but ensure that we have a "buffer" of pages around it.
- for (page = startPage; page <= endPage; page++) {
- if (!me.pageCached(page)) {
- me.prefetchPage(page);
- }
- }
- }
- },
- <span id='Ext-data-Store-method-guaranteeRange'> /**
- </span> * Guarantee a specific range, this will load the store with a range (that
- * must be the pageSize or smaller) and take care of any loading that may
- * be necessary.
- */
- guaranteeRange: function(start, end, cb, scope) {
- // Sanity check end point to be within dataset range
- end = (end > this.totalCount) ? this.totalCount - 1 : end;
- var me = this,
- lastRequestStart = me.lastRequestStart,
- options = {
- prefetchStart: start,
- prefetchEnd: end,
- cb: cb,
- scope: scope
- },
- pageAddHandler;
- me.lastRequestStart = start;
- // If data request can be satisfied from the page cache
- if (me.rangeCached(start, end)) {
- // Attempt to keep the page cache primed with pages which encompass the live data range
- if (start < lastRequestStart) {
- start = Math.max(start - me.leadingBufferZone, 0);
- end = Math.min(end + me.trailingBufferZone, me.totalCount - 1);
- } else {
- start = Math.max(Math.min(start - me.trailingBufferZone, me.totalCount - me.pageSize), 0);
- end = Math.min(end + me.leadingBufferZone, me.totalCount - 1);
- }
- // If the prefetch window calculated round the requested range is not already satisfied in the page cache,
- // then arrange to prefetch it.
- if (!me.rangeCached(start, end)) {
- // We have the range, but ensure that we have a "buffer" of pages around it.
- me.prefetchRange(start, end);
- }
- me.onGuaranteedRange(options);
- }
- // At least some of the requested range needs loading from server
- else {
- // Private event used by the LoadMask class to perform masking when the range required for rendering is not found in the cache
- me.fireEvent('cachemiss', me, start, end);
- // Calculate a prefetch range which is centered on the requested data
- start = Math.min(Math.max(Math.floor(start - ((me.leadingBufferZone + me.trailingBufferZone) / 2)), 0), me.totalCount - me.pageSize);
- end = Math.min(Math.max(Math.ceil (end + ((me.leadingBufferZone + me.trailingBufferZone) / 2)), 0), me.totalCount - 1);
- // Add a pageAdded listener, and as soon as the requested range is loaded, fire the guaranteedrange event
- pageAddHandler = function(page, records) {
- if (me.rangeCached(options.prefetchStart, options.prefetchEnd)) {
- // Private event used by the LoadMask class to unmask when the range required for rendering has been loaded into the cache
- me.fireEvent('cachefilled', me, start, end);
- me.pageMap.un('pageAdded', pageAddHandler);
- me.onGuaranteedRange(options);
- }
- };
- me.pageMap.on('pageAdded', pageAddHandler);
- // Prioritize the request for the *exact range that the UI is asking for*.
- // When a page request is in flight, it will not be requested again by checking the me.pageRequests hash,
- // so the request after this will only request the *remaining* unrequested pages .
- me.prefetchRange(options.prefetchStart, options.prefetchEnd);
- // Load the pages that need loading.
- me.prefetchRange(start, end);
- }
- },
- // because prefetchData is stored by index
- // this invalidates all of the prefetchedData
- sort: function() {
- var me = this,
- prefetchData = me.pageMap;
- if (me.buffered) {
- if (me.remoteSort) {
- prefetchData.clear();
- me.callParent(arguments);
- } else {
- me.callParent(arguments);
- }
- } else {
- me.callParent(arguments);
- }
- },
- // overriden to provide striping of the indexes as sorting occurs.
- // this cannot be done inside of sort because datachanged has already
- // fired and will trigger a repaint of the bound view.
- doSort: function(sorterFn) {
- var me = this,
- range,
- ln,
- i;
- if (me.remoteSort) {
- // For a buffered Store, we have to clear the prefetch cache since it is keyed by the index within the dataset.
- // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
- // via the guaranteedrange event
- if (me.buffered) {
- me.pageMap.clear();
- me.loadPage(1);
- } else {
- //the load function will pick up the new sorters and request the sorted data from the proxy
- me.load();
- }
- } else {
- me.data.sortBy(sorterFn);
- if (!me.buffered) {
- range = me.getRange();
- ln = range.length;
- for (i = 0; i < ln; i++) {
- range[i].index = i;
- }
- }
- me.fireEvent('datachanged', me);
- me.fireEvent('refresh', me);
- }
- },
- <span id='Ext-data-Store-method-find'> /**
- </span> * Finds the index of the first matching Record in this store by a specific field value.
- *
- * When store is filtered, finds records only within filter.
- *
- * @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=0] The index to start searching at
- * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
- * @param {Boolean} [caseSensitive=false] True for case sensitive comparison
- * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
- * @return {Number} The matched index or -1
- */
- find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
- var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
- return fn ? this.data.findIndexBy(fn, null, start) : -1;
- },
- <span id='Ext-data-Store-method-findRecord'> /**
- </span> * Finds the first matching Record in this store by a specific field value.
- *
- * When store is filtered, finds records only within filter.
- *
- * @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=0] The index to start searching at
- * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
- * @param {Boolean} [caseSensitive=false] True for case sensitive comparison
- * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
- * @return {Ext.data.Model} The matched record or null
- */
- findRecord: function() {
- var me = this,
- index = me.find.apply(me, arguments);
- return index !== -1 ? me.getAt(index) : null;
- },
- <span id='Ext-data-Store-method-createFilterFn'> /**
- </span> * @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=false] True if we don't care if the filter value is not the full value.
- * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
- * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
- * Ignored if anyMatch is true.
- */
- createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
- if (Ext.isEmpty(value)) {
- return false;
- }
- value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
- return function(r) {
- return value.test(r.data[property]);
- };
- },
- <span id='Ext-data-Store-method-findExact'> /**
- </span> * Finds the index of the first matching Record in this store by a specific field value.
- *
- * When store is filtered, finds records only within filter.
- *
- * @param {String} fieldName The name of the Record field to test.
- * @param {Object} value The value to match the field against.
- * @param {Number} [startIndex=0] The index to start searching at
- * @return {Number} The matched index or -1
- */
- findExact: function(property, value, start) {
- return this.data.findIndexBy(function(rec) {
- return rec.isEqual(rec.get(property), value);
- },
- this, start);
- },
- <span id='Ext-data-Store-method-findBy'> /**
- </span> * Find the index of the first matching Record in this Store by a function.
- * If the function returns `true` it is considered a match.
- *
- * When store is filtered, finds records only within filter.
- *
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
- * using {@link Ext.data.Model#get}.
- * @param {Object} fn.id The ID of the Record passed.
- * @param {Object} [scope] The scope (this reference) in which the function is executed.
- * Defaults to this Store.
- * @param {Number} [startIndex=0] The index to start searching at
- * @return {Number} The matched index or -1
- */
- findBy: function(fn, scope, start) {
- return this.data.findIndexBy(fn, scope, start);
- },
- <span id='Ext-data-Store-method-collect'> /**
- </span> * Collects unique values for a particular dataIndex from this store.
- *
- * @param {String} dataIndex The property to collect
- * @param {Boolean} [allowNull] Pass true to allow null, undefined or empty string values
- * @param {Boolean} [bypassFilter] Pass true to collect from all records, even ones which are filtered.
- * @return {Object[]} An array of the unique values
- */
- collect: function(dataIndex, allowNull, bypassFilter) {
- var me = this,
- data = (bypassFilter === true && me.snapshot) ? me.snapshot : me.data;
- return data.collect(dataIndex, 'data', allowNull);
- },
- <span id='Ext-data-Store-method-getCount'> /**
- </span> * Gets the number of records in store.
- *
- * 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. **Note**: see the Important note in {@link #method-load}.
- *
- * When store is filtered, it's the number of records matching the filter.
- *
- * @return {Number} The number of Records in the Store.
- */
- getCount: function() {
- return this.data.length || 0;
- },
- <span id='Ext-data-Store-method-getTotalCount'> /**
- </span> * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
- * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
- * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
- * could be loaded into the Store if the Store contained all data
- * @return {Number} The total number of Model instances available via the Proxy. 0 returned if
- * no value has been set via the reader.
- */
- getTotalCount: function() {
- return this.totalCount || 0;
- },
- <span id='Ext-data-Store-method-getAt'> /**
- </span> * Get the Record at the specified index.
- *
- * The index is effected by filtering.
- *
- * @param {Number} index The index of the Record to find.
- * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
- */
- getAt: function(index) {
- return this.data.getAt(index);
- },
- <span id='Ext-data-Store-method-getRange'> /**
- </span> * Returns a range of Records between specified indices.
- *
- * This method is effected by filtering.
- *
- * @param {Number} [startIndex=0] The starting index
- * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
- * @return {Ext.data.Model[]} An array of Records
- */
- getRange: function(start, end) {
- return this.data.getRange(start, end);
- },
- <span id='Ext-data-Store-method-getById'> /**
- </span> * Get the Record with the specified id.
- *
- * This method is not effected by filtering, lookup will be performed from all records
- * inside the store, filtered or not.
- *
- * @param {Mixed} id The id of the Record to find.
- * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
- */
- getById: function(id) {
- return (this.snapshot || this.data).findBy(function(record) {
- return record.getId() === id;
- });
- },
- <span id='Ext-data-Store-method-indexOf'> /**
- </span> * Get the index of the record within the store.
- *
- * When store is filtered, records outside of filter will not be found.
- *
- * @param {Ext.data.Model} record The Ext.data.Model object to find.
- * @return {Number} The index of the passed Record. Returns -1 if not found.
- */
- indexOf: function(record) {
- return this.data.indexOf(record);
- },
- <span id='Ext-data-Store-method-indexOfTotal'> /**
- </span> * Get the index within the entire dataset. From 0 to the totalCount.
- *
- * Like #indexOf, this method is effected by filtering.
- *
- * @param {Ext.data.Model} record The Ext.data.Model object to find.
- * @return {Number} The index of the passed Record. Returns -1 if not found.
- */
- indexOfTotal: function(record) {
- var index = record.index;
- if (index || index === 0) {
- return index;
- }
- return this.indexOf(record);
- },
- <span id='Ext-data-Store-method-indexOfId'> /**
- </span> * Get the index within the store of the Record with the passed id.
- *
- * Like #indexOf, this method is effected by filtering.
- *
- * @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.indexOf(this.getById(id));
- },
- <span id='Ext-data-Store-method-removeAll'> /**
- </span> * Removes all items from the store.
- * @param {Boolean} silent Prevent the `clear` event from being fired.
- */
- removeAll: function(silent) {
- var me = this;
- me.clearData();
- if (me.snapshot) {
- me.snapshot.clear();
- }
- // Special handling to synch the PageMap only for removeAll
- // TODO: handle other store/data modifications WRT buffered Stores.
- if (me.pageMap) {
- me.pageMap.clear();
- }
- if (silent !== true) {
- me.fireEvent('clear', me);
- }
- },
- /*
- * Aggregation methods
- */
- <span id='Ext-data-Store-method-first'> /**
- </span> * Convenience function for getting the first model instance in the store.
- *
- * When store is filtered, will return first item within the filter.
- *
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the first record being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
- */
- first: function(grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(function(records) {
- return records.length ? records[0] : undefined;
- }, me, true);
- } else {
- return me.data.first();
- }
- },
- <span id='Ext-data-Store-method-last'> /**
- </span> * Convenience function for getting the last model instance in the store.
- *
- * When store is filtered, will return last item within the filter.
- *
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the last record being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
- */
- last: function(grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(function(records) {
- var len = records.length;
- return len ? records[len - 1] : undefined;
- }, me, true);
- } else {
- return me.data.last();
- }
- },
- <span id='Ext-data-Store-method-sum'> /**
- </span> * Sums the value of `property` for each {@link Ext.data.Model record} between `start`
- * and `end` and returns the result.
- *
- * When store is filtered, only sums items within the filter.
- *
- * @param {String} field A field in each record
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the sum for that group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Number} The sum
- */
- sum: function(field, grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getSum, me, true, [field]);
- } else {
- return me.getSum(me.data.items, field);
- }
- },
- // @private, see sum
- getSum: function(records, field) {
- var total = 0,
- i = 0,
- len = records.length;
- for (; i < len; ++i) {
- total += records[i].get(field);
- }
- return total;
- },
- <span id='Ext-data-Store-method-count'> /**
- </span> * Gets the count of items in the store.
- *
- * When store is filtered, only items within the filter are counted.
- *
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the count for each group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Number} the count
- */
- count: function(grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(function(records) {
- return records.length;
- }, me, true);
- } else {
- return me.getCount();
- }
- },
- <span id='Ext-data-Store-method-min'> /**
- </span> * Gets the minimum value in the store.
- *
- * When store is filtered, only items within the filter are aggregated.
- *
- * @param {String} field The field in each record
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the minimum in the group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Object} The minimum value, if no items exist, undefined.
- */
- min: function(field, grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getMin, me, true, [field]);
- } else {
- return me.getMin(me.data.items, field);
- }
- },
- // @private, see min
- getMin: function(records, field) {
- var i = 1,
- len = records.length,
- value, min;
- if (len > 0) {
- min = records[0].get(field);
- }
- for (; i < len; ++i) {
- value = records[i].get(field);
- if (value < min) {
- min = value;
- }
- }
- return min;
- },
- <span id='Ext-data-Store-method-max'> /**
- </span> * Gets the maximum value in the store.
- *
- * When store is filtered, only items within the filter are aggregated.
- *
- * @param {String} field The field in each record
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the maximum in the group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Object} The maximum value, if no items exist, undefined.
- */
- max: function(field, grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getMax, me, true, [field]);
- } else {
- return me.getMax(me.data.items, field);
- }
- },
- // @private, see max
- getMax: function(records, field) {
- var i = 1,
- len = records.length,
- value,
- max;
- if (len > 0) {
- max = records[0].get(field);
- }
- for (; i < len; ++i) {
- value = records[i].get(field);
- if (value > max) {
- max = value;
- }
- }
- return max;
- },
- <span id='Ext-data-Store-method-average'> /**
- </span> * Gets the average value in the store.
- *
- * When store is filtered, only items within the filter are aggregated.
- *
- * @param {String} field The field in each record
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the group average being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Object} The average value, if no items exist, 0.
- */
- average: function(field, grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getAverage, me, true, [field]);
- } else {
- return me.getAverage(me.data.items, field);
- }
- },
- // @private, see average
- getAverage: function(records, field) {
- var i = 0,
- len = records.length,
- sum = 0;
- if (records.length > 0) {
- for (; i < len; ++i) {
- sum += records[i].get(field);
- }
- return sum / len;
- }
- return 0;
- },
- <span id='Ext-data-Store-method-aggregate'> /**
- </span> * Runs the aggregate function for all the records in the store.
- *
- * When store is filtered, only items within the filter are aggregated.
- *
- * @param {Function} fn The function to execute. The function is called with a single parameter,
- * an array of records for that group.
- * @param {Object} [scope] The scope to execute the function in. Defaults to the store.
- * @param {Boolean} [grouped] True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the group average being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @param {Array} [args] Any arguments to append to the function call
- * @return {Object} An object literal with the group names and their appropriate values.
- */
- aggregate: function(fn, scope, grouped, args) {
- args = args || [];
- if (grouped && this.isGrouped()) {
- var groups = this.getGroups(),
- i = 0,
- len = groups.length,
- out = {},
- group;
- for (; i < len; ++i) {
- group = groups[i];
- out[group.name] = fn.apply(scope || this, [group.children].concat(args));
- }
- return out;
- } else {
- return fn.apply(scope || this, [this.data.items].concat(args));
- }
- },
- <span id='Ext-data-Store-method-commitChanges'> /**
- </span> * Commits all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
- * subscribe to the Store's {@link #event-update update event}, and perform updating when the third parameter is
- * Ext.data.Record.COMMIT.
- */
- commitChanges : function(){
- var me = this,
- recs = me.getModifiedRecords(),
- len = recs.length,
- i = 0;
- for (; i < len; i++){
- recs[i].commit();
- }
- // Since removals are cached in a simple array we can simply reset it here.
- // Adds and updates are managed in the data MixedCollection and should already be current.
- me.removed.length = 0;
- },
- filterNewOnly: function(item){
- return item.phantom === true;
- },
- // Ideally in the future this will use getModifiedRecords, where there will be a param
- // to getNewRecords & getUpdatedRecords to indicate whether to get only the valid
- // records or grab all of them
- getRejectRecords: function() {
- // Return phantom records + updated records
- return Ext.Array.push(this.data.filterBy(this.filterNewOnly).items, this.getUpdatedRecords());
- },
- <span id='Ext-data-Store-method-rejectChanges'> /**
- </span> * {@link Ext.data.Model#reject Rejects} outstanding changes on all {@link #getModifiedRecords modified records}
- * and re-insert any records that were removed locally. Any phantom records will be removed.
- */
- rejectChanges : function() {
- var me = this,
- recs = me.getRejectRecords(),
- len = recs.length,
- i = 0,
- rec;
- for (; i < len; i++) {
- rec = recs[i];
- rec.reject();
- if (rec.phantom) {
- me.remove(rec);
- }
- }
- // Restore removed records back to their original positions
- recs = me.removed;
- len = recs.length;
- for (i = 0; i < len; i++) {
- rec = recs[i];
- me.insert(rec.removedFrom || 0, rec);
- rec.reject();
- }
- // Since removals are cached in a simple array we can simply reset it here.
- // Adds and updates are managed in the data MixedCollection and should already be current.
- me.removed.length = 0;
- }
- }, function() {
- // A dummy empty store with a fieldless Model defined in it.
- // Just for binding to Views which are instantiated with no Store defined.
- // They will be able to run and render fine, and be bound to a generated Store later.
- Ext.regStore('ext-empty-store', {fields: [], proxy: 'memory'});
- <span id='Ext-data-Store-PageMap'> /**
- </span> * @class Ext.data.Store.PageMap
- * @extends Ext.util.LruCache
- * Private class for use by only Store when configured `buffered: true`.
- * @private
- */
- this.prototype.PageMap = new Ext.Class({
- extend: 'Ext.util.LruCache',
- // Maintain a generation counter, so that the Store can reject incoming pages destined for the previous generation
- clear: function(initial) {
- this.generation = (this.generation ||0) + 1;
- this.callParent(arguments);
- },
- getPageFromRecordIndex: this.prototype.getPageFromRecordIndex,
- addPage: function(page, records) {
- this.add(page, records);
- this.fireEvent('pageAdded', page, records);
- },
- getPage: function(page) {
- return this.get(page);
- },
- hasRange: function(start, end) {
- var page = this.getPageFromRecordIndex(start),
- endPage = this.getPageFromRecordIndex(end);
- for (; page <= endPage; page++) {
- if (!this.hasPage(page)) {
- return false;
- }
- }
- return true;
- },
- hasPage: function(page) {
- // We must use this.get to trigger an access so that the page which is checked for presence is not eligible for pruning
- return !!this.get(page);
- },
- getRange: function(start, end) {
- if (!this.hasRange(start, end)) {
- Ext.Error.raise('PageMap asked for range which it does not have');
- }
- var me = this,
- startPage = me.getPageFromRecordIndex(start),
- endPage = me.getPageFromRecordIndex(end),
- dataStart = (startPage - 1) * me.pageSize,
- dataEnd = (endPage * me.pageSize) - 1,
- page = startPage,
- result = [],
- sliceBegin, sliceEnd, doSlice,
- i = 0, len;
- for (; page <= endPage; page++) {
- // First and last pages will need slicing to cut into the actual wanted records
- if (page == startPage) {
- sliceBegin = start - dataStart;
- doSlice = true;
- } else {
- sliceBegin = 0;
- doSlice = false;
- }
- if (page == endPage) {
- sliceEnd = me.pageSize - (dataEnd - end);
- doSlice = true;
- }
- // First and last pages will need slicing
- if (doSlice) {
- Ext.Array.push(result, Ext.Array.slice(me.getPage(page), sliceBegin, sliceEnd));
- } else {
- Ext.Array.push(result, me.getPage(page));
- }
- }
- // Inject the dataset ordinal position into the record as the index
- for (len = result.length; i < len; i++) {
- result[i].index = start++;
- }
- return result;
- }
- });
- });
- </pre>
- </body>
- </html>
|