ExtJS远程数据-本地分页

来源:互联网 发布:格式化js代码 编辑:程序博客网 时间:2024/05/21 09:54

背景

    一般情况下,分页展示是前端只负责展示,后台通过SQL语句实现分页查询。当总数据量在千条以下,适合一次性查询出符合条件的所有数据,让前端页面负责分页也是一种选择。

实例

    现通过ExtJS 4扩展类库Ext.ux.data.PagingStore来实现分页,建议使用前在GitHub获取最新版本。

    使用时非常简单,只需将Store的继承类改为“Ext.ux.data.PagingStore”,其他分页配置可参照之前的文章《ExtJS实现分页grid paging》。

  1. Ext.define('XXX', {
  2.  extend : 'Ext.ux.data.PagingStore'
  3.  ...
  4. })

    但是,针对不同的应用场景还有2个疑问:

  • 本地分页后,如何强制重新查询后台数据?

    根据PagingStore的实现来看,只有查询参数修改后才会再次调用后台进行查询。但是,如果我们修改了列表中某条数据后,需要按当前条件刷新列表。这时,我是在条件中添加一个时间戳来进行刷新的。

    1. store.getProxy().extraParams._TIME=new Date().getTime();


  • 分页按钮中的“刷新”如何去除?

        因为是本地分页,ExtJS自带的分页“刷新”按钮似乎成了摆设。可以在页面加载完成后,将其隐藏。在Controller层添加afterrender事件来实现。代码中的tab_id可通过开发人员工具在ExtJS生成的页面源码中看到,这里是抛砖引玉,希望大家写出更好的选择器。

  1. afterrender : function(){
  2.        Ext.get("tab_id").down(".x-tbar-loading").up(".x-btn").setVisible(false);
  3. }


    

    

