背景

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

实例

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

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

Ext.define('XXX', {
extend : 'Ext.ux.data.PagingStore'
...
})

  

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

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

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

    

store.getProxy().extraParams._TIME=new Date().getTime();
  • 分页按钮中的“刷新”如何去除?

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

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

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

/*
* PagingStore for Ext 4 - v0.6
* Based on Ext.ux.data.PagingStore for Ext JS 3, by Condor, found at
* http://www.sencha.com/forum/showthread.php?71532-Ext.ux.data.PagingStore-v0.5
* Stores are configured as normal, with whatever proxy you need for remote or local. Set the
* lastOptions when defining the store to set start, limit and current page. Store should only
* request new data if params or extraParams changes. In Ext JS 4, start, limit and page are part of the
* options but no longer part of params.
* Example remote store:
* var myStore = Ext.create('Ext.ux.data.PagingStore', {
model: 'Artist',
pageSize: 3,
lastOptions: {start: 0, limit: 3, page: 1},
proxy: {
type: 'ajax',
url: 'url/goes/here',
reader: {
type: 'json',
root: 'rows'
}
}
});
* Example local store:
* var myStore = Ext.create('Ext.ux.data.PagingStore', {
model: 'Artist',
pageSize: 3,
proxy: {
type: 'memory',
reader: {
type: 'array'
}
},
data: data
});
* To force a reload, delete store.lastParams.
*/
Ext.define('Ext.ux.data.PagingStore', {
extend: 'Ext.data.Store',
alias: 'store.pagingstore',
destroyStore: function () {
this.callParent(arguments);
this.allData = null;
},
/**
* Currently, only looking at start, limit, page and params properties of options. Ignore everything
* else.
* @param {Ext.data.Operation} options
* @return {boolean}
*/
isPaging: function (options) {
var me = this,
start = options.start,
limit = options.limit,
page = options.page,
currentParams;
if ((typeof start != 'number') || (typeof limit != 'number')) {
delete me.start;
delete me.limit;
delete me.page;
me.lastParams = options.params;
return false;
}
me.start = start;
me.limit = limit;
me.currentPage = page;
var lastParams = this.lastParams;
currentParams = Ext.apply({}, options.params, this.proxy ? this.proxy.extraParams : {});
me.lastParams = currentParams;
if (!this.proxy) {
return true;
}
// No params from a previous load, must be the first load
if (!lastParams) {
return false;
}
//Iterate through all of the current parameters, if there are differences, then this is
//not just a paging request, but instead a true load request
for (var param in currentParams) {
if (currentParams.hasOwnProperty(param) && (currentParams[param] !== lastParams[param])) {
return false;
}
}
//Do the same iteration, but this time walking through the lastParams
for (param in lastParams) {
if (lastParams.hasOwnProperty(param) && (currentParams[param] !== lastParams[param])) {
return false;
}
}
return true;
},
applyPaging: function () {
var me = this,
start = me.start,
limit = me.limit,
allData, data;
if ((typeof start == 'number') && (typeof limit == 'number')) {
allData = this.data;
data = new Ext.util.MixedCollection(allData.allowFunctions, allData.getKey);
data.addAll(allData.items.slice(start, start + limit));
me.allData = allData;
me.data = data;
}
},
loadRecords: function (records, options) {
var me = this,
i = 0,
length = records.length,
start,
addRecords,
snapshot = me.snapshot,
allData = me.allData;
if (options) {
start = options.start;
addRecords = options.addRecords;
}
if (!addRecords) {
delete me.allData;
delete me.snapshot;
me.clearData(true);
} else if (allData) {
allData.addAll(records);
} else if (snapshot) {
snapshot.addAll(records);
}
me.data.addAll(records);
if (!me.allData) {
me.applyPaging();
}
if (start !== undefined) {
for (; i < length; i++) {
records[i].index = start + i;
records[i].join(me);
}
} else {
for (; i < length; i++) {
records[i].join(me);
}
}
/*
* this rather inelegant suspension and resumption of events is required because both the filter and sort functions
* fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
* datachanged event is fired by the call to this.add, above.
*/
me.suspendEvents();
if (me.filterOnLoad && !me.remoteFilter) {
me.filter();
}
if (me.sortOnLoad && !me.remoteSort) {
me.sort(undefined, undefined, undefined, true);
}
me.resumeEvents();
me.fireEvent('datachanged', me);
me.fireEvent('refresh', me);
},
loadData: function (data, append) {
var me = this,
model = me.model,
length = data.length,
newData = [],
i,
record;
me.isPaging(Ext.apply({}, this.lastOptions ? this.lastOptions : {}));
//make sure each data element is an Ext.data.Model instance
for (i = 0; i < length; i++) {
record = data[i];
if (!(record.isModel)) {
record = Ext.ModelManager.create(record, model);
}
newData.push(record);
}
me.loadRecords(newData, append ? me.addRecordsOptions : undefined);
},
loadRawData: function (data, append) {
var me = this,
result = me.proxy.reader.read(data),
records = result.records;
if (result.success) {
me.totalCount = result.total;
me.isPaging(Ext.apply({}, this.lastOptions ? this.lastOptions : {}));
me.loadRecords(records, append ? me.addRecordsOptions : undefined);
me.fireEvent('load', me, records, true);
}
},
load: function (options) {
var me = this,
pagingOptions;
options = options || {};
if (typeof options == 'function') {
options = {
callback: options
};
}
options.groupers = options.groupers || me.groupers.items;
options.page = options.page || me.currentPage;
options.start = (options.start !== undefined) ? options.start : (options.page - 1) * me.pageSize;
options.limit = options.limit || me.pageSize;
options.addRecords = options.addRecords || false;
if (me.buffered) {
return me.loadToPrefetch(options);
}
var operation;
options = Ext.apply({
action: 'read',
filters: me.filters.items,
sorters: me.getSorters()
}, options);
me.lastOptions = options;
operation = new Ext.data.Operation(options);
if (me.fireEvent('beforeload', me, operation) !== false) {
me.loading = true;
pagingOptions = Ext.apply({}, options);
if (me.isPaging(pagingOptions)) {
Ext.Function.defer(function () {
if (me.allData) {
me.data = me.allData;
delete me.allData;
}
me.applyPaging();
me.fireEvent("datachanged", me);
me.fireEvent('refresh', me);
var r = [].concat(me.data.items);
me.loading = false;
me.fireEvent("load", me, r, true);
if (me.hasListeners.read) {
me.fireEvent('read', me, r, true);
}
if (options.callback) {
options.callback.call(options.scope || me, r, options, true);
}
}, 1, me);
return me;
}
me.proxy.read(operation, me.onProxyLoad, me);
}
return me;
},
insert: function (index, records) {
var me = this,
sync = false,
i,
record,
len;
records = [].concat(records);
for (i = 0, len = records.length; i < len; i++) {
record = me.createModel(records[i]);
record.set(me.modelDefaults);
// reassign the model in the array in case it wasn't created yet
records[i] = record;
me.data.insert(index + i, record);
record.join(me);
sync = sync || record.phantom === true;
}
if (me.allData) {
me.allData.addAll(records);
}
if (me.snapshot) {
me.snapshot.addAll(records);
}
if (me.requireSort) {
// suspend events so the usual data changed events don't get fired.
me.suspendEvents();
me.sort();
me.resumeEvents();
}
me.fireEvent('add', me, records, index);
me.fireEvent('datachanged', me);
if (me.autoSync && sync && !me.autoSyncSuspended) {
me.sync();
}
},
doSort: function (sorterFn) {
var me = this,
range,
ln,
i;
if (me.remoteSort) {
// For a buffered Store, we have to clear the prefetch cache since it is keyed by the index within the dataset.
// Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
// via the guaranteedrange event
if (me.buffered) {
me.pageMap.clear();
me.loadPage(1);
} else {
//the load function will pick up the new sorters and request the sorted data from the proxy
me.load();
}
} else {
if (me.allData) {
me.data = me.allData;
delete me.allData;
}
me.data.sortBy(sorterFn);
if (!me.buffered) {
range = me.getRange();
ln = range.length;
for (i = 0; i < ln; i++) {
range[i].index = i;
}
}
me.applyPaging();
me.fireEvent('datachanged', me);
me.fireEvent('refresh', me);
}
},
getTotalCount: function () {
return this.allData ? this.allData.getCount() : this.totalCount || 0;
},
//inherit docs
getNewRecords: function () {
if (this.allData) {
return this.allData.filterBy(this.filterNew).items;
}
return this.data.filterBy(this.filterNew).items;
},
//inherit docs
getUpdatedRecords: function () {
if (this.allData) {
return this.allData.filterBy(this.filterUpdated).items;
}
return this.data.filterBy(this.filterUpdated).items;
},
remove: function (records, /* private */ isMove) {
if (!Ext.isArray(records)) {
records = [records];
}
/*
* Pass the isMove parameter if we know we're going to be re-inserting this record
*/
isMove = isMove === true;
var me = this,
sync = false,
i = 0,
length = records.length,
isNotPhantom,
index,
record;
for (; i < length; i++) {
record = records[i];
index = me.data.indexOf(record);
if (me.allData) {
me.allData.remove(record);
}
if (me.snapshot) {
me.snapshot.remove(record);
}
if (index > -1) {
isNotPhantom = record.phantom !== true;
// don't push phantom records onto removed
if (!isMove && isNotPhantom) {
// Store the index the record was removed from so that rejectChanges can re-insert at the correct place.
// The record's index property won't do, as that is the index in the overall dataset when Store is buffered.
record.removedFrom = index;
me.removed.push(record);
}
record.unjoin(me);
me.data.remove(record);
sync = sync || isNotPhantom;
me.fireEvent('remove', me, record, index);
}
}
me.fireEvent('datachanged', me);
if (!isMove && me.autoSync && sync && !me.autoSyncSuspended) {
me.sync();
}
},
filter: function (filters, value) {
if (Ext.isString(filters)) {
filters = {
property: filters,
value: value
};
}
var me = this,
decoded = me.decodeFilters(filters),
i = 0,
doLocalSort = me.sorters.length && me.sortOnFilter && !me.remoteSort,
length = decoded.length;
for (; i < length; i++) {
me.filters.replace(decoded[i]);
}
if (me.remoteFilter) {
// So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
delete me.totalCount;
// For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
// Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
// via the guaranteedrange event
if (me.buffered) {
me.pageMap.clear();
me.loadPage(1);
} else {
// Reset to the first page, the filter is likely to produce a smaller data set
me.currentPage = 1;
//the load function will pick up the new filters and request the filtered data from the proxy
me.load();
}
} else {
/**
* @property {Ext.util.MixedCollection} snapshot
* A pristine (unfiltered) collection of the records in this store. This is used to reinstate
* records when a filter is removed or changed
*/
if (me.filters.getCount()) {
me.snapshot = me.snapshot || me.allData.clone() || me.data.clone();
if (me.allData) {
me.data = me.allData;
delete me.allData;
}
me.data = me.data.filter(me.filters.items);
me.applyPaging();
if (doLocalSort) {
me.sort();
} else {
// fire datachanged event if it hasn't already been fired by doSort
me.fireEvent('datachanged', me);
me.fireEvent('refresh', me);
}
}
}
},
clearFilter: function (suppressEvent) {
var me = this;
me.filters.clear();
if (me.remoteFilter) {
// In a buffered Store, the meaing of suppressEvent is to simply clear the filters collection
if (suppressEvent) {
return;
}
// So that prefetchPage does not consider the store to be fully loaded if the local count is equal to the total count
delete me.totalCount;
// For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
// Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
// via the guaranteedrange event
if (me.buffered) {
me.pageMap.clear();
me.loadPage(1);
} else {
// Reset to the first page, clearing a filter will destroy the context of the current dataset
me.currentPage = 1;
me.load();
}
} else if (me.isFiltered()) {
me.data = me.snapshot.clone();
delete me.allData;
delete me.snapshot;
me.applyPaging();
if (suppressEvent !== true) {
me.fireEvent('datachanged', me);
me.fireEvent('refresh', me);
}
}
},
isFiltered: function () {
var snapshot = this.snapshot;
return !!snapshot && snapshot !== (this.allData || this.data);
},
filterBy: function (fn, scope) {
var me = this;
me.snapshot = me.snapshot || me.allData.clone() || me.data.clone();
me.data = me.queryBy(fn, scope || me);
me.applyPaging();
me.fireEvent('datachanged', me);
me.fireEvent('refresh', me);
},
queryBy: function (fn, scope) {
var me = this,
data = me.snapshot || me.allData || me.data;
return data.filterBy(fn, scope || me);
},
collect: function (dataIndex, allowNull, bypassFilter) {
var me = this,
data = (bypassFilter === true && (me.snapshot || me.allData)) ? (me.snapshot || me.allData) : me.data;
return data.collect(dataIndex, 'data', allowNull);
},
getById: function (id) {
return (this.snapshot || this.allData || this.data).findBy(function (record) {
return record.getId() === id;
});
},
removeAll: function (silent) {
var me = this;
me.clearData();
if (me.snapshot) {
me.snapshot.clear();
}
if (me.allData) {
me.allData.clear();
}
// Special handling to synch the PageMap only for removeAll
// TODO: handle other store/data modifications WRT buffered Stores.
if (me.pageMap) {
me.pageMap.clear();
}
if (silent !== true) {
me.fireEvent('clear', me);
}
}
});
  

  

