extjs-rails 4.1.0.alpha1

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