[JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析



大家都知道EasyUI的Datagrid组件在加载大数据量时的优势并不是很明显,相对于其他一些框架,如果数据量达到几千,便会比较慢,特别是在IE下面。针对这种情况,我们首要做的是要相办法优化datagrid组件的各方面性能,不过任何事情都是可以变通解决的,virtualScrollView就是一种不错的解决方案。

virtualScrollView的准则就是尽量少画tr到table里,表格的高度是有限的,而用户的可见区域是很有限的,所以数据量很大的时候,是没有必要将所有数据数据都画到表格中,这样造成庞大的DOM,导致加载速度变慢。

源码分析

jQuery EasyUI的datagrid组件官方也扩展了一个virtualScrollView视图,我们来分析一下它的源码:

  1. var scrollview = $.extend({}, $.fn.datagrid.defaults.view, {
  2. render: function(target, container, frozen){
  3. var state = $.data(target, ‘datagrid‘);
  4. var opts = state.options;
  5. //这个地方要特别注意,并不是用的state.data.rows数据
  6. //而是用的view.rows,而view.rows在onBeforeRender事件中被设置为undefined了
  7. //onBeforeRender事件在scrollview中,即便是url方式有也只会被触发一次,所以在第一次rend时,是没有数据直接return了。
  8. var rows = this.rows || [];
  9. if (!rows.length) {
  10. return;
  11. }
  12. var fields = $(target).datagrid(‘getColumnFields‘, frozen);
  13. //如果是rend frozen部分,但是有没有行号和frozenColumns的话,那就直接返回
  14. if (frozen){
  15. if (!(opts.rownumbers || (opts.frozenColumns && opts.frozenColumns.length))){
  16. return;
  17. }
  18. }
  19. var index = this.index;
  20. var table = [‘<table class="datagrid-btable" cellspacing="0" cellpadding="0" border="0"><tbody>‘];
  21. for(var i=0; i<rows.length; i++) {
  22. var css = opts.rowStyler ? opts.rowStyler.call(target, index, rows[i]) : ‘‘;
  23. var classValue = ‘‘;
  24. var styleValue = ‘‘;
  25. if (typeof css == ‘string‘){
  26. styleValue = css;
  27. } else if (css){
  28. classValue = css[‘class‘] || ‘‘;
  29. styleValue = css[‘style‘] || ‘‘;
  30. }
  31. var cls = ‘class="datagrid-row ‘ + (index % 2 && opts.striped ? ‘datagrid-row-alt ‘ : ‘ ‘) + classValue + ‘"‘;
  32. var style = styleValue ? ‘style="‘ + styleValue + ‘"‘ : ‘‘;
  33. // get the class and style attributes for this row
  34. //          var cls = (index % 2 && opts.striped) ? ‘class="datagrid-row datagrid-row-alt"‘ : ‘class="datagrid-row"‘;
  35. //          var styleValue = opts.rowStyler ? opts.rowStyler.call(target, index, rows[i]) : ‘‘;
  36. //          var style = styleValue ? ‘style="‘ + styleValue + ‘"‘ : ‘‘;
  37. var rowId = state.rowIdPrefix + ‘-‘ + (frozen?1:2) + ‘-‘ + index;
  38. table.push(‘<tr id="‘ + rowId + ‘" datagrid-row-index="‘ + index + ‘" ‘ + cls + ‘ ‘ + style + ‘>‘);
  39. table.push(this.renderRow.call(this, target, fields, frozen, index, rows[i]));
  40. table.push(‘</tr>‘);
  41. index++;
  42. }
  43. table.push(‘</tbody></table>‘);
  44. $(container).html(table.join(‘‘));
  45. },
  46. /**
  47. * onBeforeRender事件,首先要明白两点:
  48. * 1-调用loadData方法加载数据数据时,loadData内部rend之前会触发这个事件
  49. * 2-url方式时,获取到远程数据之后,也是使用loadData方法加载数据的,所以url方式也会触发onBeforeRender事件
  50. * @param  {DOM} target datagrid实例的宿主DOM对象
  51. * @return {[type]}        [description]
  52. */
  53. onBeforeRender: function(target){
  54. var state = $.data(target, ‘datagrid‘);
  55. var opts = state.options;
  56. var dc = state.dc;
  57. var view = this;
  58. // 删除onLoadSuccess事件,防止被触发,将备份到state.onLoadSuccess上
  59. state.onLoadSuccess = opts.onLoadSuccess;
  60. opts.onLoadSuccess = function(){};
  61. opts.finder.getRow = function(t, p){
  62. var index = (typeof p == ‘object‘) ? p.attr(‘datagrid-row-index‘) : p;
  63. var row = $.data(t, ‘datagrid‘).data.rows[index];
  64. if (!row){//什么情况会取不到呢?
  65. var v = $(t).datagrid(‘options‘).view;
  66. row = v.rows[index - v.index];
  67. }
  68. return row;
  69. };
  70. dc.body1.add(dc.body2).empty();
  71. this.rows = undefined;  // 把需要画的tr绑定到view.rows上了
  72. this.r1 = this.r2 = []; // view.r1和viwe.r2分别存放对第一页tr和最后一页tr的引用
  73. //这里不要想当然,只是绑定了事件,在第一次加载数据时,究竟是什么时候触发这个事件的呢
  74. //这个问题得追溯到loadData方法了,每次loadData之后都会直接使用triggerHandler触发scroll的
  75. dc.body2.unbind(‘.datagrid‘).bind(‘scroll.datagrid‘, function(e){
  76. if (state.onLoadSuccess){
  77. opts.onLoadSuccess = state.onLoadSuccess;   // 恢复onLoadSuccess事件
  78. state.onLoadSuccess = undefined;
  79. }
  80. if (view.scrollTimer){// 清除定时器
  81. clearTimeout(view.scrollTimer);
  82. }
  83. // 延时五十毫秒执行
  84. view.scrollTimer = setTimeout(function(){
  85. scrolling.call(view);
  86. }, 50);
  87. });
  88. function scrolling(){
  89. if (dc.body2.is(‘:empty‘)){//dc.body2对应普通列数据,如果为空的话,说明没有数据。
  90. //没有数据就尝试加载数据
  91. reload.call(this);
  92. } else {
  93. var firstTr = opts.finder.getTr(target, this.index, ‘body‘, 2);
  94. var lastTr = opts.finder.getTr(target, 0, ‘last‘, 2);
  95. var headerHeight = dc.view2.children(‘div.datagrid-header‘).outerHeight();
  96. var top = firstTr.position().top - headerHeight;
  97. var bottom = lastTr.position().top + lastTr.outerHeight() - headerHeight;
  98. if (top > dc.body2.height() || bottom < 0){
  99. reload.call(this);
  100. } else if (top > 0){
  101. var page = Math.floor(this.index/opts.pageSize);
  102. this.getRows.call(this, target, page, function(rows){
  103. this.r2 = this.r1;
  104. this.r1 = rows;
  105. this.index = (page-1)*opts.pageSize;
  106. this.rows = this.r1.concat(this.r2);
  107. this.populate.call(this, target);
  108. });
  109. } else if (bottom < dc.body2.height()){// 需要加载下一页的情况
  110. var page = Math.floor(this.index/opts.pageSize)+2;
  111. if (this.r2.length){
  112. page++;
  113. }
  114. this.getRows.call(this, target, page, function(rows){
  115. if (!this.r2.length){
  116. this.r2 = rows;
  117. } else {
  118. this.r1 = this.r2;
  119. this.r2 = rows;
  120. this.index += opts.pageSize;
  121. }
  122. this.rows = this.r1.concat(this.r2);
  123. this.populate.call(this, target);
  124. });
  125. }
  126. }
  127. function reload(){
  128. var top = $(dc.body2).scrollTop();//被卷起的高度
  129. var index = Math.floor(top/25);//获取被卷起的行索引,如:卷起一行半37.5,index为1
  130. var page = Math.floor(index/opts.pageSize) + 1;//获取页数,如果每页10条,卷起262.5,page为2
  131. this.getRows.call(this, target, page, function(rows){
  132. this.index = (page-1)*opts.pageSize;//view.index存放的是page页第一行的索引
  133. this.rows = rows;//view.rows存放需要画的tr
  134. this.r1 = rows;
  135. this.r2 = [];
  136. this.populate.call(this, target);
  137. dc.body2.triggerHandler(‘scroll.datagrid‘);
  138. });
  139. }
  140. }
  141. },
  142. getRows: function(target, page, callback){
  143. var state = $.data(target, ‘datagrid‘);
  144. var opts = state.options;
  145. var index = (page-1)*opts.pageSize;
  146. var rows = state.data.rows.slice(index, index+opts.pageSize);
  147. if (rows.length){//这是一次性加载完所有数据的方式,可以直接从本地javascript数组中取出数据
  148. callback.call(this, rows);
  149. } else {//懒加载方式
  150. var param = $.extend({}, opts.queryParams, {
  151. page: page,
  152. rows: opts.pageSize
  153. });
  154. if (opts.sortName){
  155. $.extend(param, {
  156. sort: opts.sortName,
  157. order: opts.sortOrder
  158. });
  159. }
  160. if (opts.onBeforeLoad.call(target, param) == false) return;
  161. $(target).datagrid(‘loading‘);
  162. var result = opts.loader.call(target, param, function(data){
  163. $(target).datagrid(‘loaded‘);
  164. var data = opts.loadFilter.call(target, data);
  165. callback.call(opts.view, data.rows);
  166. //              opts.onLoadSuccess.call(target, data);
  167. }, function(){
  168. $(target).datagrid(‘loaded‘);
  169. opts.onLoadError.apply(target, arguments);
  170. });
  171. if (result == false){
  172. $(target).datagrid(‘loaded‘);
  173. }
  174. }
  175. },
  176. populate: function(target){
  177. var state = $.data(target, ‘datagrid‘);
  178. var opts = state.options;
  179. var dc = state.dc;
  180. var rowHeight = 25;
  181. if (this.rows.length){
  182. opts.view.render.call(opts.view, target, dc.body2, false);
  183. opts.view.render.call(opts.view, target, dc.body1, true);
  184. // 看到了么,滚动条有那么大空间是怎么实现的了么?用的padding!
  185. dc.body1.add(dc.body2).children(‘table.datagrid-btable‘).css({
  186. paddingTop: this.index*rowHeight,
  187. paddingBottom: state.data.total*rowHeight - this.rows.length*rowHeight - this.index*rowHeight
  188. });
  189. opts.onLoadSuccess.call(target, {
  190. total: state.data.total,
  191. rows: this.rows
  192. });
  193. }
  194. }
  195. });

分析结论

  • 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事件会请求后台数据,我已我们只要加上条件就行了:

  1. if(rows && rows.length > 0){
  2. dc.body2.triggerHandler(‘scroll.datagrid‘);
  3. }
二次请求后台

url方式下,如果后台返回数据不足以填充表格高度的时候,会重复请求后台(注意这地方只重复请求一次,跟第一个bug不同)。这个问题的原因也很简单,其实这种情况,datagrid高度有点大,但是后台又只有很少几条数据造成的,表现在只有一批数据,而这批数据又不足以填满这个表格可视区高度。我们把122行对getRows方法的调用加个条件就可以了:

  1. if (this.rows.length == opts.pageSize) {
  2. this.getRows.call(this, target, page, function(rows) {
  3. if (!this.r2.length) {
  4. this.r2 = rows;
  5. } else {
  6. this.r1 = this.r2;
  7. this.r2 = rows;
  8. this.index += opts.pageSize;
  9. }
  10. this.rows = this.r1.concat(this.r2);
  11. this.populate.call(this, target);
  12. });
  13. }

this.rows是当前已经画的一批rows,如果rows的条数没有pageSize大,那就说明不需要再请求数据了。

virtualScrollView是一种很好的优化手段,以后会被应用的越来越广的,EasyUI的VirtualScrollView视图是否支持editor我并有去尝试,估计是不支持的,有兴趣的同学可以去研究研究。

[JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析

时间: 2024-10-14 12:15:24

[JS][easyui]jQuery EasyUI Datagrid VirtualScrollView视图简单分析的相关文章

jQuery EasyUI Datagrid VirtualScrollView视图简单分析

大家都知道EasyUI的Datagrid组件在加载大数据量时的优势并不是很明显,相对于其他一些框架,如果数据量达到几千,便会比较慢,特别是在IE下面.针对这种情况,我们首要做的是要相办法优化datagrid组件的各方面性能,不过任何事情都是可以变通解决的,virtualScrollView就是一种不错的解决方案. virtualScrollView的准则就是尽量少画tr到table里,表格的高度是有限的,而用户的可见区域是很有限的,所以数据量很大的时候,是没有必要将所有数据数据都画到表格中,这样

出位的template.js 基于jquery的模板渲染插件,简单、好用

找了好几款基于jquery的模板渲染插件,无一感觉很难用(教程较少.绑定不统一),也可能我智商问题,比如jquery template.js .jtemplate.js. 然后在github上找到这一款,和我在公司之前用的差不多(apicloud云端开发app,致敬[百小僧]大神封装的HUI,简化了在公司很多工作), 这款模板渲染和HUI的很相似,也比较简单  基于jquery的模板渲染插件. 附上github https://github.com/yanhaijing/template.js

jQuery EasyUI之DataGrid使用示例

jQuery EasyUI是一个轻量级的Web前端开发框架,提供了很多的现成组件帮助程序员减轻前端代码开发量,之前有个项目中就用到了其中的DataGrid. jQuery EasyUI框架的官方主页:http://www.jeasyui.com/demo/main/index.php.可以下载完整开发包,里面有示例代码可以参考. 由于我使用的是ASP.NET webform技术,下面我就贴出主要的代码以供参考. 在页面中首先要引用相关的css以及js文件,这样才能使用该组件. css部分:  <

一点点关于JS的东西:EasyUI布局+下拉框之个人简单使用

根据项目需要,自己写的一个小小demo:实现的简单效果是这样子的,通过下拉框选择数据项,点击页面Button触发,根据下拉框值加载选显卡Tbs. html代码如下: 一个下拉框,一个button,一个空的tbs,两个隐藏内容div(可以动态合成). <select id="Model" class="easyui-combobox" name="Model" style="width:250px;">      

Jquery EasyUI datagrid后台数据表格生成及分页详解

由于项目原因,网站后台需要对用户信息进行各种操作,有时还需要进行批量操作,所以首先需要将用户信息展示出来,查了不少资料.发现Jquery EasyUI确实是一个不错的选择,功能强大,文档也比较全面,而且容易上手.今天就把自己在项目中用到的功能做了一个总结.生成数据表格如下所示: 接下来上代码 界面html及js代码 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"/>

jquery easyui DataGrid 数据表格 属性

中文帮助请点这里:中文属性详解 以下为未完结版 <!DOCTYPE html> <!-- To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates and open the template in the editor. --> <html> <head>

jQuery easyUI 使用 datagrid 表格

获取后台数据依旧是使用一般处理程序(ashx) ,分页上添加一个函数(pagerFilter(data)) 前端代码: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="dataGrid.aspx.cs" Inherits="Web.Management.dataGrid" %> <!DOCTYPE html> <html xmln

jquery easyui tree异步加载的简单用法

jsp页面: <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+reque

JQuery EasyUI后台UI框架使用连载

在正式了解 jQuery EasyUI 之前,我们先了解一下什么是 jQuery EasyUI.它的学习条件.市场上的同类产品.所支持的浏览器等.然后配置运行 jQuery EasyUI. 一.什么是JQuery EasyUI jQuery EasyUI 是一组基于 jQuery 的 UI 插件集合,而 jQuery EasyUI 的目标就是帮助Web 开发者更轻松的打造出功能丰富并且美观的 UI 界面.开发者不需要编写复杂的JavaScript,也不需要对 css 样式有深入的了解,开发者需要