ExtJS远程数据-本地分页的更多相关文章

  1. ExtJS ComboBox同时加载远程和本地数据

    ExtJS ComboBox同时加载远程和本地数据 原文:http://gblog.hbcf.net/index.php/archives/233 ComboBox比较特殊需求,将远程数据和本地数据同 ...

  2. rsync+inotify实现远程数据备份

    一.rsync的基本介绍 1.  什么是rsync Rsync是一款开源的.快速的.多功能的.可以实现增量的本地货远程数据镜像同步备份的优秀工具,Rsync使用与unix,linux,windows等 ...

  3. jQuery EasyUI datagrid实现本地分页的方法

    http://www.codeweblog.com/jquery-easyui-datagrid%e5%ae%9e%e7%8e%b0%e6%9c%ac%e5%9c%b0%e5%88%86%e9%a1% ...

  4. Linux 远程和本地的一些解决方案

     有的小伙伴想Linux 远程登录 两台机器同时root登录,其实可以同时多个用户的. Linux是多用户的多任务系统,可以同时多个用户登录到系统,也可以一个用户通过不同终端登录到一个系统执行不同的操 ...

  5. [置顶] Xamarin android 调用Web Api(ListView使用远程数据)

    xamarin android如何调用sqlserver 数据库呢(或者其他的),很多新手都会有这个疑问.xamarin android调用远程数据主要有两种方式: 在Android中保存数据或调用数 ...

  6. Linux 远程和本地的一些解决方式

     有的小伙伴想Linux 远程登录 两台机器同一时候root登录.事实上能够同一时候多个用户的. Linux是多用户的多任务系统,能够同一时候多个用户登录到系统,也能够一个用户通过不同终端登录到一个系 ...

  7. 使用PL/SQL Developer连接远程数据

    本机不安装Oracle客户端,使用PL/SQL Developer连接远程数据 1.先到Oracle网站下载Instant Client : http://www.oracle.com/technet ...

  8. Oracle远程数据建物化视图(materialized)创建简单记录,以及DBLINK的创建

    目的:实现远程数据库访问及其相应表的定时同步 一.远程数据库dblink的创建 select * from dba_db_links; select * from user_sys_privs;--查 ...

  9. Linux系统备份还原工具4(rsync/远程数据同步工具)

    rsync即是能备份系统也是数据同步的工具. 在Jenkins上可以使用rsync结合SSH的免密登录做数据同步和分发.这样一来可以达到部署全命令化,不需要依赖任何插件去实现. 命令参考:http:/ ...