附上Ext.ux.data.PagingStore.js的源码:

  1. /*
  2. * PagingStore for Ext 4 - v0.6
  3. * Based on Ext.ux.data.PagingStore for Ext JS 3, by Condor, found at
  4. * http://www.sencha.com/forum/showthread.php?71532-Ext.ux.data.PagingStore-v0.5
  5. * Stores are configured as normal, with whatever proxy you need for remote or local.  Set the
  6. * lastOptions when defining the store to set start, limit and current page.  Store should only
  7. * request new data if params or extraParams changes.  In Ext JS 4, start, limit and page are part of the
  8. * options but no longer part of params.
  9. * Example remote store:
  10. *     var myStore = Ext.create('Ext.ux.data.PagingStore', {
  11.             model: 'Artist',
  12.             pageSize: 3,
  13.             lastOptions: {start: 0, limit: 3, page: 1},
  14.             proxy: {
  15.               type: 'ajax',
  16.               url: 'url/goes/here',
  17.               reader: {
  18.                 type: 'json',
  19.                 root: 'rows'
  20.               }
  21.             }
  22.       });
  23. * Example local store:
  24. *    var myStore = Ext.create('Ext.ux.data.PagingStore', {
  25.            model: 'Artist',
  26.            pageSize: 3,
  27.            proxy: {
  28.              type: 'memory',
  29.              reader: {
  30.                type: 'array'
  31.              }
  32.            },
  33.            data: data
  34.      });
  35. * To force a reload, delete store.lastParams.
  36. */
  37. Ext.define('Ext.ux.data.PagingStore', {
  38. extend: 'Ext.data.Store',
  39. alias: 'store.pagingstore',
  40. destroyStore: function () {
  41. this.callParent(arguments);
  42. this.allData = null;
  43. },
  44. /**
  45. * Currently, only looking at start, limit, page and params properties of options.  Ignore everything
  46. * else.
  47. * @param {Ext.data.Operation} options
  48. * @return {boolean}
  49. */
  50. isPaging: function (options) {
  51. var me = this,
  52. start = options.start,
  53. limit = options.limit,
  54. page = options.page,
  55. currentParams;
  56. if ((typeof start != 'number') || (typeof limit != 'number')) {
  57. delete me.start;
  58. delete me.limit;
  59. delete me.page;
  60. me.lastParams = options.params;
  61. return false;
  62. }
  63. me.start = start;
  64. me.limit = limit;
  65. me.currentPage = page;
  66. var lastParams = this.lastParams;
  67. currentParams = Ext.apply({}, options.params, this.proxy ? this.proxy.extraParams : {});
  68. me.lastParams = currentParams;
  69. if (!this.proxy) {
  70. return true;
  71. }
  72. // No params from a previous load, must be the first load
  73. if (!lastParams) {
  74. return false;
  75. }
  76. //Iterate through all of the current parameters, if there are differences, then this is
  77. //not just a paging request, but instead a true load request
  78. for (var param in currentParams) {
  79. if (currentParams.hasOwnProperty(param) && (currentParams[param] !== lastParams[param])) {
  80. return false;
  81. }
  82. }
  83. //Do the same iteration, but this time walking through the lastParams
  84. for (param in lastParams) {
  85. if (lastParams.hasOwnProperty(param) && (currentParams[param] !== lastParams[param])) {
  86. return false;
  87. }
  88. }
  89. return true;
  90. },
  91. applyPaging: function () {
  92. var me = this,
  93. start = me.start,
  94. limit = me.limit,
  95. allData, data;
  96. if ((typeof start == 'number') && (typeof limit == 'number')) {
  97. allData = this.data;
  98. data = new Ext.util.MixedCollection(allData.allowFunctions, allData.getKey);
  99. data.addAll(allData.items.slice(start, start + limit));
  100. me.allData = allData;
  101. me.data = data;
  102. }
  103. },
  104. loadRecords: function (records, options) {
  105. var me = this,
  106. i = 0,
  107. length = records.length,
  108. start,
  109. addRecords,
  110. snapshot = me.snapshot,
  111. allData = me.allData;
  112. if (options) {
  113. start = options.start;
  114. addRecords = options.addRecords;
  115. }
  116. if (!addRecords) {
  117. delete me.allData;
  118. delete me.snapshot;
  119. me.clearData(true);
  120. } else if (allData) {
  121. allData.addAll(records);
  122. } else if (snapshot) {
  123. snapshot.addAll(records);
  124. }
  125. me.data.addAll(records);
  126. if (!me.allData) {
  127. me.applyPaging();
  128. }
  129. if (start !== undefined) {
  130. for (; i < length; i++) {
  131. records[i].index = start + i;
  132. records[i].join(me);
  133. }
  134. } else {
  135. for (; i < length; i++) {
  136. records[i].join(me);
  137. }
  138. }
  139. /*
  140. * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
  141. * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
  142. * datachanged event is fired by the call to this.add, above.
  143. */
  144. me.suspendEvents();
  145. if (me.filterOnLoad && !me.remoteFilter) {
  146. me.filter();
  147. }
  148. if (me.sortOnLoad && !me.remoteSort) {
  149. me.sort(undefined, undefined, undefined, true);
  150. }
  151. me.resumeEvents();
  152. me.fireEvent('datachanged', me);
  153. me.fireEvent('refresh', me);
  154. },
  155. loadData: function (data, append) {
  156. var me = this,
  157. model = me.model,
  158. length = data.length,
  159. newData = [],
  160. i,
  161. record;
  162. me.isPaging(Ext.apply({}, this.lastOptions ? this.lastOptions : {}));
  163. //make sure each data element is an Ext.data.Model instance
  164. for (i = 0; i < length; i++) {
  165. record = data[i];
  166. if (!(record.isModel)) {
  167. record = Ext.ModelManager.create(record, model);
  168. }
  169. newData.push(record);
  170. }
  171. me.loadRecords(newData, append ? me.addRecordsOptions : undefined);
  172. },
  173. loadRawData: function (data, append) {
  174. var me = this,
  175. result = me.proxy.reader.read(data),
  176. records = result.records;
  177. if (result.success) {
  178. me.totalCount = result.total;
  179. me.isPaging(Ext.apply({}, this.lastOptions ? this.lastOptions : {}));
  180. me.loadRecords(records, append ? me.addRecordsOptions : undefined);
  181. me.fireEvent('load', me, records, true);
  182. }
  183. },
  184. load: function (options) {
  185. var me = this,
  186. pagingOptions;
  187. options = options || {};
  188. if (typeof options == 'function') {
  189. options = {
  190. callback: options
  191. };
  192. }
  193. options.groupers = options.groupers || me.groupers.items;
  194. options.page = options.page || me.currentPage;
  195. options.start = (options.start !== undefined) ? options.start : (options.page - 1) * me.pageSize;
  196. options.limit = options.limit || me.pageSize;
  197. options.addRecords = options.addRecords || false;
  198. if (me.buffered) {
  199. return me.loadToPrefetch(options);
  200. }
  201. var operation;
  202. options = Ext.apply({
  203. action: 'read',
  204. filters: me.filters.items,
  205. sorters: me.getSorters()
  206. }, options);
  207. me.lastOptions = options;
  208. operation = new Ext.data.Operation(options);
  209. if (me.fireEvent('beforeload', me, operation) !== false) {
  210. me.loading = true;
  211. pagingOptions = Ext.apply({}, options);
  212. if (me.isPaging(pagingOptions)) {
  213. Ext.Function.defer(function () {
  214. if (me.allData) {
  215. me.data = me.allData;
  216. delete me.allData;
  217. }
  218. me.applyPaging();
  219. me.fireEvent("datachanged", me);
  220. me.fireEvent('refresh', me);
  221. var r = [].concat(me.data.items);
  222. me.loading = false;
  223. me.fireEvent("load", me, r, true);
  224. if (me.hasListeners.read) {
  225. me.fireEvent('read', me, r, true);
  226. }
  227. if (options.callback) {
  228. options.callback.call(options.scope || me, r, options, true);
  229. }
  230. }, 1, me);
  231. return me;
  232. }
  233. me.proxy.read(operation, me.onProxyLoad, me);
  234. }
  235. return me;
  236. },
  237. insert: function (index, records) {
  238. var me = this,
  239. sync = false,
  240. i,
  241. record,
  242. len;
  243. records = [].concat(records);
  244. for (i = 0, len = records.length; i < len; i++) {
  245. record = me.createModel(records[i]);
  246. record.set(me.modelDefaults);
  247. // reassign the model in the array in case it wasn't created yet
  248. records[i] = record;
  249. me.data.insert(index + i, record);
  250. record.join(me);
  251. sync = sync || record.phantom === true;
  252. }
  253. if (me.allData) {
  254. me.allData.addAll(records);
  255. }
  256. if (me.snapshot) {
  257. me.snapshot.addAll(records);
  258. }
  259. if (me.requireSort) {
  260. // suspend events so the usual data changed events don't get fired.
  261. me.suspendEvents();
  262. me.sort();
  263. me.resumeEvents();
  264. }
  265. me.fireEvent('add', me, records, index);
  266. me.fireEvent('datachanged', me);
  267. if (me.autoSync && sync && !me.autoSyncSuspended) {
  268. me.sync();
  269. }
  270. },
  271. doSort: function (sorterFn) {
  272. var me = this,
  273. range,
  274. ln,
  275. i;
  276. if (me.remoteSort) {
  277. // For a buffered Store, we have to clear the prefetch cache since it is keyed by the index within the dataset.
  278. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  279. // via the guaranteedrange event
  280. if (me.buffered) {
  281. me.pageMap.clear();
  282. me.loadPage(1);
  283. } else {
  284. //the load function will pick up the new sorters and request the sorted data from the proxy
  285. me.load();
  286. }
  287. } else {
  288. if (me.allData) {
  289. me.data = me.allData;
  290. delete me.allData;
  291. }
  292. me.data.sortBy(sorterFn);
  293. if (!me.buffered) {
  294. range = me.getRange();
  295. ln = range.length;
  296. for (i = 0; i < ln; i++) {
  297. range[i].index = i;
  298. }
  299. }
  300. me.applyPaging();
  301. me.fireEvent('datachanged', me);
  302. me.fireEvent('refresh', me);
  303. }
  304. },
  305. getTotalCount: function () {
  306. return this.allData ? this.allData.getCount() : this.totalCount || 0;
  307. },
  308. //inherit docs
  309. getNewRecords: function () {
  310. if (this.allData) {
  311. return this.allData.filterBy(this.filterNew).items;
  312. }
  313. return this.data.filterBy(this.filterNew).items;
  314. },
  315. //inherit docs
  316. getUpdatedRecords: function () {
  317. if (this.allData) {
  318. return this.allData.filterBy(this.filterUpdated).items;
  319. }
  320. return this.data.filterBy(this.filterUpdated).items;
  321. },
  322. remove: function (records, /* private */ isMove) {
  323. if (!Ext.isArray(records)) {
  324. records = [records];
  325. }
  326. /*
  327. * Pass the isMove parameter if we know we're going to be re-inserting this record
  328. */
  329. isMove = isMove === true;
  330. var me = this,
  331. sync = false,
  332. i = 0,
  333. length = records.length,
  334. isNotPhantom,
  335. index,
  336. record;
  337. for (; i < length; i++) {
  338. record = records[i];
  339. index = me.data.indexOf(record);
  340. if (me.allData) {
  341. me.allData.remove(record);
  342. }
  343. if (me.snapshot) {
  344. me.snapshot.remove(record);
  345. }
  346. if (index > -1) {
  347. isNotPhantom = record.phantom !== true;
  348. // don't push phantom records onto removed
  349. if (!isMove && isNotPhantom) {
  350. // Store the index the record was removed from so that rejectChanges can re-insert at the correct place.
  351. // The record's index property won't do, as that is the index in the overall dataset when Store is buffered.
  352. record.removedFrom = index;
  353. me.removed.push(record);
  354. }
  355. record.unjoin(me);
  356. me.data.remove(record);
  357. sync = sync || isNotPhantom;
  358. me.fireEvent('remove', me, record, index);
  359. }
  360. }
  361. me.fireEvent('datachanged', me);
  362. if (!isMove && me.autoSync && sync && !me.autoSyncSuspended) {
  363. me.sync();
  364. }
  365. },
  366. filter: function (filters, value) {
  367. if (Ext.isString(filters)) {
  368. filters = {
  369. property: filters,
  370. value: value
  371. };
  372. }
  373. var me = this,
  374. decoded = me.decodeFilters(filters),
  375. i = 0,
  376. doLocalSort = me.sorters.length && me.sortOnFilter && !me.remoteSort,
  377. length = decoded.length;
  378. for (; i < length; i++) {
  379. me.filters.replace(decoded[i]);
  380. }
  381. if (me.remoteFilter) {
  382. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  383. delete me.totalCount;
  384. // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
  385. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  386. // via the guaranteedrange event
  387. if (me.buffered) {
  388. me.pageMap.clear();
  389. me.loadPage(1);
  390. } else {
  391. // Reset to the first page, the filter is likely to produce a smaller data set
  392. me.currentPage = 1;
  393. //the load function will pick up the new filters and request the filtered data from the proxy
  394. me.load();
  395. }
  396. } else {
  397. /**
  398. * @property {Ext.util.MixedCollection} snapshot
  399. * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
  400. * records when a filter is removed or changed
  401. */
  402. if (me.filters.getCount()) {
  403. me.snapshot = me.snapshot || me.allData.clone() || me.data.clone();
  404. if (me.allData) {
  405. me.data = me.allData;
  406. delete me.allData;
  407. }
  408. me.data = me.data.filter(me.filters.items);
  409. me.applyPaging();
  410. if (doLocalSort) {
  411. me.sort();
  412. } else {
  413. // fire datachanged event if it hasn't already been fired by doSort
  414. me.fireEvent('datachanged', me);
  415. me.fireEvent('refresh', me);
  416. }
  417. }
  418. }
  419. },
  420. clearFilter: function (suppressEvent) {
  421. var me = this;
  422. me.filters.clear();
  423. if (me.remoteFilter) {
  424. // In a buffered Store, the meaing of suppressEvent is to simply clear the filters collection
  425. if (suppressEvent) {
  426. return;
  427. }
  428. // So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
  429. delete me.totalCount;
  430. // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
  431. // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
  432. // via the guaranteedrange event
  433. if (me.buffered) {
  434. me.pageMap.clear();
  435. me.loadPage(1);
  436. } else {
  437. // Reset to the first page, clearing a filter will destroy the context of the current dataset
  438. me.currentPage = 1;
  439. me.load();
  440. }
  441. } else if (me.isFiltered()) {
  442. me.data = me.snapshot.clone();
  443. delete me.allData;
  444. delete me.snapshot;
  445. me.applyPaging();
  446. if (suppressEvent !== true) {
  447. me.fireEvent('datachanged', me);
  448. me.fireEvent('refresh', me);
  449. }
  450. }
  451. },
  452. isFiltered: function () {
  453. var snapshot = this.snapshot;
  454. return !!snapshot && snapshot !== (this.allData || this.data);
  455. },
  456. filterBy: function (fn, scope) {
  457. var me = this;
  458. me.snapshot = me.snapshot || me.allData.clone() || me.data.clone();
  459. me.data = me.queryBy(fn, scope || me);
  460. me.applyPaging();
  461. me.fireEvent('datachanged', me);
  462. me.fireEvent('refresh', me);
  463. },
  464. queryBy: function (fn, scope) {
  465. var me = this,
  466. data = me.snapshot || me.allData || me.data;
  467. return data.filterBy(fn, scope || me);
  468. },
  469. collect: function (dataIndex, allowNull, bypassFilter) {
  470. var me = this,
  471. data = (bypassFilter === true && (me.snapshot || me.allData)) ? (me.snapshot || me.allData) : me.data;
  472. return data.collect(dataIndex, 'data', allowNull);
  473. },
  474. getById: function (id) {
  475. return (this.snapshot || this.allData || this.data).findBy(function (record) {
  476. return record.getId() === id;
  477. });
  478. },
  479. removeAll: function (silent) {
  480. var me = this;
  481. me.clearData();
  482. if (me.snapshot) {
  483. me.snapshot.clear();
  484. }
  485. if (me.allData) {
  486. me.allData.clear();
  487. }
  488. // Special handling to synch the PageMap only for removeAll
  489. // TODO: handle other store/data modifications WRT buffered Stores.
  490. if (me.pageMap) {
  491. me.pageMap.clear();
  492. }
  493. if (silent !== true) {
  494. me.fireEvent('clear', me);
  495. }
  496. }
  497. });

0 0
原创粉丝点击