Store.html 106 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-data-Store'>/**
  19. </span> * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load data via a
  20. * {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting}, {@link #filter filtering}
  21. * and querying the {@link Ext.data.Model model} instances contained within it.
  22. *
  23. * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
  24. *
  25. * // Set up a {@link Ext.data.Model model} to use in our Store
  26. * Ext.define('User', {
  27. * extend: 'Ext.data.Model',
  28. * fields: [
  29. * {name: 'firstName', type: 'string'},
  30. * {name: 'lastName', type: 'string'},
  31. * {name: 'age', type: 'int'},
  32. * {name: 'eyeColor', type: 'string'}
  33. * ]
  34. * });
  35. *
  36. * var myStore = Ext.create('Ext.data.Store', {
  37. * model: 'User',
  38. * proxy: {
  39. * type: 'ajax',
  40. * url: '/users.json',
  41. * reader: {
  42. * type: 'json',
  43. * root: 'users'
  44. * }
  45. * },
  46. * autoLoad: true
  47. * });
  48. *
  49. * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy to use a
  50. * {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object - {@link
  51. * Ext.data.reader.Json see the docs on JsonReader} for details.
  52. *
  53. * ## Inline data
  54. *
  55. * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #cfg-data} into
  56. * Model instances:
  57. *
  58. * Ext.create('Ext.data.Store', {
  59. * model: 'User',
  60. * data : [
  61. * {firstName: 'Ed', lastName: 'Spencer'},
  62. * {firstName: 'Tommy', lastName: 'Maintz'},
  63. * {firstName: 'Aaron', lastName: 'Conran'},
  64. * {firstName: 'Jamie', lastName: 'Avins'}
  65. * ]
  66. * });
  67. *
  68. * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't
  69. * need to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode
  70. * the data structure, use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory
  71. * MemoryProxy} docs for an example).
  72. *
  73. * Additional data can also be loaded locally using {@link #method-add}.
  74. *
  75. * ## Dynamic Loading
  76. *
  77. * Stores can be dynamically updated by calling the {@link #method-load} method:
  78. *
  79. * store.load({
  80. * params: {
  81. * group: 3,
  82. * type: 'user'
  83. * },
  84. * callback: function(records, operation, success) {
  85. * // do something after the load finishes
  86. * },
  87. * scope: this
  88. * });
  89. *
  90. * Here a bunch of arbitrary parameters is passed along with the load request and a callback function is set
  91. * up to do something after the loading is over.
  92. *
  93. * ## Loading Nested Data
  94. *
  95. * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
  96. * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load
  97. * a nested dataset and allow the Reader to automatically populate the associated models. Below is a brief example, see
  98. * the {@link Ext.data.reader.Reader} intro docs for a full explanation:
  99. *
  100. * var store = Ext.create('Ext.data.Store', {
  101. * autoLoad: true,
  102. * model: &quot;User&quot;,
  103. * proxy: {
  104. * type: 'ajax',
  105. * url: 'users.json',
  106. * reader: {
  107. * type: 'json',
  108. * root: 'users'
  109. * }
  110. * }
  111. * });
  112. *
  113. * Which would consume a response like this:
  114. *
  115. * {
  116. * &quot;users&quot;: [{
  117. * &quot;id&quot;: 1,
  118. * &quot;name&quot;: &quot;Ed&quot;,
  119. * &quot;orders&quot;: [{
  120. * &quot;id&quot;: 10,
  121. * &quot;total&quot;: 10.76,
  122. * &quot;status&quot;: &quot;invoiced&quot;
  123. * },{
  124. * &quot;id&quot;: 11,
  125. * &quot;total&quot;: 13.45,
  126. * &quot;status&quot;: &quot;shipped&quot;
  127. * }]
  128. * }]
  129. * }
  130. *
  131. * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
  132. *
  133. * ## Filtering and Sorting
  134. *
  135. * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and
  136. * {@link #cfg-filters} are held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage.
  137. * Usually it is sufficient to either just specify sorters and filters in the Store configuration or call {@link #sort}
  138. * or {@link #filter}:
  139. *
  140. * var store = Ext.create('Ext.data.Store', {
  141. * model: 'User',
  142. * sorters: [{
  143. * property: 'age',
  144. * direction: 'DESC'
  145. * }, {
  146. * property: 'firstName',
  147. * direction: 'ASC'
  148. * }],
  149. *
  150. * filters: [{
  151. * property: 'firstName',
  152. * value: /Ed/
  153. * }]
  154. * });
  155. *
  156. * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By
  157. * default, sorting and filtering are both performed locally by the Store - see {@link #remoteSort} and
  158. * {@link #remoteFilter} to allow the server to perform these operations instead.
  159. *
  160. * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter
  161. * to the Store and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all
  162. * existing filters). Note that by default {@link #sortOnFilter} is set to true, which means that your sorters are
  163. * automatically reapplied if using local sorting.
  164. *
  165. * store.filter('eyeColor', 'Brown');
  166. *
  167. * Change the sorting at any time by calling {@link #sort}:
  168. *
  169. * store.sort('height', 'ASC');
  170. *
  171. * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no
  172. * arguments, the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new
  173. * ones, just add them to the MixedCollection:
  174. *
  175. * store.sorters.add(new Ext.util.Sorter({
  176. * property : 'shoeSize',
  177. * direction: 'ASC'
  178. * }));
  179. *
  180. * store.sort();
  181. *
  182. * ## Registering with StoreManager
  183. *
  184. * Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link
  185. * Ext.data.StoreManager StoreManager}. This makes it easy to reuse the same store in multiple views:
  186. *
  187. * //this store can be used several times
  188. * Ext.create('Ext.data.Store', {
  189. * model: 'User',
  190. * storeId: 'usersStore'
  191. * });
  192. *
  193. * new Ext.List({
  194. * store: 'usersStore',
  195. * //other config goes here
  196. * });
  197. *
  198. * new Ext.view.View({
  199. * store: 'usersStore',
  200. * //other config goes here
  201. * });
  202. *
  203. * ## Further Reading
  204. *
  205. * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
  206. * pieces and how they fit together, see:
  207. *
  208. * - {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
  209. * - {@link Ext.data.Model Model} - the core class in the data package
  210. * - {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
  211. *
  212. * @author Ed Spencer
  213. */
  214. Ext.define('Ext.data.Store', {
  215. extend: 'Ext.data.AbstractStore',
  216. alias: 'store.store',
  217. // Required classes must be loaded before the definition callback runs
  218. // The class definition callback creates a dummy Store which requires that
  219. // all the classes below have been loaded.
  220. requires: [
  221. 'Ext.data.StoreManager',
  222. 'Ext.data.Model',
  223. 'Ext.data.proxy.Ajax',
  224. 'Ext.data.proxy.Memory',
  225. 'Ext.data.reader.Json',
  226. 'Ext.data.writer.Json',
  227. 'Ext.util.LruCache'
  228. ],
  229. uses: [
  230. 'Ext.ModelManager',
  231. 'Ext.util.Grouper'
  232. ],
  233. remoteSort: false,
  234. remoteFilter: false,
  235. <span id='Ext-data-Store-cfg-remoteGroup'> /**
  236. </span> * @cfg {Boolean} remoteGroup
  237. * True if the grouping should apply on the server side, false if it is local only. If the
  238. * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
  239. * helper, automatically sending the grouping information to the server.
  240. */
  241. remoteGroup : false,
  242. <span id='Ext-data-Store-cfg-proxy'> /**
  243. </span> * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
  244. * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
  245. * see {@link #setProxy} for details.
  246. */
  247. <span id='Ext-data-Store-cfg-data'> /**
  248. </span> * @cfg {Object[]/Ext.data.Model[]} data
  249. * Array of Model instances or data objects to load locally. See &quot;Inline data&quot; above for details.
  250. */
  251. <span id='Ext-data-Store-cfg-groupField'> /**
  252. </span> * @cfg {String} groupField
  253. * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
  254. * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
  255. * level of grouping, and groups can be fetched via the {@link #getGroups} method.
  256. */
  257. groupField: undefined,
  258. <span id='Ext-data-Store-cfg-groupDir'> /**
  259. </span> * @cfg {String} groupDir
  260. * The direction in which sorting should be applied when grouping. Supported values are &quot;ASC&quot; and &quot;DESC&quot;.
  261. */
  262. groupDir: &quot;ASC&quot;,
  263. <span id='Ext-data-Store-cfg-trailingBufferZone'> /**
  264. </span> * @cfg {Number} trailingBufferZone
  265. * When {@link #buffered}, the number of extra records to keep cached on the trailing side of scrolling buffer
  266. * as scrolling proceeds. A larger number means fewer replenishments from the server.
  267. */
  268. trailingBufferZone: 25,
  269. <span id='Ext-data-Store-cfg-leadingBufferZone'> /**
  270. </span> * @cfg {Number} leadingBufferZone
  271. * When {@link #buffered}, the number of extra rows to keep cached on the leading side of scrolling buffer
  272. * as scrolling proceeds. A larger number means fewer replenishments from the server.
  273. */
  274. leadingBufferZone: 200,
  275. <span id='Ext-data-Store-cfg-pageSize'> /**
  276. </span> * @cfg {Number} pageSize
  277. * The number of records considered to form a 'page'. This is used to power the built-in
  278. * paging using the nextPage and previousPage functions when the grid is paged using a
  279. * {@link Ext.toolbar.Paging PagingScroller} Defaults to 25.
  280. *
  281. * If this Store is {@link #buffered}, pages are loaded into a page cache before the Store's
  282. * data is updated from the cache. The pageSize is the number of rows loaded into the cache in one request.
  283. * This will not affect the rendering of a buffered grid, but a larger page size will mean fewer loads.
  284. *
  285. * In a buffered grid, scrolling is monitored, and the page cache is kept primed with data ahead of the
  286. * direction of scroll to provide rapid access to data when scrolling causes it to be required. Several pages
  287. * in advance may be requested depending on various parameters.
  288. *
  289. * It is recommended to tune the {@link #pageSize}, {@link #trailingBufferZone} and
  290. * {@link #leadingBufferZone} configurations based upon the conditions pertaining in your deployed application.
  291. *
  292. * The provided SDK example `examples/grid/infinite-scroll-grid-tuner.html` can be used to experiment with
  293. * different settings including simulating Ajax latency.
  294. */
  295. pageSize: undefined,
  296. <span id='Ext-data-Store-property-currentPage'> /**
  297. </span> * @property {Number} currentPage
  298. * The page that the Store has most recently loaded (see {@link #loadPage})
  299. */
  300. currentPage: 1,
  301. <span id='Ext-data-Store-cfg-clearOnPageLoad'> /**
  302. </span> * @cfg {Boolean} clearOnPageLoad
  303. * True to empty the store when loading another page via {@link #loadPage},
  304. * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
  305. * large data sets to be loaded one page at a time but rendered all together.
  306. */
  307. clearOnPageLoad: true,
  308. <span id='Ext-data-Store-property-loading'> /**
  309. </span> * @property {Boolean} loading
  310. * True if the Store is currently loading via its Proxy
  311. * @private
  312. */
  313. loading: false,
  314. <span id='Ext-data-Store-cfg-sortOnFilter'> /**
  315. </span> * @cfg {Boolean} sortOnFilter
  316. * For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
  317. * causing the sorters to be reapplied after filtering. Defaults to true
  318. */
  319. sortOnFilter: true,
  320. <span id='Ext-data-Store-cfg-buffered'> /**
  321. </span> * @cfg {Boolean} buffered
  322. * Allows the Store to prefetch and cache in a **page cache**, pages of Records, and to then satisfy
  323. * loading requirements from this page cache.
  324. *
  325. * To use buffered Stores, initiate the process by loading the first page. The number of rows rendered are
  326. * determined automatically, and the range of pages needed to keep the cache primed for scrolling is
  327. * requested and cached.
  328. * Example:
  329. *
  330. * // Load page 1
  331. * myStore.loadPage(1);
  332. *
  333. * A {@link Ext.grid.PagingScroller PagingScroller} is instantiated which will monitor the scrolling in the grid, and
  334. * refresh the view's rows from the page cache as needed. It will also pull new data into the page
  335. * cache when scrolling of the view draws upon data near either end of the prefetched data.
  336. *
  337. * The margins which trigger view refreshing from the prefetched data are {@link Ext.grid.PagingScroller#numFromEdge},
  338. * {@link Ext.grid.PagingScroller#leadingBufferZone} and {@link Ext.grid.PagingScroller#trailingBufferZone}.
  339. *
  340. * The margins which trigger loading more data into the page cache are, {@link #leadingBufferZone} and
  341. * {@link #trailingBufferZone}.
  342. *
  343. * By defult, only 5 pages of data are cached in the page cache, with pages &quot;scrolling&quot; out of the buffer
  344. * as the view moves down through the dataset.
  345. * Setting this value to zero means that no pages are *ever* scrolled out of the page cache, and
  346. * that eventually the whole dataset may become present in the page cache. This is sometimes desirable
  347. * as long as datasets do not reach astronomical proportions.
  348. *
  349. * Selection state may be maintained across page boundaries by configuring the SelectionModel not to discard
  350. * records from its collection when those Records cycle out of the Store's primary collection. This is done
  351. * by configuring the SelectionModel like this:
  352. *
  353. * selModel: {
  354. * pruneRemoved: false
  355. * }
  356. *
  357. */
  358. buffered: false,
  359. <span id='Ext-data-Store-cfg-purgePageCount'> /**
  360. </span> * @cfg {Number} purgePageCount
  361. * *Valid only when used with a {@link Ext.data.Store#buffered buffered} Store.*
  362. *
  363. * The number of pages *additional to the required buffered range* to keep in the prefetch cache before purging least recently used records.
  364. *
  365. * For example, if the height of the view area and the configured {@link #trailingBufferZone} and {@link #leadingBufferZone} require that there
  366. * 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.
  367. *
  368. * A value of 0 indicates to never purge the prefetched data.
  369. */
  370. purgePageCount: 5,
  371. <span id='Ext-data-Store-cfg-clearRemovedOnLoad'> /**
  372. </span> * @cfg {Boolean} [clearRemovedOnLoad=true]
  373. * True to clear anything in the {@link #removed} record collection when the store loads.
  374. */
  375. clearRemovedOnLoad: true,
  376. defaultPageSize: 25,
  377. // Private. Used as parameter to loadRecords
  378. addRecordsOptions: {
  379. addRecords: true
  380. },
  381. statics: {
  382. recordIdFn: function(record) {
  383. return record.internalId;
  384. },
  385. recordIndexFn: function(record) {
  386. return record.index;
  387. }
  388. },
  389. onClassExtended: function(cls, data, hooks) {
  390. var model = data.model,
  391. onBeforeClassCreated;
  392. if (typeof model == 'string') {
  393. onBeforeClassCreated = hooks.onBeforeCreated;
  394. hooks.onBeforeCreated = function() {
  395. var me = this,
  396. args = arguments;
  397. Ext.require(model, function() {
  398. onBeforeClassCreated.apply(me, args);
  399. });
  400. };
  401. }
  402. },
  403. <span id='Ext-data-Store-method-constructor'> /**
  404. </span> * Creates the store.
  405. * @param {Object} [config] Config object
  406. */
  407. constructor: function(config) {
  408. // Clone the config so we don't modify the original config object
  409. config = Ext.Object.merge({}, config);
  410. var me = this,
  411. groupers = config.groupers || me.groupers,
  412. groupField = config.groupField || me.groupField,
  413. proxy,
  414. data;
  415. <span id='Ext-data-Store-event-beforeprefetch'> /**
  416. </span> * @event beforeprefetch
  417. * Fires before a prefetch occurs. Return false to cancel.
  418. * @param {Ext.data.Store} this
  419. * @param {Ext.data.Operation} operation The associated operation
  420. */
  421. <span id='Ext-data-Store-event-groupchange'> /**
  422. </span> * @event groupchange
  423. * Fired whenever the grouping in the grid changes
  424. * @param {Ext.data.Store} store The store
  425. * @param {Ext.util.Grouper[]} groupers The array of grouper objects
  426. */
  427. <span id='Ext-data-Store-event-prefetch'> /**
  428. </span> * @event prefetch
  429. * Fires whenever records have been prefetched
  430. * @param {Ext.data.Store} this
  431. * @param {Ext.data.Model[]} records An array of records.
  432. * @param {Boolean} successful True if the operation was successful.
  433. * @param {Ext.data.Operation} operation The associated operation
  434. */
  435. data = config.data || me.data;
  436. <span id='Ext-data-Store-property-data'> /**
  437. </span> * @property {Ext.util.MixedCollection} data
  438. * The MixedCollection that holds this store's local cache of records.
  439. */
  440. me.data = new Ext.util.MixedCollection(false, Ext.data.Store.recordIdFn);
  441. if (data) {
  442. me.inlineData = data;
  443. delete config.data;
  444. }
  445. if (!groupers &amp;&amp; groupField) {
  446. groupers = [{
  447. property : groupField,
  448. direction: config.groupDir || me.groupDir
  449. }];
  450. }
  451. delete config.groupers;
  452. <span id='Ext-data-Store-property-groupers'> /**
  453. </span> * @property {Ext.util.MixedCollection} groupers
  454. * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store.
  455. */
  456. me.groupers = new Ext.util.MixedCollection();
  457. me.groupers.addAll(me.decodeGroupers(groupers));
  458. this.callParent([config]);
  459. // don't use *config* anymore from here on... use *me* instead...
  460. if (me.buffered) {
  461. <span id='Ext-data-Store-property-pageMap'> /**
  462. </span> * @property {Ext.data.Store.PageMap} pageMap
  463. * Internal PageMap instance.
  464. * @private
  465. */
  466. me.pageMap = new me.PageMap({
  467. pageSize: me.pageSize,
  468. maxSize: me.purgePageCount,
  469. listeners: {
  470. // Whenever PageMap gets cleared, it means we re no longer interested in
  471. // any outstanding page prefetches, so cancel tham all
  472. clear: me.cancelAllPrefetches,
  473. scope: me
  474. }
  475. });
  476. me.pageRequests = {};
  477. me.sortOnLoad = false;
  478. me.filterOnLoad = false;
  479. }
  480. // Only sort by group fields if we are doing local grouping
  481. if (me.remoteGroup) {
  482. me.remoteSort = true;
  483. }
  484. if (me.groupers.items.length &amp;&amp; !me.remoteGroup) {
  485. me.sort(me.groupers.items, 'prepend', false);
  486. }
  487. proxy = me.proxy;
  488. data = me.inlineData;
  489. // Page size for non-buffered Store defaults to 25
  490. // For a buffered Store, the default page size is taken from the initial call to prefetch.
  491. if (!me.buffered &amp;&amp; !me.pageSize) {
  492. me.pageSize = me.defaultPageSize;
  493. }
  494. if (data) {
  495. if (proxy instanceof Ext.data.proxy.Memory) {
  496. proxy.data = data;
  497. me.read();
  498. } else {
  499. me.add.apply(me, [data]);
  500. }
  501. me.sort();
  502. delete me.inlineData;
  503. } else if (me.autoLoad) {
  504. Ext.defer(me.load, 10, me, [ typeof me.autoLoad === 'object' ? me.autoLoad : undefined ]);
  505. // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
  506. // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
  507. }
  508. },
  509. // private override
  510. // After destroying the Store, clear the page prefetch cache
  511. destroyStore: function() {
  512. this.callParent(arguments);
  513. // Release cached pages.
  514. // Will also cancel outstanding prefetch requests, and cause a generation change
  515. // so that incoming prefetch data will be ignored.
  516. if (this.pageMap) {
  517. this.pageMap.clear();
  518. }
  519. },
  520. onBeforeSort: function() {
  521. var groupers = this.groupers;
  522. if (groupers.getCount() &gt; 0) {
  523. this.sort(groupers.items, 'prepend', false);
  524. }
  525. },
  526. <span id='Ext-data-Store-method-decodeGroupers'> /**
  527. </span> * @private
  528. * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
  529. * @param {Object[]} groupers The groupers array
  530. * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
  531. */
  532. decodeGroupers: function(groupers) {
  533. if (!Ext.isArray(groupers)) {
  534. if (groupers === undefined) {
  535. groupers = [];
  536. } else {
  537. groupers = [groupers];
  538. }
  539. }
  540. var length = groupers.length,
  541. Grouper = Ext.util.Grouper,
  542. config, i, result = [];
  543. for (i = 0; i &lt; length; i++) {
  544. config = groupers[i];
  545. if (!(config instanceof Grouper)) {
  546. if (Ext.isString(config)) {
  547. config = {
  548. property: config
  549. };
  550. }
  551. config = Ext.apply({
  552. root : 'data',
  553. direction: &quot;ASC&quot;
  554. }, config);
  555. //support for 3.x style sorters where a function can be defined as 'fn'
  556. if (config.fn) {
  557. config.sorterFn = config.fn;
  558. }
  559. //support a function to be passed as a sorter definition
  560. if (typeof config == 'function') {
  561. config = {
  562. sorterFn: config
  563. };
  564. }
  565. // return resulting Groupers in a separate array so as not to mutate passed in data objects.
  566. result.push(new Grouper(config));
  567. } else {
  568. result.push(config);
  569. }
  570. }
  571. return result;
  572. },
  573. <span id='Ext-data-Store-method-group'> /**
  574. </span> * Groups data inside the store.
  575. * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's
  576. * configured {@link Ext.data.Model Model}, or an Array of grouper configurations.
  577. * @param {String} [direction=&quot;ASC&quot;] The overall direction to group the data by.
  578. */
  579. group: function(groupers, direction) {
  580. var me = this,
  581. hasNew = false,
  582. grouper,
  583. newGroupers;
  584. if (Ext.isArray(groupers)) {
  585. newGroupers = groupers;
  586. } else if (Ext.isObject(groupers)) {
  587. newGroupers = [groupers];
  588. } else if (Ext.isString(groupers)) {
  589. grouper = me.groupers.get(groupers);
  590. if (!grouper) {
  591. grouper = {
  592. property : groupers,
  593. direction: direction
  594. };
  595. newGroupers = [grouper];
  596. } else if (direction === undefined) {
  597. grouper.toggle();
  598. } else {
  599. grouper.setDirection(direction);
  600. }
  601. }
  602. if (newGroupers &amp;&amp; newGroupers.length) {
  603. hasNew = true;
  604. newGroupers = me.decodeGroupers(newGroupers);
  605. me.groupers.clear();
  606. me.groupers.addAll(newGroupers);
  607. }
  608. if (me.remoteGroup) {
  609. if (me.buffered) {
  610. me.pageMap.clear();
  611. me.loadPage(1, { groupChange: true });
  612. } else {
  613. me.load({
  614. scope: me,
  615. callback: me.fireGroupChange
  616. });
  617. }
  618. } else {
  619. // need to explicitly force a sort if we have groupers
  620. me.sort(null, null, null, hasNew);
  621. me.fireGroupChange();
  622. }
  623. },
  624. <span id='Ext-data-Store-method-clearGrouping'> /**
  625. </span> * Clear any groupers in the store
  626. */
  627. clearGrouping: function() {
  628. var me = this,
  629. groupers = me.groupers.items,
  630. gLen = groupers.length,
  631. grouper, g;
  632. for (g = 0; g &lt; gLen; g++) {
  633. grouper = groupers[g];
  634. me.sorters.remove(grouper);
  635. }
  636. me.groupers.clear();
  637. if (me.remoteGroup) {
  638. if (me.buffered) {
  639. me.pageMap.clear();
  640. me.loadPage(1, { groupChange: true });
  641. } else {
  642. me.load({
  643. scope: me,
  644. callback: me.fireGroupChange
  645. });
  646. }
  647. } else {
  648. me.sort();
  649. me.fireGroupChange();
  650. }
  651. },
  652. <span id='Ext-data-Store-method-isGrouped'> /**
  653. </span> * Checks if the store is currently grouped
  654. * @return {Boolean} True if the store is grouped.
  655. */
  656. isGrouped: function() {
  657. return this.groupers.getCount() &gt; 0;
  658. },
  659. <span id='Ext-data-Store-method-fireGroupChange'> /**
  660. </span> * Fires the groupchange event. Abstracted out so we can use it
  661. * as a callback
  662. * @private
  663. */
  664. fireGroupChange: function() {
  665. this.fireEvent('groupchange', this, this.groupers);
  666. },
  667. <span id='Ext-data-Store-method-getGroups'> /**
  668. </span> * Returns an array containing the result of applying grouping to the records in this store.
  669. * See {@link #groupField}, {@link #groupDir} and {@link #getGroupString}. Example for a store
  670. * containing records with a color field:
  671. *
  672. * var myStore = Ext.create('Ext.data.Store', {
  673. * groupField: 'color',
  674. * groupDir : 'DESC'
  675. * });
  676. *
  677. * myStore.getGroups(); // returns:
  678. * [
  679. * {
  680. * name: 'yellow',
  681. * children: [
  682. * // all records where the color field is 'yellow'
  683. * ]
  684. * },
  685. * {
  686. * name: 'red',
  687. * children: [
  688. * // all records where the color field is 'red'
  689. * ]
  690. * }
  691. * ]
  692. *
  693. * Group contents are effected by filtering.
  694. *
  695. * @param {String} [groupName] Pass in an optional groupName argument to access a specific
  696. * group as defined by {@link #getGroupString}.
  697. * @return {Object/Object[]} The grouped data
  698. */
  699. getGroups: function(requestGroupString) {
  700. var records = this.data.items,
  701. length = records.length,
  702. groups = [],
  703. pointers = {},
  704. record,
  705. groupStr,
  706. group,
  707. i;
  708. for (i = 0; i &lt; length; i++) {
  709. record = records[i];
  710. groupStr = this.getGroupString(record);
  711. group = pointers[groupStr];
  712. if (group === undefined) {
  713. group = {
  714. name: groupStr,
  715. children: []
  716. };
  717. groups.push(group);
  718. pointers[groupStr] = group;
  719. }
  720. group.children.push(record);
  721. }
  722. return requestGroupString ? pointers[requestGroupString] : groups;
  723. },
  724. <span id='Ext-data-Store-method-getGroupsForGrouper'> /**
  725. </span> * @private
  726. * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
  727. * matching a certain group.
  728. */
  729. getGroupsForGrouper: function(records, grouper) {
  730. var length = records.length,
  731. groups = [],
  732. oldValue,
  733. newValue,
  734. record,
  735. group,
  736. i;
  737. for (i = 0; i &lt; length; i++) {
  738. record = records[i];
  739. newValue = grouper.getGroupString(record);
  740. if (newValue !== oldValue) {
  741. group = {
  742. name: newValue,
  743. grouper: grouper,
  744. records: []
  745. };
  746. groups.push(group);
  747. }
  748. group.records.push(record);
  749. oldValue = newValue;
  750. }
  751. return groups;
  752. },
  753. <span id='Ext-data-Store-method-getGroupsForGrouperIndex'> /**
  754. </span> * @private
  755. * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
  756. * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
  757. * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
  758. *
  759. * @param {Ext.data.Model[]} records The set or subset of records to group
  760. * @param {Number} grouperIndex The grouper index to retrieve
  761. * @return {Object[]} The grouped records
  762. */
  763. getGroupsForGrouperIndex: function(records, grouperIndex) {
  764. var me = this,
  765. groupers = me.groupers,
  766. grouper = groupers.getAt(grouperIndex),
  767. groups = me.getGroupsForGrouper(records, grouper),
  768. length = groups.length,
  769. i;
  770. if (grouperIndex + 1 &lt; groupers.length) {
  771. for (i = 0; i &lt; length; i++) {
  772. groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
  773. }
  774. }
  775. for (i = 0; i &lt; length; i++) {
  776. groups[i].depth = grouperIndex;
  777. }
  778. return groups;
  779. },
  780. <span id='Ext-data-Store-method-getGroupData'> /**
  781. </span> * @private
  782. * Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
  783. * this case grouping by genre and then author in a fictional books dataset):
  784. *
  785. * [
  786. * {
  787. * name: 'Fantasy',
  788. * depth: 0,
  789. * records: [
  790. * //book1, book2, book3, book4
  791. * ],
  792. * children: [
  793. * {
  794. * name: 'Rowling',
  795. * depth: 1,
  796. * records: [
  797. * //book1, book2
  798. * ]
  799. * },
  800. * {
  801. * name: 'Tolkein',
  802. * depth: 1,
  803. * records: [
  804. * //book3, book4
  805. * ]
  806. * }
  807. * ]
  808. * }
  809. * ]
  810. *
  811. * @param {Boolean} [sort=true] True to call {@link #sort} before finding groups. Sorting is required to make grouping
  812. * function correctly so this should only be set to false if the Store is known to already be sorted correctly.
  813. * @return {Object[]} The group data
  814. */
  815. getGroupData: function(sort) {
  816. var me = this;
  817. if (sort !== false) {
  818. me.sort();
  819. }
  820. return me.getGroupsForGrouperIndex(me.data.items, 0);
  821. },
  822. <span id='Ext-data-Store-method-getGroupString'> /**
  823. </span> * Returns the string to group on for a given model instance. The default implementation of this method returns
  824. * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
  825. * group by the first letter of a model's 'name' field, use the following code:
  826. *
  827. * Ext.create('Ext.data.Store', {
  828. * groupDir: 'ASC',
  829. * getGroupString: function(instance) {
  830. * return instance.get('name')[0];
  831. * }
  832. * });
  833. *
  834. * @param {Ext.data.Model} instance The model instance
  835. * @return {String} The string to compare when forming groups
  836. */
  837. getGroupString: function(instance) {
  838. var group = this.groupers.first();
  839. if (group) {
  840. return instance.get(group.property);
  841. }
  842. return '';
  843. },
  844. <span id='Ext-data-Store-method-insert'> /**
  845. </span> * Inserts Model instances into the Store at the given index and fires the {@link #event-add} event.
  846. * See also {@link #method-add}.
  847. *
  848. * @param {Number} index The start index at which to insert the passed Records.
  849. * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the store.
  850. */
  851. insert: function(index, records) {
  852. var me = this,
  853. sync = false,
  854. i,
  855. record,
  856. len;
  857. records = [].concat(records);
  858. for (i = 0,len = records.length; i &lt; len; i++) {
  859. record = me.createModel(records[i]);
  860. record.set(me.modelDefaults);
  861. // reassign the model in the array in case it wasn't created yet
  862. records[i] = record;
  863. me.data.insert(index + i, record);
  864. record.join(me);
  865. sync = sync || record.phantom === true;
  866. }
  867. if (me.snapshot) {
  868. me.snapshot.addAll(records);
  869. }
  870. if (me.requireSort) {
  871. // suspend events so the usual data changed events don't get fired.
  872. me.suspendEvents();
  873. me.sort();
  874. me.resumeEvents();
  875. }
  876. me.fireEvent('add', me, records, index);
  877. me.fireEvent('datachanged', me);
  878. if (me.autoSync &amp;&amp; sync &amp;&amp; !me.autoSyncSuspended) {
  879. me.sync();
  880. }
  881. },
  882. <span id='Ext-data-Store-method-add'> /**
  883. </span> * Adds Model instance to the Store. This method accepts either:
  884. *
  885. * - An array of Model instances or Model configuration objects.
  886. * - Any number of Model instance or Model configuration object arguments.
  887. *
  888. * The new Model instances will be added at the end of the existing collection.
  889. *
  890. * Sample usage:
  891. *
  892. * myStore.add({some: 'data'}, {some: 'other data'});
  893. *
  894. * Note that if this Store is sorted, the new Model instances will be inserted
  895. * at the correct point in the Store to maintain the sort order.
  896. *
  897. * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
  898. * or Model configuration objects, or variable number of Model instance or config arguments.
  899. * @return {Ext.data.Model[]} The model instances that were added
  900. */
  901. add: function(records) {
  902. //accept both a single-argument array of records, or any number of record arguments
  903. if (!Ext.isArray(records)) {
  904. records = Array.prototype.slice.apply(arguments);
  905. } else {
  906. // Create an array copy
  907. records = records.slice(0);
  908. }
  909. var me = this,
  910. i = 0,
  911. length = records.length,
  912. record,
  913. isSorted = !me.remoteSort &amp;&amp; me.sorters &amp;&amp; me.sorters.items.length;
  914. // If this Store is sorted, and they only passed one Record (99% or use cases)
  915. // then it's much more efficient to add it sorted than to append and then sort.
  916. if (isSorted &amp;&amp; length === 1) {
  917. return [ me.addSorted(me.createModel(records[0])) ];
  918. }
  919. for (; i &lt; length; i++) {
  920. record = me.createModel(records[i]);
  921. // reassign the model in the array in case it wasn't created yet
  922. records[i] = record;
  923. }
  924. // If this sort is sorted, set the flag used by the insert method to sort
  925. // before firing events.
  926. if (isSorted) {
  927. me.requireSort = true;
  928. }
  929. me.insert(me.data.length, records);
  930. delete me.requireSort;
  931. return records;
  932. },
  933. <span id='Ext-data-Store-method-addSorted'> /**
  934. </span> * (Local sort only) Inserts the passed Record into the Store at the index where it
  935. * should go based on the current sort information.
  936. *
  937. * @param {Ext.data.Record} record
  938. */
  939. addSorted: function(record) {
  940. var me = this,
  941. index = me.data.findInsertionIndex(record, me.generateComparator());
  942. me.insert(index, record);
  943. return record;
  944. },
  945. <span id='Ext-data-Store-method-createModel'> /**
  946. </span> * Converts a literal to a model, if it's not a model already
  947. * @private
  948. * @param {Ext.data.Model/Object} record The record to create
  949. * @return {Ext.data.Model}
  950. */
  951. createModel: function(record) {
  952. if (!record.isModel) {
  953. record = Ext.ModelManager.create(record, this.model);
  954. }
  955. return record;
  956. },
  957. <span id='Ext-data-Store-method-each'> /**
  958. </span> * Calls the specified function for each {@link Ext.data.Model record} in the store.
  959. *
  960. * When store is filtered, only loops over the filtered records.
  961. *
  962. * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
  963. * Returning `false` aborts and exits the iteration.
  964. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  965. * Defaults to the current {@link Ext.data.Model record} in the iteration.
  966. */
  967. each: function(fn, scope) {
  968. var data = this.data.items,
  969. dLen = data.length,
  970. record, d;
  971. for (d = 0; d &lt; dLen; d++) {
  972. record = data[d];
  973. if (fn.call(scope || record, record, d, dLen) === false) {
  974. break;
  975. }
  976. }
  977. },
  978. <span id='Ext-data-Store-method-remove'> /**
  979. </span> * Removes the given record from the Store, firing the 'remove' event for each instance that is removed,
  980. * plus a single 'datachanged' event after removal.
  981. *
  982. * @param {Ext.data.Model/Ext.data.Model[]} records Model instance or array of instances to remove.
  983. */
  984. remove: function(records, /* private */ isMove) {
  985. if (!Ext.isArray(records)) {
  986. records = [records];
  987. }
  988. /*
  989. * Pass the isMove parameter if we know we're going to be re-inserting this record
  990. */
  991. isMove = isMove === true;
  992. var me = this,
  993. sync = false,
  994. i = 0,
  995. length = records.length,
  996. isNotPhantom,
  997. index,
  998. record;
  999. for (; i &lt; length; i++) {
  1000. record = records[i];
  1001. index = me.data.indexOf(record);
  1002. if (me.snapshot) {
  1003. me.snapshot.remove(record);
  1004. }
  1005. if (index &gt; -1) {
  1006. isNotPhantom = record.phantom !== true;
  1007. // don't push phantom records onto removed
  1008. if (!isMove &amp;&amp; isNotPhantom) {
  1009. // Store the index the record was removed from so that rejectChanges can re-insert at the correct place.
  1010. // The record's index property won't do, as that is the index in the overall dataset when Store is buffered.
  1011. record.removedFrom = index;
  1012. me.removed.push(record);
  1013. }
  1014. record.unjoin(me);
  1015. me.data.remove(record);
  1016. sync = sync || isNotPhantom;
  1017. me.fireEvent('remove', me, record, index);
  1018. }
  1019. }
  1020. me.fireEvent('datachanged', me);
  1021. if (!isMove &amp;&amp; me.autoSync &amp;&amp; sync &amp;&amp; !me.autoSyncSuspended) {
  1022. me.sync();
  1023. }
  1024. },
  1025. <span id='Ext-data-Store-method-removeAt'> /**
  1026. </span> * Removes the model instance at the given index
  1027. * @param {Number} index The record index
  1028. */
  1029. removeAt: function(index) {
  1030. var record = this.getAt(index);
  1031. if (record) {
  1032. this.remove(record);
  1033. }
  1034. },
  1035. <span id='Ext-data-Store-method-load'> /**
  1036. </span> * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
  1037. * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
  1038. * instances into the Store and calling an optional callback if required. Example usage:
  1039. *
  1040. * store.load({
  1041. * scope: this,
  1042. * callback: function(records, operation, success) {
  1043. * // the {@link Ext.data.Operation operation} object
  1044. * // contains all of the details of the load operation
  1045. * console.log(records);
  1046. * }
  1047. * });
  1048. *
  1049. * If the callback scope does not need to be set, a function can simply be passed:
  1050. *
  1051. * store.load(function(records, operation, success) {
  1052. * console.log('loaded records');
  1053. * });
  1054. *
  1055. * @param {Object/Function} [options] config object, passed into the Ext.data.Operation object before loading.
  1056. * Additionally `addRecords: true` can be specified to add these records to the existing records, default is
  1057. * to remove the Store's existing records first.
  1058. */
  1059. load: function(options) {
  1060. var me = this;
  1061. options = options || {};
  1062. if (typeof options == 'function') {
  1063. options = {
  1064. callback: options
  1065. };
  1066. }
  1067. options.groupers = options.groupers || me.groupers.items;
  1068. options.page = options.page || me.currentPage;
  1069. options.start = (options.start !== undefined) ? options.start : (options.page - 1) * me.pageSize;
  1070. options.limit = options.limit || me.pageSize;
  1071. options.addRecords = options.addRecords || false;
  1072. if (me.buffered) {
  1073. return me.loadToPrefetch(options);
  1074. }
  1075. return me.callParent([options]);
  1076. },
  1077. reload: function(options) {
  1078. var me = this,
  1079. startIdx,
  1080. endIdx,
  1081. startPage,
  1082. endPage,
  1083. i,
  1084. waitForReload,
  1085. bufferZone,
  1086. records;
  1087. if (!options) {
  1088. options = {};
  1089. }
  1090. // If buffered, we have to clear the page cache and then
  1091. // cache the page range surrounding store's loaded range.
  1092. if (me.buffered) {
  1093. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  1094. delete me.totalCount;
  1095. waitForReload = function() {
  1096. if (me.rangeCached(startIdx, endIdx)) {
  1097. me.loading = false;
  1098. me.pageMap.un('pageAdded', waitForReload);
  1099. records = me.pageMap.getRange(startIdx, endIdx);
  1100. me.loadRecords(records, {
  1101. start: startIdx
  1102. });
  1103. me.fireEvent('load', me, records, true);
  1104. }
  1105. };
  1106. bufferZone = Math.ceil((me.leadingBufferZone + me.trailingBufferZone) / 2);
  1107. // Get our record index range in the dataset
  1108. startIdx = options.start || me.getAt(0).index;
  1109. endIdx = startIdx + (options.count || me.getCount()) - 1;
  1110. // Calculate a page range which encompasses the Store's loaded range plus both buffer zones
  1111. startPage = me.getPageFromRecordIndex(Math.max(startIdx - bufferZone, 0));
  1112. endPage = me.getPageFromRecordIndex(endIdx + bufferZone);
  1113. // Clear cache (with initial flag so that any listening PagingScroller does not reset to page 1).
  1114. me.pageMap.clear(true);
  1115. if (me.fireEvent('beforeload', me, options) !== false) {
  1116. me.loading = true;
  1117. // Recache the page range which encapsulates our visible records
  1118. for (i = startPage; i &lt;= endPage; i++) {
  1119. me.prefetchPage(i, options);
  1120. }
  1121. // Wait for the requested range to become available in the page map
  1122. // Load the range as soon as the whole range is available
  1123. me.pageMap.on('pageAdded', waitForReload);
  1124. }
  1125. } else {
  1126. return me.callParent(arguments);
  1127. }
  1128. },
  1129. <span id='Ext-data-Store-method-onProxyLoad'> /**
  1130. </span> * @private
  1131. * Called internally when a Proxy has completed a load request
  1132. */
  1133. onProxyLoad: function(operation) {
  1134. var me = this,
  1135. resultSet = operation.getResultSet(),
  1136. records = operation.getRecords(),
  1137. successful = operation.wasSuccessful();
  1138. if (resultSet) {
  1139. me.totalCount = resultSet.total;
  1140. }
  1141. if (successful) {
  1142. me.loadRecords(records, operation);
  1143. }
  1144. me.loading = false;
  1145. if (me.hasListeners.load) {
  1146. me.fireEvent('load', me, records, successful);
  1147. }
  1148. //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
  1149. //People are definitely using this so can't deprecate safely until 2.x
  1150. if (me.hasListeners.read) {
  1151. me.fireEvent('read', me, records, successful);
  1152. }
  1153. //this is a callback that would have been passed to the 'read' function and is optional
  1154. Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
  1155. },
  1156. //inherit docs
  1157. getNewRecords: function() {
  1158. return this.data.filterBy(this.filterNew).items;
  1159. },
  1160. //inherit docs
  1161. getUpdatedRecords: function() {
  1162. return this.data.filterBy(this.filterUpdated).items;
  1163. },
  1164. <span id='Ext-data-Store-method-filter'> /**
  1165. </span> * Filters the loaded set of records by a given set of filters.
  1166. *
  1167. * By default, the passed filter(s) are *added* to the collection of filters being used to filter this Store.
  1168. *
  1169. * To remove existing filters before applying a new set of filters use
  1170. *
  1171. * // Clear the filter collection without updating the UI
  1172. * store.clearFilter(true);
  1173. *
  1174. * see {@link #clearFilter}.
  1175. *
  1176. * Alternatively, if filters are configured with an `id`, then existing filters store may be *replaced* by new
  1177. * filters having the same `id`.
  1178. *
  1179. * Filtering by single field:
  1180. *
  1181. * store.filter(&quot;email&quot;, /\.com$/);
  1182. *
  1183. * Using multiple filters:
  1184. *
  1185. * store.filter([
  1186. * {property: &quot;email&quot;, value: /\.com$/},
  1187. * {filterFn: function(item) { return item.get(&quot;age&quot;) &gt; 10; }}
  1188. * ]);
  1189. *
  1190. * Using Ext.util.Filter instances instead of config objects
  1191. * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
  1192. *
  1193. * store.filter([
  1194. * Ext.create('Ext.util.Filter', {property: &quot;email&quot;, value: /\.com$/, root: 'data'}),
  1195. * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get(&quot;age&quot;) &gt; 10; }, root: 'data'})
  1196. * ]);
  1197. *
  1198. * When store is filtered, most of the methods for accessing store data will be working only
  1199. * within the set of filtered records. Two notable exceptions are {@link #queryBy} and
  1200. * {@link #getById}.
  1201. *
  1202. * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data.
  1203. * These are stored internally on the store, but the filtering itself is done on the Store's
  1204. * {@link Ext.util.MixedCollection MixedCollection}. See MixedCollection's
  1205. * {@link Ext.util.MixedCollection#filter filter} method for filter syntax.
  1206. * Alternatively, pass in a property string
  1207. * @param {String} [value] value to filter by (only if using a property string as the first argument)
  1208. */
  1209. filter: function(filters, value) {
  1210. if (Ext.isString(filters)) {
  1211. filters = {
  1212. property: filters,
  1213. value: value
  1214. };
  1215. }
  1216. var me = this,
  1217. decoded = me.decodeFilters(filters),
  1218. i = 0,
  1219. doLocalSort = me.sorters.length &amp;&amp; me.sortOnFilter &amp;&amp; !me.remoteSort,
  1220. length = decoded.length;
  1221. for (; i &lt; length; i++) {
  1222. me.filters.replace(decoded[i]);
  1223. }
  1224. if (me.remoteFilter) {
  1225. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  1226. delete me.totalCount;
  1227. // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
  1228. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  1229. // via the guaranteedrange event
  1230. if (me.buffered) {
  1231. me.pageMap.clear();
  1232. me.loadPage(1);
  1233. } else {
  1234. // Reset to the first page, the filter is likely to produce a smaller data set
  1235. me.currentPage = 1;
  1236. //the load function will pick up the new filters and request the filtered data from the proxy
  1237. me.load();
  1238. }
  1239. } else {
  1240. <span id='Ext-data-Store-property-snapshot'> /**
  1241. </span> * @property {Ext.util.MixedCollection} snapshot
  1242. * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
  1243. * records when a filter is removed or changed
  1244. */
  1245. if (me.filters.getCount()) {
  1246. me.snapshot = me.snapshot || me.data.clone();
  1247. me.data = me.data.filter(me.filters.items);
  1248. if (doLocalSort) {
  1249. me.sort();
  1250. } else {
  1251. // fire datachanged event if it hasn't already been fired by doSort
  1252. me.fireEvent('datachanged', me);
  1253. me.fireEvent('refresh', me);
  1254. }
  1255. }
  1256. }
  1257. },
  1258. <span id='Ext-data-Store-method-clearFilter'> /**
  1259. </span> * Reverts to a view of the Record cache with no filtering applied.
  1260. * @param {Boolean} suppressEvent If `true` the filter is cleared silently.
  1261. *
  1262. * For a locally filtered Store, this means that the filter collection is cleared without firing the
  1263. * {@link #datachanged} event.
  1264. *
  1265. * For a remotely filtered Store, this means that the filter collection is cleared, but the store
  1266. * is not reloaded from the server.
  1267. */
  1268. clearFilter: function(suppressEvent) {
  1269. var me = this;
  1270. me.filters.clear();
  1271. if (me.remoteFilter) {
  1272. // In a buffered Store, the meaing of suppressEvent is to simply clear the filters collection
  1273. if (suppressEvent) {
  1274. return;
  1275. }
  1276. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  1277. delete me.totalCount;
  1278. // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
  1279. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  1280. // via the guaranteedrange event
  1281. if (me.buffered) {
  1282. me.pageMap.clear();
  1283. me.loadPage(1);
  1284. } else {
  1285. // Reset to the first page, clearing a filter will destroy the context of the current dataset
  1286. me.currentPage = 1;
  1287. me.load();
  1288. }
  1289. } else if (me.isFiltered()) {
  1290. me.data = me.snapshot.clone();
  1291. delete me.snapshot;
  1292. if (suppressEvent !== true) {
  1293. me.fireEvent('datachanged', me);
  1294. me.fireEvent('refresh', me);
  1295. }
  1296. }
  1297. },
  1298. <span id='Ext-data-Store-method-isFiltered'> /**
  1299. </span> * Returns true if this store is currently filtered
  1300. * @return {Boolean}
  1301. */
  1302. isFiltered: function() {
  1303. var snapshot = this.snapshot;
  1304. return !! snapshot &amp;&amp; snapshot !== this.data;
  1305. },
  1306. <span id='Ext-data-Store-method-filterBy'> /**
  1307. </span> * Filters by a function. The specified function will be called for each
  1308. * Record in this Store. If the function returns `true` the Record is included,
  1309. * otherwise it is filtered out.
  1310. *
  1311. * When store is filtered, most of the methods for accessing store data will be working only
  1312. * within the set of filtered records. Two notable exceptions are {@link #queryBy} and
  1313. * {@link #getById}.
  1314. *
  1315. * @param {Function} fn The function to be called. It will be passed the following parameters:
  1316. * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
  1317. * using {@link Ext.data.Model#get}.
  1318. * @param {Object} fn.id The ID of the Record passed.
  1319. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  1320. * Defaults to this Store.
  1321. */
  1322. filterBy: function(fn, scope) {
  1323. var me = this;
  1324. me.snapshot = me.snapshot || me.data.clone();
  1325. me.data = me.queryBy(fn, scope || me);
  1326. me.fireEvent('datachanged', me);
  1327. me.fireEvent('refresh', me);
  1328. },
  1329. <span id='Ext-data-Store-method-queryBy'> /**
  1330. </span> * Query all the cached records in this Store using a filtering function. The specified function
  1331. * will be called with each record in this Store. If the function returns `true` the record is
  1332. * included in the results.
  1333. *
  1334. * This method is not effected by filtering, it will always look from all records inside the store
  1335. * no matter if filter is applied or not.
  1336. *
  1337. * @param {Function} fn The function to be called. It will be passed the following parameters:
  1338. * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
  1339. * using {@link Ext.data.Model#get}.
  1340. * @param {Object} fn.id The ID of the Record passed.
  1341. * @param {Object} [scope] The scope (this reference) in which the function is executed
  1342. * Defaults to this Store.
  1343. * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  1344. */
  1345. queryBy: function(fn, scope) {
  1346. var me = this,
  1347. data = me.snapshot || me.data;
  1348. return data.filterBy(fn, scope || me);
  1349. },
  1350. <span id='Ext-data-Store-method-query'> /**
  1351. </span> * Query all the cached records in this Store by name/value pair.
  1352. * The parameters will be used to generated a filter function that is given
  1353. * to the queryBy method.
  1354. *
  1355. * This method compliments queryBy by generating the query function automatically.
  1356. *
  1357. * @param {String} property The property to create the filter function for
  1358. * @param {String/RegExp} value The string/regex to compare the property value to
  1359. * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
  1360. * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
  1361. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  1362. * Ignored if anyMatch is true.
  1363. * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
  1364. */
  1365. query: function(property, value, anyMatch, caseSensitive, exactMatch) {
  1366. var me = this,
  1367. queryFn = me.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch),
  1368. results = me.queryBy(queryFn);
  1369. //create an empty mixed collection for use if queryBy returns null
  1370. if(!results) {
  1371. results = new Ext.util.MixedCollection();
  1372. }
  1373. return results;
  1374. },
  1375. <span id='Ext-data-Store-method-loadData'> /**
  1376. </span> * Loads an array of data straight into the Store.
  1377. *
  1378. * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
  1379. * processed by a reader). If your data requires processing to decode the data structure, use a
  1380. * {@link Ext.data.proxy.Memory MemoryProxy} instead.
  1381. *
  1382. * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
  1383. * into model instances first.
  1384. * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
  1385. * to remove the old ones first.
  1386. */
  1387. loadData: function(data, append) {
  1388. var me = this,
  1389. model = me.model,
  1390. length = data.length,
  1391. newData = [],
  1392. i,
  1393. record;
  1394. //make sure each data element is an Ext.data.Model instance
  1395. for (i = 0; i &lt; length; i++) {
  1396. record = data[i];
  1397. if (!(record.isModel)) {
  1398. record = Ext.ModelManager.create(record, model);
  1399. }
  1400. newData.push(record);
  1401. }
  1402. me.loadRecords(newData, append ? me.addRecordsOptions : undefined);
  1403. },
  1404. <span id='Ext-data-Store-method-loadRawData'> /**
  1405. </span> * Loads data via the bound Proxy's reader
  1406. *
  1407. * Use this method if you are attempting to load data and want to utilize the configured data reader.
  1408. *
  1409. * @param {Object[]} data The full JSON object you'd like to load into the Data store.
  1410. * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
  1411. * to remove the old ones first.
  1412. */
  1413. loadRawData : function(data, append) {
  1414. var me = this,
  1415. result = me.proxy.reader.read(data),
  1416. records = result.records;
  1417. if (result.success) {
  1418. me.totalCount = result.total;
  1419. me.loadRecords(records, append ? me.addRecordsOptions : undefined);
  1420. me.fireEvent('load', me, records, true);
  1421. }
  1422. },
  1423. <span id='Ext-data-Store-method-loadRecords'> /**
  1424. </span> * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
  1425. * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #method-add} instead
  1426. * @param {Ext.data.Model[]} records The array of records to load
  1427. * @param {Object} options
  1428. * @param {Boolean} [options.addRecords=false] Pass `true` to add these records to the existing records, `false` to remove the Store's existing records first.
  1429. * @param {Number} [options.start] Only used by buffered Stores. The index *within the overall dataset* of the first record in the array.
  1430. */
  1431. loadRecords: function(records, options) {
  1432. var me = this,
  1433. i = 0,
  1434. length = records.length,
  1435. start,
  1436. addRecords,
  1437. snapshot = me.snapshot;
  1438. if (options) {
  1439. start = options.start;
  1440. addRecords = options.addRecords;
  1441. }
  1442. if (!addRecords) {
  1443. delete me.snapshot;
  1444. me.clearData(true);
  1445. } else if (snapshot) {
  1446. snapshot.addAll(records);
  1447. }
  1448. me.data.addAll(records);
  1449. if (start !== undefined) {
  1450. for (; i &lt; length; i++) {
  1451. records[i].index = start + i;
  1452. records[i].join(me);
  1453. }
  1454. } else {
  1455. for (; i &lt; length; i++) {
  1456. records[i].join(me);
  1457. }
  1458. }
  1459. /*
  1460. * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
  1461. * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
  1462. * datachanged event is fired by the call to this.add, above.
  1463. */
  1464. me.suspendEvents();
  1465. if (me.filterOnLoad &amp;&amp; !me.remoteFilter) {
  1466. me.filter();
  1467. }
  1468. if (me.sortOnLoad &amp;&amp; !me.remoteSort) {
  1469. me.sort(undefined, undefined, undefined, true);
  1470. }
  1471. me.resumeEvents();
  1472. me.fireEvent('datachanged', me);
  1473. me.fireEvent('refresh', me);
  1474. },
  1475. // PAGING METHODS
  1476. <span id='Ext-data-Store-method-loadPage'> /**
  1477. </span> * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
  1478. * load operation, passing in calculated 'start' and 'limit' params
  1479. * @param {Number} page The number of the page to load
  1480. * @param {Object} options See options for {@link #method-load}
  1481. */
  1482. loadPage: function(page, options) {
  1483. var me = this;
  1484. me.currentPage = page;
  1485. // Copy options into a new object so as not to mutate passed in objects
  1486. options = Ext.apply({
  1487. page: page,
  1488. start: (page - 1) * me.pageSize,
  1489. limit: me.pageSize,
  1490. addRecords: !me.clearOnPageLoad
  1491. }, options);
  1492. if (me.buffered) {
  1493. return me.loadToPrefetch(options);
  1494. }
  1495. me.read(options);
  1496. },
  1497. <span id='Ext-data-Store-method-nextPage'> /**
  1498. </span> * Loads the next 'page' in the current data set
  1499. * @param {Object} options See options for {@link #method-load}
  1500. */
  1501. nextPage: function(options) {
  1502. this.loadPage(this.currentPage + 1, options);
  1503. },
  1504. <span id='Ext-data-Store-method-previousPage'> /**
  1505. </span> * Loads the previous 'page' in the current data set
  1506. * @param {Object} options See options for {@link #method-load}
  1507. */
  1508. previousPage: function(options) {
  1509. this.loadPage(this.currentPage - 1, options);
  1510. },
  1511. // private
  1512. clearData: function(isLoad) {
  1513. var me = this,
  1514. records = me.data.items,
  1515. i = records.length;
  1516. while (i--) {
  1517. records[i].unjoin(me);
  1518. }
  1519. me.data.clear();
  1520. if (isLoad !== true || me.clearRemovedOnLoad) {
  1521. me.removed.length = 0;
  1522. }
  1523. },
  1524. loadToPrefetch: function(options) {
  1525. var me = this,
  1526. i,
  1527. records,
  1528. // Get the requested record index range in the dataset
  1529. startIdx = options.start,
  1530. endIdx = options.start + options.limit - 1,
  1531. // The end index to load into the store's live record collection
  1532. loadEndIdx = options.start + (me.viewSize || options.limit) - 1,
  1533. // Calculate a page range which encompasses the requested range plus both buffer zones
  1534. startPage = me.getPageFromRecordIndex(Math.max(startIdx - me.trailingBufferZone, 0)),
  1535. endPage = me.getPageFromRecordIndex(endIdx + me.leadingBufferZone),
  1536. // Wait for the viewable range to be available
  1537. waitForRequestedRange = function() {
  1538. if (me.rangeCached(startIdx, loadEndIdx)) {
  1539. me.loading = false;
  1540. records = me.pageMap.getRange(startIdx, loadEndIdx);
  1541. me.pageMap.un('pageAdded', waitForRequestedRange);
  1542. // If there is a listener for guranteedrange (PagingScroller uses this), then go through that event
  1543. if (me.hasListeners.guaranteedrange) {
  1544. me.guaranteeRange(startIdx, loadEndIdx, options.callback, options.scope);
  1545. }
  1546. // Otherwise load the records directly
  1547. else {
  1548. me.loadRecords(records, {
  1549. start: startIdx
  1550. });
  1551. }
  1552. me.fireEvent('load', me, records, true);
  1553. if (options.groupChange) {
  1554. me.fireGroupChange();
  1555. }
  1556. }
  1557. };
  1558. if (me.fireEvent('beforeload', me, options) !== false) {
  1559. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  1560. delete me.totalCount;
  1561. me.loading = true;
  1562. // Wait for the requested range to become available in the page map
  1563. me.pageMap.on('pageAdded', waitForRequestedRange);
  1564. // Load the first page in the range, which will give us the initial total count.
  1565. // Once it is loaded, go ahead and prefetch any subsequent pages, if necessary.
  1566. // The prefetchPage has a check to prevent us loading more than the totalCount,
  1567. // so we don't want to blindly load up &lt;n&gt; pages where it isn't required.
  1568. me.on('prefetch', function(){
  1569. for (i = startPage + 1; i &lt;= endPage; ++i) {
  1570. me.prefetchPage(i, options);
  1571. }
  1572. }, null, {single: true});
  1573. me.prefetchPage(startPage, options);
  1574. }
  1575. },
  1576. // Buffering
  1577. <span id='Ext-data-Store-method-prefetch'> /**
  1578. </span> * Prefetches data into the store using its configured {@link #proxy}.
  1579. * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
  1580. * See {@link #method-load}
  1581. */
  1582. prefetch: function(options) {
  1583. var me = this,
  1584. pageSize = me.pageSize,
  1585. proxy,
  1586. operation;
  1587. // Check pageSize has not been tampered with. That would break page caching
  1588. if (pageSize) {
  1589. if (me.lastPageSize &amp;&amp; pageSize != me.lastPageSize) {
  1590. Ext.error.raise(&quot;pageSize cannot be dynamically altered&quot;);
  1591. }
  1592. if (!me.pageMap.pageSize) {
  1593. me.pageMap.pageSize = pageSize;
  1594. }
  1595. }
  1596. // Allow first prefetch call to imply the required page size.
  1597. else {
  1598. me.pageSize = me.pageMap.pageSize = pageSize = options.limit;
  1599. }
  1600. // So that we can check for tampering next time through
  1601. me.lastPageSize = pageSize;
  1602. // Always get whole pages.
  1603. if (!options.page) {
  1604. options.page = me.getPageFromRecordIndex(options.start);
  1605. options.start = (options.page - 1) * pageSize;
  1606. options.limit = Math.ceil(options.limit / pageSize) * pageSize;
  1607. }
  1608. // Currently not requesting this page, then request it...
  1609. if (!me.pageRequests[options.page]) {
  1610. // Copy options into a new object so as not to mutate passed in objects
  1611. options = Ext.apply({
  1612. action : 'read',
  1613. filters: me.filters.items,
  1614. sorters: me.sorters.items,
  1615. groupers: me.groupers.items,
  1616. // Generation # of the page map to which the requested records belong.
  1617. // If page map is cleared while this request is in flight, the generation will increment and the payload will be rejected
  1618. generation: me.pageMap.generation
  1619. }, options);
  1620. operation = new Ext.data.Operation(options);
  1621. if (me.fireEvent('beforeprefetch', me, operation) !== false) {
  1622. me.loading = true;
  1623. proxy = me.proxy;
  1624. me.pageRequests[options.page] = proxy.read(operation, me.onProxyPrefetch, me);
  1625. if (proxy.isSynchronous) {
  1626. delete me.pageRequests[options.page];
  1627. }
  1628. }
  1629. }
  1630. return me;
  1631. },
  1632. <span id='Ext-data-Store-method-cancelAllPrefetches'> /**
  1633. </span> * @private
  1634. * Cancels all pending prefetch requests.
  1635. *
  1636. * This is called when the page map is cleared.
  1637. *
  1638. * Any requests which still make it through will be for the previous page map generation
  1639. * (generation is incremented upon clear), and so will be rejected upon arrival.
  1640. */
  1641. cancelAllPrefetches: function() {
  1642. var me = this,
  1643. reqs = me.pageRequests,
  1644. req,
  1645. page;
  1646. // If any requests return, we no longer respond to them.
  1647. if (me.pageMap.events.pageadded) {
  1648. me.pageMap.events.pageadded.clearListeners();
  1649. }
  1650. // Cancel all outstanding requests
  1651. for (page in reqs) {
  1652. if (reqs.hasOwnProperty(page)) {
  1653. req = reqs[page];
  1654. delete reqs[page];
  1655. delete req.callback;
  1656. }
  1657. }
  1658. },
  1659. <span id='Ext-data-Store-method-prefetchPage'> /**
  1660. </span> * Prefetches a page of data.
  1661. * @param {Number} page The page to prefetch
  1662. * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
  1663. * See {@link #method-load}
  1664. */
  1665. prefetchPage: function(page, options) {
  1666. var me = this,
  1667. pageSize = me.pageSize || me.defaultPageSize,
  1668. start = (page - 1) * me.pageSize,
  1669. total = me.totalCount;
  1670. // No more data to prefetch.
  1671. if (total !== undefined &amp;&amp; me.getCount() === total) {
  1672. return;
  1673. }
  1674. // Copy options into a new object so as not to mutate passed in objects
  1675. me.prefetch(Ext.applyIf({
  1676. page : page,
  1677. start : start,
  1678. limit : pageSize
  1679. }, options));
  1680. },
  1681. <span id='Ext-data-Store-method-onProxyPrefetch'> /**
  1682. </span> * Called after the configured proxy completes a prefetch operation.
  1683. * @private
  1684. * @param {Ext.data.Operation} operation The operation that completed
  1685. */
  1686. onProxyPrefetch: function(operation) {
  1687. var me = this,
  1688. resultSet = operation.getResultSet(),
  1689. records = operation.getRecords(),
  1690. successful = operation.wasSuccessful(),
  1691. page = operation.page;
  1692. // Only cache the data if the operation was invoked for the current generation of the page map.
  1693. // If the generation has changed since the request was fired off, it will have been cancelled.
  1694. if (operation.generation === me.pageMap.generation) {
  1695. if (resultSet) {
  1696. me.totalCount = resultSet.total;
  1697. me.fireEvent('totalcountchange', me.totalCount);
  1698. }
  1699. // Remove the loaded page from the outstanding pages hash
  1700. if (page !== undefined) {
  1701. delete me.pageRequests[page];
  1702. }
  1703. // Add the page into the page map.
  1704. // pageAdded event may trigger the onGuaranteedRange
  1705. if (successful) {
  1706. me.cachePage(records, operation.page);
  1707. }
  1708. me.loading = false;
  1709. me.fireEvent('prefetch', me, records, successful, operation);
  1710. //this is a callback that would have been passed to the 'read' function and is optional
  1711. Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
  1712. }
  1713. },
  1714. <span id='Ext-data-Store-method-cachePage'> /**
  1715. </span> * Caches the records in the prefetch and stripes them with their server-side
  1716. * index.
  1717. * @private
  1718. * @param {Ext.data.Model[]} records The records to cache
  1719. * @param {Ext.data.Operation} The associated operation
  1720. */
  1721. cachePage: function(records, page) {
  1722. var me = this;
  1723. if (!Ext.isDefined(me.totalCount)) {
  1724. me.totalCount = records.length;
  1725. me.fireEvent('totalcountchange', me.totalCount);
  1726. }
  1727. // Add the fetched page into the pageCache
  1728. me.pageMap.addPage(page, records);
  1729. },
  1730. <span id='Ext-data-Store-method-rangeCached'> /**
  1731. </span> * Determines if the passed range is available in the page cache.
  1732. * @private
  1733. * @param {Number} start The start index
  1734. * @param {Number} end The end index in the range
  1735. */
  1736. rangeCached: function(start, end) {
  1737. return this.pageMap &amp;&amp; this.pageMap.hasRange(start, end);
  1738. },
  1739. <span id='Ext-data-Store-method-pageCached'> /**
  1740. </span> * Determines if the passed page is available in the page cache.
  1741. * @private
  1742. * @param {Number} page The page to find in the page cache.
  1743. */
  1744. pageCached: function(page) {
  1745. return this.pageMap &amp;&amp; this.pageMap.hasPage(page);
  1746. },
  1747. <span id='Ext-data-Store-method-rangeSatisfied'> /**
  1748. </span> * Determines if the passed range is available in the page cache.
  1749. * @private
  1750. * @deprecated 4.1.0 use {@link #rangeCached} instead
  1751. * @param {Number} start The start index
  1752. * @param {Number} end The end index in the range
  1753. */
  1754. rangeSatisfied: function(start, end) {
  1755. return this.rangeCached(start, end);
  1756. },
  1757. <span id='Ext-data-Store-method-getPageFromRecordIndex'> /**
  1758. </span> * Determines the page from a record index
  1759. * @param {Number} index The record index
  1760. * @return {Number} The page the record belongs to
  1761. */
  1762. getPageFromRecordIndex: function(index) {
  1763. return Math.floor(index / this.pageSize) + 1;
  1764. },
  1765. <span id='Ext-data-Store-method-onGuaranteedRange'> /**
  1766. </span> * Handles a guaranteed range being loaded
  1767. * @private
  1768. */
  1769. onGuaranteedRange: function(options) {
  1770. var me = this,
  1771. totalCount = me.getTotalCount(),
  1772. start = options.prefetchStart,
  1773. end = ((totalCount - 1) &lt; options.prefetchEnd) ? totalCount - 1 : options.prefetchEnd,
  1774. range;
  1775. end = Math.max(0, end);
  1776. //&lt;debug&gt;
  1777. if (start &gt; end) {
  1778. Ext.log({
  1779. level: 'warn',
  1780. msg: 'Start (' + start + ') was greater than end (' + end +
  1781. ') for the range of records requested (' + start + '-' +
  1782. options.prefetchEnd + ')' + (this.storeId ? ' from store &quot;' + this.storeId + '&quot;' : '')
  1783. });
  1784. }
  1785. //&lt;/debug&gt;
  1786. range = me.pageMap.getRange(start, end);
  1787. me.fireEvent('guaranteedrange', range, start, end);
  1788. if (options.cb) {
  1789. options.cb.call(options.scope || me, range, start, end);
  1790. }
  1791. },
  1792. <span id='Ext-data-Store-method-prefetchRange'> /**
  1793. </span> * Ensures that the specified range of rows is present in the cache.
  1794. *
  1795. * Converts the row range to a page range and then only load pages which are not already
  1796. * present in the page cache.
  1797. */
  1798. prefetchRange: function(start, end) {
  1799. var me = this,
  1800. startPage, endPage, page;
  1801. if (!me.rangeCached(start, end)) {
  1802. startPage = me.getPageFromRecordIndex(start);
  1803. endPage = me.getPageFromRecordIndex(end);
  1804. // Ensure that the page cache's max size is correct.
  1805. // Our purgePageCount is the number of additional pages *outside of the required range* which
  1806. // may be kept in the cache. A purgePageCount of zero means unlimited.
  1807. me.pageMap.maxSize = me.purgePageCount ? (endPage - startPage + 1) + me.purgePageCount : 0;
  1808. // We have the range, but ensure that we have a &quot;buffer&quot; of pages around it.
  1809. for (page = startPage; page &lt;= endPage; page++) {
  1810. if (!me.pageCached(page)) {
  1811. me.prefetchPage(page);
  1812. }
  1813. }
  1814. }
  1815. },
  1816. <span id='Ext-data-Store-method-guaranteeRange'> /**
  1817. </span> * Guarantee a specific range, this will load the store with a range (that
  1818. * must be the pageSize or smaller) and take care of any loading that may
  1819. * be necessary.
  1820. */
  1821. guaranteeRange: function(start, end, cb, scope) {
  1822. // Sanity check end point to be within dataset range
  1823. end = (end &gt; this.totalCount) ? this.totalCount - 1 : end;
  1824. var me = this,
  1825. lastRequestStart = me.lastRequestStart,
  1826. options = {
  1827. prefetchStart: start,
  1828. prefetchEnd: end,
  1829. cb: cb,
  1830. scope: scope
  1831. },
  1832. pageAddHandler;
  1833. me.lastRequestStart = start;
  1834. // If data request can be satisfied from the page cache
  1835. if (me.rangeCached(start, end)) {
  1836. // Attempt to keep the page cache primed with pages which encompass the live data range
  1837. if (start &lt; lastRequestStart) {
  1838. start = Math.max(start - me.leadingBufferZone, 0);
  1839. end = Math.min(end + me.trailingBufferZone, me.totalCount - 1);
  1840. } else {
  1841. start = Math.max(Math.min(start - me.trailingBufferZone, me.totalCount - me.pageSize), 0);
  1842. end = Math.min(end + me.leadingBufferZone, me.totalCount - 1);
  1843. }
  1844. // If the prefetch window calculated round the requested range is not already satisfied in the page cache,
  1845. // then arrange to prefetch it.
  1846. if (!me.rangeCached(start, end)) {
  1847. // We have the range, but ensure that we have a &quot;buffer&quot; of pages around it.
  1848. me.prefetchRange(start, end);
  1849. }
  1850. me.onGuaranteedRange(options);
  1851. }
  1852. // At least some of the requested range needs loading from server
  1853. else {
  1854. // Private event used by the LoadMask class to perform masking when the range required for rendering is not found in the cache
  1855. me.fireEvent('cachemiss', me, start, end);
  1856. // Calculate a prefetch range which is centered on the requested data
  1857. start = Math.min(Math.max(Math.floor(start - ((me.leadingBufferZone + me.trailingBufferZone) / 2)), 0), me.totalCount - me.pageSize);
  1858. end = Math.min(Math.max(Math.ceil (end + ((me.leadingBufferZone + me.trailingBufferZone) / 2)), 0), me.totalCount - 1);
  1859. // Add a pageAdded listener, and as soon as the requested range is loaded, fire the guaranteedrange event
  1860. pageAddHandler = function(page, records) {
  1861. if (me.rangeCached(options.prefetchStart, options.prefetchEnd)) {
  1862. // Private event used by the LoadMask class to unmask when the range required for rendering has been loaded into the cache
  1863. me.fireEvent('cachefilled', me, start, end);
  1864. me.pageMap.un('pageAdded', pageAddHandler);
  1865. me.onGuaranteedRange(options);
  1866. }
  1867. };
  1868. me.pageMap.on('pageAdded', pageAddHandler);
  1869. // Prioritize the request for the *exact range that the UI is asking for*.
  1870. // When a page request is in flight, it will not be requested again by checking the me.pageRequests hash,
  1871. // so the request after this will only request the *remaining* unrequested pages .
  1872. me.prefetchRange(options.prefetchStart, options.prefetchEnd);
  1873. // Load the pages that need loading.
  1874. me.prefetchRange(start, end);
  1875. }
  1876. },
  1877. // because prefetchData is stored by index
  1878. // this invalidates all of the prefetchedData
  1879. sort: function() {
  1880. var me = this,
  1881. prefetchData = me.pageMap;
  1882. if (me.buffered) {
  1883. if (me.remoteSort) {
  1884. prefetchData.clear();
  1885. me.callParent(arguments);
  1886. } else {
  1887. me.callParent(arguments);
  1888. }
  1889. } else {
  1890. me.callParent(arguments);
  1891. }
  1892. },
  1893. // overriden to provide striping of the indexes as sorting occurs.
  1894. // this cannot be done inside of sort because datachanged has already
  1895. // fired and will trigger a repaint of the bound view.
  1896. doSort: function(sorterFn) {
  1897. var me = this,
  1898. range,
  1899. ln,
  1900. i;
  1901. if (me.remoteSort) {
  1902. // For a buffered Store, we have to clear the prefetch cache since it is keyed by the index within the dataset.
  1903. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  1904. // via the guaranteedrange event
  1905. if (me.buffered) {
  1906. me.pageMap.clear();
  1907. me.loadPage(1);
  1908. } else {
  1909. //the load function will pick up the new sorters and request the sorted data from the proxy
  1910. me.load();
  1911. }
  1912. } else {
  1913. me.data.sortBy(sorterFn);
  1914. if (!me.buffered) {
  1915. range = me.getRange();
  1916. ln = range.length;
  1917. for (i = 0; i &lt; ln; i++) {
  1918. range[i].index = i;
  1919. }
  1920. }
  1921. me.fireEvent('datachanged', me);
  1922. me.fireEvent('refresh', me);
  1923. }
  1924. },
  1925. <span id='Ext-data-Store-method-find'> /**
  1926. </span> * Finds the index of the first matching Record in this store by a specific field value.
  1927. *
  1928. * When store is filtered, finds records only within filter.
  1929. *
  1930. * @param {String} fieldName The name of the Record field to test.
  1931. * @param {String/RegExp} value Either a string that the field value
  1932. * should begin with, or a RegExp to test against the field.
  1933. * @param {Number} [startIndex=0] The index to start searching at
  1934. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  1935. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison
  1936. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  1937. * @return {Number} The matched index or -1
  1938. */
  1939. find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
  1940. var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
  1941. return fn ? this.data.findIndexBy(fn, null, start) : -1;
  1942. },
  1943. <span id='Ext-data-Store-method-findRecord'> /**
  1944. </span> * Finds the first matching Record in this store by a specific field value.
  1945. *
  1946. * When store is filtered, finds records only within filter.
  1947. *
  1948. * @param {String} fieldName The name of the Record field to test.
  1949. * @param {String/RegExp} value Either a string that the field value
  1950. * should begin with, or a RegExp to test against the field.
  1951. * @param {Number} [startIndex=0] The index to start searching at
  1952. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  1953. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison
  1954. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  1955. * @return {Ext.data.Model} The matched record or null
  1956. */
  1957. findRecord: function() {
  1958. var me = this,
  1959. index = me.find.apply(me, arguments);
  1960. return index !== -1 ? me.getAt(index) : null;
  1961. },
  1962. <span id='Ext-data-Store-method-createFilterFn'> /**
  1963. </span> * @private
  1964. * Returns a filter function used to test a the given property's value. Defers most of the work to
  1965. * Ext.util.MixedCollection's createValueMatcher function.
  1966. *
  1967. * @param {String} property The property to create the filter function for
  1968. * @param {String/RegExp} value The string/regex to compare the property value to
  1969. * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
  1970. * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
  1971. * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
  1972. * Ignored if anyMatch is true.
  1973. */
  1974. createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
  1975. if (Ext.isEmpty(value)) {
  1976. return false;
  1977. }
  1978. value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
  1979. return function(r) {
  1980. return value.test(r.data[property]);
  1981. };
  1982. },
  1983. <span id='Ext-data-Store-method-findExact'> /**
  1984. </span> * Finds the index of the first matching Record in this store by a specific field value.
  1985. *
  1986. * When store is filtered, finds records only within filter.
  1987. *
  1988. * @param {String} fieldName The name of the Record field to test.
  1989. * @param {Object} value The value to match the field against.
  1990. * @param {Number} [startIndex=0] The index to start searching at
  1991. * @return {Number} The matched index or -1
  1992. */
  1993. findExact: function(property, value, start) {
  1994. return this.data.findIndexBy(function(rec) {
  1995. return rec.isEqual(rec.get(property), value);
  1996. },
  1997. this, start);
  1998. },
  1999. <span id='Ext-data-Store-method-findBy'> /**
  2000. </span> * Find the index of the first matching Record in this Store by a function.
  2001. * If the function returns `true` it is considered a match.
  2002. *
  2003. * When store is filtered, finds records only within filter.
  2004. *
  2005. * @param {Function} fn The function to be called. It will be passed the following parameters:
  2006. * @param {Ext.data.Model} fn.record The record to test for filtering. Access field values
  2007. * using {@link Ext.data.Model#get}.
  2008. * @param {Object} fn.id The ID of the Record passed.
  2009. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  2010. * Defaults to this Store.
  2011. * @param {Number} [startIndex=0] The index to start searching at
  2012. * @return {Number} The matched index or -1
  2013. */
  2014. findBy: function(fn, scope, start) {
  2015. return this.data.findIndexBy(fn, scope, start);
  2016. },
  2017. <span id='Ext-data-Store-method-collect'> /**
  2018. </span> * Collects unique values for a particular dataIndex from this store.
  2019. *
  2020. * @param {String} dataIndex The property to collect
  2021. * @param {Boolean} [allowNull] Pass true to allow null, undefined or empty string values
  2022. * @param {Boolean} [bypassFilter] Pass true to collect from all records, even ones which are filtered.
  2023. * @return {Object[]} An array of the unique values
  2024. */
  2025. collect: function(dataIndex, allowNull, bypassFilter) {
  2026. var me = this,
  2027. data = (bypassFilter === true &amp;&amp; me.snapshot) ? me.snapshot : me.data;
  2028. return data.collect(dataIndex, 'data', allowNull);
  2029. },
  2030. <span id='Ext-data-Store-method-getCount'> /**
  2031. </span> * Gets the number of records in store.
  2032. *
  2033. * If using paging, this may not be the total size of the dataset. If the data object
  2034. * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
  2035. * the dataset size. **Note**: see the Important note in {@link #method-load}.
  2036. *
  2037. * When store is filtered, it's the number of records matching the filter.
  2038. *
  2039. * @return {Number} The number of Records in the Store.
  2040. */
  2041. getCount: function() {
  2042. return this.data.length || 0;
  2043. },
  2044. <span id='Ext-data-Store-method-getTotalCount'> /**
  2045. </span> * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
  2046. * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
  2047. * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
  2048. * could be loaded into the Store if the Store contained all data
  2049. * @return {Number} The total number of Model instances available via the Proxy. 0 returned if
  2050. * no value has been set via the reader.
  2051. */
  2052. getTotalCount: function() {
  2053. return this.totalCount || 0;
  2054. },
  2055. <span id='Ext-data-Store-method-getAt'> /**
  2056. </span> * Get the Record at the specified index.
  2057. *
  2058. * The index is effected by filtering.
  2059. *
  2060. * @param {Number} index The index of the Record to find.
  2061. * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
  2062. */
  2063. getAt: function(index) {
  2064. return this.data.getAt(index);
  2065. },
  2066. <span id='Ext-data-Store-method-getRange'> /**
  2067. </span> * Returns a range of Records between specified indices.
  2068. *
  2069. * This method is effected by filtering.
  2070. *
  2071. * @param {Number} [startIndex=0] The starting index
  2072. * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
  2073. * @return {Ext.data.Model[]} An array of Records
  2074. */
  2075. getRange: function(start, end) {
  2076. return this.data.getRange(start, end);
  2077. },
  2078. <span id='Ext-data-Store-method-getById'> /**
  2079. </span> * Get the Record with the specified id.
  2080. *
  2081. * This method is not effected by filtering, lookup will be performed from all records
  2082. * inside the store, filtered or not.
  2083. *
  2084. * @param {Mixed} id The id of the Record to find.
  2085. * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
  2086. */
  2087. getById: function(id) {
  2088. return (this.snapshot || this.data).findBy(function(record) {
  2089. return record.getId() === id;
  2090. });
  2091. },
  2092. <span id='Ext-data-Store-method-indexOf'> /**
  2093. </span> * Get the index of the record within the store.
  2094. *
  2095. * When store is filtered, records outside of filter will not be found.
  2096. *
  2097. * @param {Ext.data.Model} record The Ext.data.Model object to find.
  2098. * @return {Number} The index of the passed Record. Returns -1 if not found.
  2099. */
  2100. indexOf: function(record) {
  2101. return this.data.indexOf(record);
  2102. },
  2103. <span id='Ext-data-Store-method-indexOfTotal'> /**
  2104. </span> * Get the index within the entire dataset. From 0 to the totalCount.
  2105. *
  2106. * Like #indexOf, this method is effected by filtering.
  2107. *
  2108. * @param {Ext.data.Model} record The Ext.data.Model object to find.
  2109. * @return {Number} The index of the passed Record. Returns -1 if not found.
  2110. */
  2111. indexOfTotal: function(record) {
  2112. var index = record.index;
  2113. if (index || index === 0) {
  2114. return index;
  2115. }
  2116. return this.indexOf(record);
  2117. },
  2118. <span id='Ext-data-Store-method-indexOfId'> /**
  2119. </span> * Get the index within the store of the Record with the passed id.
  2120. *
  2121. * Like #indexOf, this method is effected by filtering.
  2122. *
  2123. * @param {String} id The id of the Record to find.
  2124. * @return {Number} The index of the Record. Returns -1 if not found.
  2125. */
  2126. indexOfId: function(id) {
  2127. return this.indexOf(this.getById(id));
  2128. },
  2129. <span id='Ext-data-Store-method-removeAll'> /**
  2130. </span> * Removes all items from the store.
  2131. * @param {Boolean} silent Prevent the `clear` event from being fired.
  2132. */
  2133. removeAll: function(silent) {
  2134. var me = this;
  2135. me.clearData();
  2136. if (me.snapshot) {
  2137. me.snapshot.clear();
  2138. }
  2139. // Special handling to synch the PageMap only for removeAll
  2140. // TODO: handle other store/data modifications WRT buffered Stores.
  2141. if (me.pageMap) {
  2142. me.pageMap.clear();
  2143. }
  2144. if (silent !== true) {
  2145. me.fireEvent('clear', me);
  2146. }
  2147. },
  2148. /*
  2149. * Aggregation methods
  2150. */
  2151. <span id='Ext-data-Store-method-first'> /**
  2152. </span> * Convenience function for getting the first model instance in the store.
  2153. *
  2154. * When store is filtered, will return first item within the filter.
  2155. *
  2156. * @param {Boolean} [grouped] True to perform the operation for each group
  2157. * in the store. The value returned will be an object literal with the key being the group
  2158. * name and the first record being the value. The grouped parameter is only honored if
  2159. * the store has a groupField.
  2160. * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
  2161. */
  2162. first: function(grouped) {
  2163. var me = this;
  2164. if (grouped &amp;&amp; me.isGrouped()) {
  2165. return me.aggregate(function(records) {
  2166. return records.length ? records[0] : undefined;
  2167. }, me, true);
  2168. } else {
  2169. return me.data.first();
  2170. }
  2171. },
  2172. <span id='Ext-data-Store-method-last'> /**
  2173. </span> * Convenience function for getting the last model instance in the store.
  2174. *
  2175. * When store is filtered, will return last item within the filter.
  2176. *
  2177. * @param {Boolean} [grouped] True to perform the operation for each group
  2178. * in the store. The value returned will be an object literal with the key being the group
  2179. * name and the last record being the value. The grouped parameter is only honored if
  2180. * the store has a groupField.
  2181. * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
  2182. */
  2183. last: function(grouped) {
  2184. var me = this;
  2185. if (grouped &amp;&amp; me.isGrouped()) {
  2186. return me.aggregate(function(records) {
  2187. var len = records.length;
  2188. return len ? records[len - 1] : undefined;
  2189. }, me, true);
  2190. } else {
  2191. return me.data.last();
  2192. }
  2193. },
  2194. <span id='Ext-data-Store-method-sum'> /**
  2195. </span> * Sums the value of `property` for each {@link Ext.data.Model record} between `start`
  2196. * and `end` and returns the result.
  2197. *
  2198. * When store is filtered, only sums items within the filter.
  2199. *
  2200. * @param {String} field A field in each record
  2201. * @param {Boolean} [grouped] True to perform the operation for each group
  2202. * in the store. The value returned will be an object literal with the key being the group
  2203. * name and the sum for that group being the value. The grouped parameter is only honored if
  2204. * the store has a groupField.
  2205. * @return {Number} The sum
  2206. */
  2207. sum: function(field, grouped) {
  2208. var me = this;
  2209. if (grouped &amp;&amp; me.isGrouped()) {
  2210. return me.aggregate(me.getSum, me, true, [field]);
  2211. } else {
  2212. return me.getSum(me.data.items, field);
  2213. }
  2214. },
  2215. // @private, see sum
  2216. getSum: function(records, field) {
  2217. var total = 0,
  2218. i = 0,
  2219. len = records.length;
  2220. for (; i &lt; len; ++i) {
  2221. total += records[i].get(field);
  2222. }
  2223. return total;
  2224. },
  2225. <span id='Ext-data-Store-method-count'> /**
  2226. </span> * Gets the count of items in the store.
  2227. *
  2228. * When store is filtered, only items within the filter are counted.
  2229. *
  2230. * @param {Boolean} [grouped] True to perform the operation for each group
  2231. * in the store. The value returned will be an object literal with the key being the group
  2232. * name and the count for each group being the value. The grouped parameter is only honored if
  2233. * the store has a groupField.
  2234. * @return {Number} the count
  2235. */
  2236. count: function(grouped) {
  2237. var me = this;
  2238. if (grouped &amp;&amp; me.isGrouped()) {
  2239. return me.aggregate(function(records) {
  2240. return records.length;
  2241. }, me, true);
  2242. } else {
  2243. return me.getCount();
  2244. }
  2245. },
  2246. <span id='Ext-data-Store-method-min'> /**
  2247. </span> * Gets the minimum value in the store.
  2248. *
  2249. * When store is filtered, only items within the filter are aggregated.
  2250. *
  2251. * @param {String} field The field in each record
  2252. * @param {Boolean} [grouped] True to perform the operation for each group
  2253. * in the store. The value returned will be an object literal with the key being the group
  2254. * name and the minimum in the group being the value. The grouped parameter is only honored if
  2255. * the store has a groupField.
  2256. * @return {Object} The minimum value, if no items exist, undefined.
  2257. */
  2258. min: function(field, grouped) {
  2259. var me = this;
  2260. if (grouped &amp;&amp; me.isGrouped()) {
  2261. return me.aggregate(me.getMin, me, true, [field]);
  2262. } else {
  2263. return me.getMin(me.data.items, field);
  2264. }
  2265. },
  2266. // @private, see min
  2267. getMin: function(records, field) {
  2268. var i = 1,
  2269. len = records.length,
  2270. value, min;
  2271. if (len &gt; 0) {
  2272. min = records[0].get(field);
  2273. }
  2274. for (; i &lt; len; ++i) {
  2275. value = records[i].get(field);
  2276. if (value &lt; min) {
  2277. min = value;
  2278. }
  2279. }
  2280. return min;
  2281. },
  2282. <span id='Ext-data-Store-method-max'> /**
  2283. </span> * Gets the maximum value in the store.
  2284. *
  2285. * When store is filtered, only items within the filter are aggregated.
  2286. *
  2287. * @param {String} field The field in each record
  2288. * @param {Boolean} [grouped] True to perform the operation for each group
  2289. * in the store. The value returned will be an object literal with the key being the group
  2290. * name and the maximum in the group being the value. The grouped parameter is only honored if
  2291. * the store has a groupField.
  2292. * @return {Object} The maximum value, if no items exist, undefined.
  2293. */
  2294. max: function(field, grouped) {
  2295. var me = this;
  2296. if (grouped &amp;&amp; me.isGrouped()) {
  2297. return me.aggregate(me.getMax, me, true, [field]);
  2298. } else {
  2299. return me.getMax(me.data.items, field);
  2300. }
  2301. },
  2302. // @private, see max
  2303. getMax: function(records, field) {
  2304. var i = 1,
  2305. len = records.length,
  2306. value,
  2307. max;
  2308. if (len &gt; 0) {
  2309. max = records[0].get(field);
  2310. }
  2311. for (; i &lt; len; ++i) {
  2312. value = records[i].get(field);
  2313. if (value &gt; max) {
  2314. max = value;
  2315. }
  2316. }
  2317. return max;
  2318. },
  2319. <span id='Ext-data-Store-method-average'> /**
  2320. </span> * Gets the average value in the store.
  2321. *
  2322. * When store is filtered, only items within the filter are aggregated.
  2323. *
  2324. * @param {String} field The field in each record
  2325. * @param {Boolean} [grouped] True to perform the operation for each group
  2326. * in the store. The value returned will be an object literal with the key being the group
  2327. * name and the group average being the value. The grouped parameter is only honored if
  2328. * the store has a groupField.
  2329. * @return {Object} The average value, if no items exist, 0.
  2330. */
  2331. average: function(field, grouped) {
  2332. var me = this;
  2333. if (grouped &amp;&amp; me.isGrouped()) {
  2334. return me.aggregate(me.getAverage, me, true, [field]);
  2335. } else {
  2336. return me.getAverage(me.data.items, field);
  2337. }
  2338. },
  2339. // @private, see average
  2340. getAverage: function(records, field) {
  2341. var i = 0,
  2342. len = records.length,
  2343. sum = 0;
  2344. if (records.length &gt; 0) {
  2345. for (; i &lt; len; ++i) {
  2346. sum += records[i].get(field);
  2347. }
  2348. return sum / len;
  2349. }
  2350. return 0;
  2351. },
  2352. <span id='Ext-data-Store-method-aggregate'> /**
  2353. </span> * Runs the aggregate function for all the records in the store.
  2354. *
  2355. * When store is filtered, only items within the filter are aggregated.
  2356. *
  2357. * @param {Function} fn The function to execute. The function is called with a single parameter,
  2358. * an array of records for that group.
  2359. * @param {Object} [scope] The scope to execute the function in. Defaults to the store.
  2360. * @param {Boolean} [grouped] True to perform the operation for each group
  2361. * in the store. The value returned will be an object literal with the key being the group
  2362. * name and the group average being the value. The grouped parameter is only honored if
  2363. * the store has a groupField.
  2364. * @param {Array} [args] Any arguments to append to the function call
  2365. * @return {Object} An object literal with the group names and their appropriate values.
  2366. */
  2367. aggregate: function(fn, scope, grouped, args) {
  2368. args = args || [];
  2369. if (grouped &amp;&amp; this.isGrouped()) {
  2370. var groups = this.getGroups(),
  2371. i = 0,
  2372. len = groups.length,
  2373. out = {},
  2374. group;
  2375. for (; i &lt; len; ++i) {
  2376. group = groups[i];
  2377. out[group.name] = fn.apply(scope || this, [group.children].concat(args));
  2378. }
  2379. return out;
  2380. } else {
  2381. return fn.apply(scope || this, [this.data.items].concat(args));
  2382. }
  2383. },
  2384. <span id='Ext-data-Store-method-commitChanges'> /**
  2385. </span> * Commits all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
  2386. * subscribe to the Store's {@link #event-update update event}, and perform updating when the third parameter is
  2387. * Ext.data.Record.COMMIT.
  2388. */
  2389. commitChanges : function(){
  2390. var me = this,
  2391. recs = me.getModifiedRecords(),
  2392. len = recs.length,
  2393. i = 0;
  2394. for (; i &lt; len; i++){
  2395. recs[i].commit();
  2396. }
  2397. // Since removals are cached in a simple array we can simply reset it here.
  2398. // Adds and updates are managed in the data MixedCollection and should already be current.
  2399. me.removed.length = 0;
  2400. },
  2401. filterNewOnly: function(item){
  2402. return item.phantom === true;
  2403. },
  2404. // Ideally in the future this will use getModifiedRecords, where there will be a param
  2405. // to getNewRecords &amp; getUpdatedRecords to indicate whether to get only the valid
  2406. // records or grab all of them
  2407. getRejectRecords: function() {
  2408. // Return phantom records + updated records
  2409. return Ext.Array.push(this.data.filterBy(this.filterNewOnly).items, this.getUpdatedRecords());
  2410. },
  2411. <span id='Ext-data-Store-method-rejectChanges'> /**
  2412. </span> * {@link Ext.data.Model#reject Rejects} outstanding changes on all {@link #getModifiedRecords modified records}
  2413. * and re-insert any records that were removed locally. Any phantom records will be removed.
  2414. */
  2415. rejectChanges : function() {
  2416. var me = this,
  2417. recs = me.getRejectRecords(),
  2418. len = recs.length,
  2419. i = 0,
  2420. rec;
  2421. for (; i &lt; len; i++) {
  2422. rec = recs[i];
  2423. rec.reject();
  2424. if (rec.phantom) {
  2425. me.remove(rec);
  2426. }
  2427. }
  2428. // Restore removed records back to their original positions
  2429. recs = me.removed;
  2430. len = recs.length;
  2431. for (i = 0; i &lt; len; i++) {
  2432. rec = recs[i];
  2433. me.insert(rec.removedFrom || 0, rec);
  2434. rec.reject();
  2435. }
  2436. // Since removals are cached in a simple array we can simply reset it here.
  2437. // Adds and updates are managed in the data MixedCollection and should already be current.
  2438. me.removed.length = 0;
  2439. }
  2440. }, function() {
  2441. // A dummy empty store with a fieldless Model defined in it.
  2442. // Just for binding to Views which are instantiated with no Store defined.
  2443. // They will be able to run and render fine, and be bound to a generated Store later.
  2444. Ext.regStore('ext-empty-store', {fields: [], proxy: 'memory'});
  2445. <span id='Ext-data-Store-PageMap'> /**
  2446. </span> * @class Ext.data.Store.PageMap
  2447. * @extends Ext.util.LruCache
  2448. * Private class for use by only Store when configured `buffered: true`.
  2449. * @private
  2450. */
  2451. this.prototype.PageMap = new Ext.Class({
  2452. extend: 'Ext.util.LruCache',
  2453. // Maintain a generation counter, so that the Store can reject incoming pages destined for the previous generation
  2454. clear: function(initial) {
  2455. this.generation = (this.generation ||0) + 1;
  2456. this.callParent(arguments);
  2457. },
  2458. getPageFromRecordIndex: this.prototype.getPageFromRecordIndex,
  2459. addPage: function(page, records) {
  2460. this.add(page, records);
  2461. this.fireEvent('pageAdded', page, records);
  2462. },
  2463. getPage: function(page) {
  2464. return this.get(page);
  2465. },
  2466. hasRange: function(start, end) {
  2467. var page = this.getPageFromRecordIndex(start),
  2468. endPage = this.getPageFromRecordIndex(end);
  2469. for (; page &lt;= endPage; page++) {
  2470. if (!this.hasPage(page)) {
  2471. return false;
  2472. }
  2473. }
  2474. return true;
  2475. },
  2476. hasPage: function(page) {
  2477. // We must use this.get to trigger an access so that the page which is checked for presence is not eligible for pruning
  2478. return !!this.get(page);
  2479. },
  2480. getRange: function(start, end) {
  2481. if (!this.hasRange(start, end)) {
  2482. Ext.Error.raise('PageMap asked for range which it does not have');
  2483. }
  2484. var me = this,
  2485. startPage = me.getPageFromRecordIndex(start),
  2486. endPage = me.getPageFromRecordIndex(end),
  2487. dataStart = (startPage - 1) * me.pageSize,
  2488. dataEnd = (endPage * me.pageSize) - 1,
  2489. page = startPage,
  2490. result = [],
  2491. sliceBegin, sliceEnd, doSlice,
  2492. i = 0, len;
  2493. for (; page &lt;= endPage; page++) {
  2494. // First and last pages will need slicing to cut into the actual wanted records
  2495. if (page == startPage) {
  2496. sliceBegin = start - dataStart;
  2497. doSlice = true;
  2498. } else {
  2499. sliceBegin = 0;
  2500. doSlice = false;
  2501. }
  2502. if (page == endPage) {
  2503. sliceEnd = me.pageSize - (dataEnd - end);
  2504. doSlice = true;
  2505. }
  2506. // First and last pages will need slicing
  2507. if (doSlice) {
  2508. Ext.Array.push(result, Ext.Array.slice(me.getPage(page), sliceBegin, sliceEnd));
  2509. } else {
  2510. Ext.Array.push(result, me.getPage(page));
  2511. }
  2512. }
  2513. // Inject the dataset ordinal position into the record as the index
  2514. for (len = result.length; i &lt; len; i++) {
  2515. result[i].index = start++;
  2516. }
  2517. return result;
  2518. }
  2519. });
  2520. });
  2521. </pre>
  2522. </body>
  2523. </html>