随机推荐

  1. Dynamic CRM 2013学习笔记(四十六)简单审批流的实现

    前面介绍过自定义审批流: Dynamic CRM 2013学习笔记(十九)自定义审批流1 - 效果演示 Dynamic CRM 2013学习笔记(二十一)自定义审批流2 - 配置按钮 Dynamic ...

  2. 自己动手写UI库——引入ExtJs(布局)

    第一: 来看一下最终的效果 第二: 来看一下使用方法: 第三: Component类代码如下所示: public class Component     {                   pub ...

  3. [C++] socket - 6 [API互斥事件对象实现线程同步]

    /*API互斥事件对象实现线程同步*/ #include<windows.h> #include<stdio.h> DWORD WINAPI myfun1(LPVOID lpP ...

  4. 由Java中toString()方法引发的无意识的递归想到的

    先看一段很简单的java代码: toString()/** * @author jeffwong */ public class InfiniteRecursion { public String t ...

  5. Redis学习笔记一:基本安装和配置

    1.安装 wget http://download.redis.io/releases/redis-3.2.3.tar.gz编译安装: tar xf redis-3.2.3.tar.gz cd red ...

  6. Atitit.实现继承的原理and方法java javascript .net c# php ...

    Atitit.实现继承的原理and方法java javascript .net c# php ... 1. 实现继承的问题 1 2. 如何拷贝基类方法?采用prototype原型方式,通过冒充对象 1 ...

  7. Git 操作 学习资源 网址

    用git 有一段时间了,有点自己的小心得.个人觉得相对SVN来讲他更灵活,更合理. 陆陆续续的收集了一些学习资源: 1.理解Xcode 中的Git 版本控制 http://www.open-open. ...

  8. oracle--创建表空间、用户名、密码

    原文链接:http://blog.sina.com.cn/s/blog_4ce992f40101cspr.html

  9. PHP一个典型的闭包实例

    <?php // 一个基本的购物车,包括一些已经添加的商品和每种商品的数量. // 其中有一个方法用来计算购物车中所有商品的总价格,该方法使 // 用了一个 closure 作为回调函数. cl ...

  10. vba中MsgBox的参数及用法

    1.作用    在消息框中显示信息,并等待用户单击按钮,可返回单击的按钮值(比如“确定”或者“取消”).通常用作显示变量值的一种方式.2.语法       MsgBox(Prompt[,Buttons ...