[JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析
来源:互联网 发布:厄米特矩阵与对称矩阵 编辑:程序博客网 时间:2024/05/16 23:43
大家都知道EasyUI的Datagrid组件在加载大数据量时的优势并不是很明显,相对于其他一些框架,如果数据量达到几千,便会比较慢,特别是在IE下面。针对这种情况,我们首要做的是要相办法优化datagrid组件的各方面性能,不过任何事情都是可以变通解决的,virtualScrollView就是一种不错的解决方案。
virtualScrollView的准则就是尽量少画tr到table里,表格的高度是有限的,而用户的可见区域是很有限的,所以数据量很大的时候,是没有必要将所有数据数据都画到表格中,这样造成庞大的DOM,导致加载速度变慢。
源码分析
jQuery EasyUI的datagrid组件官方也扩展了一个virtualScrollView视图,我们来分析一下它的源码:
var scrollview = $.extend({}, $.fn.datagrid.defaults.view, { render: function(target, container, frozen){ var state = $.data(target, 'datagrid'); var opts = state.options; //这个地方要特别注意,并不是用的state.data.rows数据 //而是用的view.rows,而view.rows在onBeforeRender事件中被设置为undefined了 //onBeforeRender事件在scrollview中,即便是url方式有也只会被触发一次,所以在第一次rend时,是没有数据直接return了。 var rows = this.rows || []; if (!rows.length) { return; } var fields = $(target).datagrid('getColumnFields', frozen); //如果是rend frozen部分,但是有没有行号和frozenColumns的话,那就直接返回 if (frozen){ if (!(opts.rownumbers || (opts.frozenColumns && opts.frozenColumns.length))){ return; } } var index = this.index; var table = ['<table class="datagrid-btable" cellspacing="0" cellpadding="0" border="0"><tbody>']; for(var i=0; i<rows.length; i++) { var css = opts.rowStyler ? opts.rowStyler.call(target, index, rows[i]) : ''; var classValue = ''; var styleValue = ''; if (typeof css == 'string'){ styleValue = css; } else if (css){ classValue = css['class'] || ''; styleValue = css['style'] || ''; } var cls = 'class="datagrid-row ' + (index % 2 && opts.striped ? 'datagrid-row-alt ' : ' ') + classValue + '"'; var style = styleValue ? 'style="' + styleValue + '"' : ''; // get the class and style attributes for this row // var cls = (index % 2 && opts.striped) ? 'class="datagrid-row datagrid-row-alt"' : 'class="datagrid-row"'; // var styleValue = opts.rowStyler ? opts.rowStyler.call(target, index, rows[i]) : ''; // var style = styleValue ? 'style="' + styleValue + '"' : ''; var rowId = state.rowIdPrefix + '-' + (frozen?1:2) + '-' + index; table.push('<tr id="' + rowId + '" datagrid-row-index="' + index + '" ' + cls + ' ' + style + '>'); table.push(this.renderRow.call(this, target, fields, frozen, index, rows[i])); table.push('</tr>'); index++; } table.push('</tbody></table>'); $(container).html(table.join('')); }, /** * onBeforeRender事件,首先要明白两点: * 1-调用loadData方法加载数据数据时,loadData内部rend之前会触发这个事件 * 2-url方式时,获取到远程数据之后,也是使用loadData方法加载数据的,所以url方式也会触发onBeforeRender事件 * @param {DOM} target datagrid实例的宿主DOM对象 * @return {[type]} [description] */ onBeforeRender: function(target){ var state = $.data(target, 'datagrid'); var opts = state.options; var dc = state.dc; var view = this; // 删除onLoadSuccess事件,防止被触发,将备份到state.onLoadSuccess上 state.onLoadSuccess = opts.onLoadSuccess; opts.onLoadSuccess = function(){}; opts.finder.getRow = function(t, p){ var index = (typeof p == 'object') ? p.attr('datagrid-row-index') : p; var row = $.data(t, 'datagrid').data.rows[index]; if (!row){//什么情况会取不到呢? var v = $(t).datagrid('options').view; row = v.rows[index - v.index]; } return row; }; dc.body1.add(dc.body2).empty(); this.rows = undefined; // 把需要画的tr绑定到view.rows上了 this.r1 = this.r2 = []; // view.r1和viwe.r2分别存放对第一页tr和最后一页tr的引用 //这里不要想当然,只是绑定了事件,在第一次加载数据时,究竟是什么时候触发这个事件的呢 //这个问题得追溯到loadData方法了,每次loadData之后都会直接使用triggerHandler触发scroll的 dc.body2.unbind('.datagrid').bind('scroll.datagrid', function(e){ if (state.onLoadSuccess){ opts.onLoadSuccess = state.onLoadSuccess; // 恢复onLoadSuccess事件 state.onLoadSuccess = undefined; } if (view.scrollTimer){// 清除定时器 clearTimeout(view.scrollTimer); } // 延时五十毫秒执行 view.scrollTimer = setTimeout(function(){ scrolling.call(view); }, 50); }); function scrolling(){ if (dc.body2.is(':empty')){//dc.body2对应普通列数据,如果为空的话,说明没有数据。 //没有数据就尝试加载数据 reload.call(this); } else { var firstTr = opts.finder.getTr(target, this.index, 'body', 2); var lastTr = opts.finder.getTr(target, 0, 'last', 2); var headerHeight = dc.view2.children('div.datagrid-header').outerHeight(); var top = firstTr.position().top - headerHeight; var bottom = lastTr.position().top + lastTr.outerHeight() - headerHeight; if (top > dc.body2.height() || bottom < 0){ reload.call(this); } else if (top > 0){ var page = Math.floor(this.index/opts.pageSize); this.getRows.call(this, target, page, function(rows){ this.r2 = this.r1; this.r1 = rows; this.index = (page-1)*opts.pageSize; this.rows = this.r1.concat(this.r2); this.populate.call(this, target); }); } else if (bottom < dc.body2.height()){// 需要加载下一页的情况 var page = Math.floor(this.index/opts.pageSize)+2; if (this.r2.length){ page++; } this.getRows.call(this, target, page, function(rows){ if (!this.r2.length){ this.r2 = rows; } else { this.r1 = this.r2; this.r2 = rows; this.index += opts.pageSize; } this.rows = this.r1.concat(this.r2); this.populate.call(this, target); }); } } function reload(){ var top = $(dc.body2).scrollTop();//被卷起的高度 var index = Math.floor(top/25);//获取被卷起的行索引,如:卷起一行半37.5,index为1 var page = Math.floor(index/opts.pageSize) + 1;//获取页数,如果每页10条,卷起262.5,page为2 this.getRows.call(this, target, page, function(rows){ this.index = (page-1)*opts.pageSize;//view.index存放的是page页第一行的索引 this.rows = rows;//view.rows存放需要画的tr this.r1 = rows; this.r2 = []; this.populate.call(this, target); dc.body2.triggerHandler('scroll.datagrid'); }); } } }, getRows: function(target, page, callback){ var state = $.data(target, 'datagrid'); var opts = state.options; var index = (page-1)*opts.pageSize; var rows = state.data.rows.slice(index, index+opts.pageSize); if (rows.length){//这是一次性加载完所有数据的方式,可以直接从本地javascript数组中取出数据 callback.call(this, rows); } else {//懒加载方式 var param = $.extend({}, opts.queryParams, { page: page, rows: opts.pageSize }); if (opts.sortName){ $.extend(param, { sort: opts.sortName, order: opts.sortOrder }); } if (opts.onBeforeLoad.call(target, param) == false) return; $(target).datagrid('loading'); var result = opts.loader.call(target, param, function(data){ $(target).datagrid('loaded'); var data = opts.loadFilter.call(target, data); callback.call(opts.view, data.rows); // opts.onLoadSuccess.call(target, data); }, function(){ $(target).datagrid('loaded'); opts.onLoadError.apply(target, arguments); }); if (result == false){ $(target).datagrid('loaded'); } } }, populate: function(target){ var state = $.data(target, 'datagrid'); var opts = state.options; var dc = state.dc; var rowHeight = 25; if (this.rows.length){ opts.view.render.call(opts.view, target, dc.body2, false); opts.view.render.call(opts.view, target, dc.body1, true); // 看到了么,滚动条有那么大空间是怎么实现的了么?用的padding! dc.body1.add(dc.body2).children('table.datagrid-btable').css({ paddingTop: this.index*rowHeight, paddingBottom: state.data.total*rowHeight - this.rows.length*rowHeight - this.index*rowHeight }); opts.onLoadSuccess.call(target, { total: state.data.total, rows: this.rows }); } } });
分析结论
- virtualScrollView原理是通过设置div的上下padding来达到模拟极大数据量的效果的,我们只画比可视部分多一点的tr
- EasyUI的virtualScrollView支持两种方式:一是一次性请求完所有数据;二是每次都是ajax到pageSize条数据
- EasyUI的virtualScrollView画的tr数量是2*pageSize(初次加载例外,这时候只画1*pageSize的tr)
- EasyUI的virtualScrollView视图把行高强制视为25px的,如果你设置非25px的行高,这个视图就不能正常工作
- 因为只画2*pageSize个tr,所以我们dategrid的高度不能设置得超过2*25*pageSize个像素,超过的话就会造成可视区有留白
- 使用loadData方法加载数据的话loadData入参不需要total属性,只要是rows数组就可以了,total在loadData内部会自动计算
对于前面几点,大家自己看看源码里我写的注释,基础差的,看个似懂非懂就行了,基础好的,最好就彻底研究下。
存在的Bug
请求后台死循环
如果是url方式,第一次加载不到数据,就会不断地请求后台。看到146行了么,如果回调函数没有接受到rows,是不应该触发scorll事件的,因为scroll事件会请求后台数据,我已我们只要加上条件就行了:
if(rows && rows.length > 0){ dc.body2.triggerHandler('scroll.datagrid'); }
二次请求后台
url方式下,如果后台返回数据不足以填充表格高度的时候,会重复请求后台(注意这地方只重复请求一次,跟第一个bug不同)。这个问题的原因也很简单,其实这种情况,datagrid高度有点大,但是后台又只有很少几条数据造成的,表现在只有一批数据,而这批数据又不足以填满这个表格可视区高度。我们把122行对getRows方法的调用加个条件就可以了:
if (this.rows.length == opts.pageSize) { this.getRows.call(this, target, page, function(rows) { if (!this.r2.length) { this.r2 = rows; } else { this.r1 = this.r2; this.r2 = rows; this.index += opts.pageSize; } this.rows = this.r1.concat(this.r2); this.populate.call(this, target); }); }
this.rows是当前已经画的一批rows,如果rows的条数没有pageSize大,那就说明不需要再请求数据了。
virtualScrollView是一种很好的优化手段,以后会被应用的越来越广的,EasyUI的VirtualScrollView视图是否支持editor我并有去尝试,估计是不支持的,有兴趣的同学可以去研究研究。
------------------------------------------------------------------------------------------------------------------------------
原文地址:http://www.easyui.info/archives/1404.html
------------------------------------------------------------------------------------------------------------------------------
- [JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析
- [JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析
- jQuery EasyUI jquery.datagrid.js源代码
- [JS][easyui]jQuery EasyUI Datagrid性能优化专题
- JQuery easyui datagrid简单使用(一)
- jquery easyui datagrid简单使用学习
- jquery easyui DataGrid
- jQuery EasyUI DataGrid
- jquery easyUI datagrid
- Jquery easyui DataGrid分页
- JQuery EasyUI DataGrid
- jquery easyui DataGrid
- jquery easyui datagrid API
- jquery easyui datagrid 分页
- jquery easyui DataGrid
- jquery easyui datagrid基本功能
- jQuery EasyUI- DataGrid使用
- jquery easyui datagrid
- LeetCode-Maximum Subarray
- jQuery 多种高级页面属性和动画效果
- java读书笔记:ArrayList源码详解(基于jdk1.8)
- 博客搬家了!!!
- ubuntu 安装nvidia驱动
- [JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析
- ibatis 中isNull, isNotNull与isEmpty, isNotEmpty区别
- vim折叠(非常好的功能)
- getChildFragment()
- arch安装指南(2016-10)
- iOS 开发 第三方分享 微信朋友圈分享
- FZU 2150 Fire Game 多起点BFS
- java 文件字符流 (复制一个不为空的目录)
- 韩信点兵问题