[JS] [easyui] Simple Analysis of jQuery EasyUI Datagrid VirtualScrollView

Source: Internet
Author: User

[JS] [easyui] Simple Analysis of jQuery EasyUI Datagrid VirtualScrollView
Zookeeper

We all know that the advantages of the EasyUI Datagrid component in loading large data volumes are not very obvious. Compared with other frameworks, if the data volume reaches several thousand, it will be slow, especially under IE. To solve this problem, we first need to optimize the performance of the datagrid component. However, everything can be changed. virtualScrollView is a good solution.

The principle of virtualScrollView is to minimize the number of records written to the table. The table height is limited, while the visible area of the user is very limited. Therefore, when the data volume is large, there is no need to draw all the data into a table, resulting in a huge DOM, resulting in slow loading.

Source code analysis

JQuery EasyUI's datagrid component also officially expands a virtualScrollView. Let's analyze its source code:

  1. Var scrollview = $. extend ({}, $. fn. datagrid. defaults. view, {render: function (target, container, frozen ){
  2. Var state = $. data (target, 'datagrid '); var opts = state. options;
  3. // Note that the state. data. rows data is not used. // view. rows is used, and view. rows is set to undefined in the onBeforeRender event.
  4. // The onBeforeRender event is in scrollview. Even the url method is triggered only once. Therefore, when the first rend is used, no data is returned directly. Var rows = this. rows | [];
  5. If (! Rows. length) {return;
  6. } Var fields = $ (target). datagrid ('getcolumnfields ', frozen );
  7. // If it is the rend frozen part, but there is no row number or frozenColumns, then the system returns the result directly.
  8. If (frozen) {if (! (Opts. rownumbers | (opts. frozenColumns & opts. frozenColumns. length ))){
  9. Return ;}
  10. }
  11. Var index = this. index; var table = ['
  12. For (var I = 0; I Var classValue = ''; var styleValue = '';
  13. Table. push (this. renderRow. call (this, target, fields, frozen, index, rows [I]); table. push ('
  14. Index ++ ;}
  15. If (typeof css = 'string') {styleValue = css;
  16. } Else if (css) {classValue = css ['class'] | '';
  17. StyleValue = css ['style'] | '';}
  18. Var cls = 'class = "datagrid-row' + (index % 2 & opts. striped? 'Datagrid-row-alt': '') + classValue + '"'; var style = styleValue? 'Style = "'+ styleValue + '"':'';
  19. // Get the class and style attributes for this row // var cls = (index % 2 & opts. striped )? 'Class = "datagrid-row-alt" ': 'class = "datagrid-row "';
  20. // Var styleValue = opts. rowStyler? Opts. rowStyler. call (target, index, rows [I]): ''; // var style = styleValue? 'Style = "'+ styleValue + '"':'';
  21. Var rowId = state. rowIdPrefix + '-' + (frozen? ) + '-' + Index; table. push ('
  22. Table. push ('
  23. ']; '); ');
    ');
  24. ((Container).html (table. join (''));},
  25. /**
  26. * For the onBeforeRender event, you must first understand two points: * 1-when the loadData method is called to load data, this event will be triggered before the internal rend of loadData.
  27. * When using the 2-url method, the loadData method is used to load data after obtaining remote data. Therefore, the url method also triggers the onBeforeRender event * @ param {DOM} target datagrid instance's host DOM object.
  28. * @ Return {[type]} [description] */
  29. OnBeforeRender: function (target) {var state = $. data (target, 'datagrid ');
  30. Var opts = state. options; var dc = state. dc;
  31. Var view = this; // Delete the onLoadSuccess event to prevent the event from being triggered. Back up the event to state. onLoadSuccess.
  32. State. onLoadSuccess = opts. onLoadSuccess; opts. onLoadSuccess = function (){};
  33. Opts. finder. getRow = function (t, p ){
  34. Var index = (typeof p = 'object ')? P. attr ('datagrid-row-Index'): p; var row = $. data (t, 'datagrid '). data. rows [index];
  35. If (! Row) {// under which conditions cannot be obtained? Var v = $ (t). datagrid ('options'). view;
  36. Row = v. rows [index-v. index];}
  37. Return row ;};
  38. Dc. body1.add (dc. body2). empty ();
  39. This. rows = undefined; // binds the tr to view. rows has this. r1 = this. r2 = []; // view. r1 and viwe. r2 stores references to tr on the first page and tr on the last page respectively.
  40. // Do not take it for granted here. Only events are bound. When is the first time data is loaded, when is the event triggered? // This problem has to be traced back to the loadData method, after each loadData operation, triggerHandler is used to trigger scroll.
  41. Dc. body2.unbind ('. datagrid'). bind ('scroll. datagrid ', function (e) {if (state. onLoadSuccess ){
  42. Opts. onLoadSuccess = state. onLoadSuccess; // restores the onLoadSuccess event state. onLoadSuccess = undefined;
  43. } If (view. scrollTimer) {// clear the timer
  44. ClearTimeout (view. scrollTimer );}
  45. // View. scrollTimer = setTimeout (function (){
  46. Scrolling. call (view) ;}, 50 );
  47. });
  48. Function scrolling () {if (dc. body2.is (': empty') {// dc. body2 corresponds to common column data. if it is null, there is no data.
  49. // If there is no data, try to load the data reload. call (this );
  50. } Else {var firstTr = opts. finder. getTr (target, this. index, 'body', 2 );
  51. Var lastTr = opts. finder. getTr (target, 0, 'last', 2); var headerHeight = dc. view2.children ('div. datagrid-head'). outerHeight ();
  52. Var top = firstTr. position (). top-headerHeight; var bottom = lastTr. position (). top + lastTr. outerHeight ()-headerHeight;
  53. If (top> dc. body2.height () | bottom <0 ){
  54. Reload. call (this);} else if (top> 0 ){
  55. Var page = Math. floor (this. index/opts. pageSize); this. getRows. call (this, target, page, function (rows ){
  56. This. r2 = this. r1; this. r1 = rows;
  57. This. index = (page-1) * opts. pageSize; this. rows = this. r1.concat (this. r2 );
  58. This. populate. call (this, target );});
  59. } Else if (bottom <dc. body2.height () {// var page = Math. floor (this. index/opts. pageSize) + 2;
  60. If (this. r2.length) {page ++;
  61. } This. getRows. call (this, target, page, function (rows ){
  62. If (! This. r2.length) {this. r2 = rows;
  63. } Else {this. r1 = this. r2;
  64. This. r2 = rows; this. index + = opts. pageSize;
  65. } This. rows = this. r1.concat (this. r2 );
  66. This. populate. call (this, target );});
  67. }}
  68. Function reload (){
  69. Var top = $ (dc. body2 ). scrollTop (); // the rolled-up height var index = Math. floor (top/25); // obtain the row index to be rolled up. For example, roll up a row and a half 37.5 row, and the index is 1.
  70. Var page = Math. floor (index/opts. pageSize) + 1; // obtain the number of pages. If there are 10 entries per page, roll up the page by 262.5 and the page is 2
  71. This. getRows. call (this, target, page, function (rows) {this. index = (page-1) * opts. pageSize; // view. index stores the index of the first row of the page.
  72. This. rows = rows; // view. rows stores the tr this. r1 = rows;
  73. This. r2 = []; this. populate. call (this, target );
  74. Dc. body2.triggerHandler ('scroll. datagrid ');});
  75. }}
  76. },
  77. GetRows: function (target, page, callback) {var state = $. data (target, 'datagrid ');
  78. Var opts = state. options; var index = (page-1) * opts. pageSize;
  79. Var rows = state. data. rows. slice (index, index + opts. pageSize); if (rows. length) {// This method loads all the data at a time. You can retrieve the data directly from the local javascript array.
  80. Callback. call (this, rows );
  81. } Else {// lazy loading method var param = $. extend ({}, opts. queryParams ,{
  82. Page: page, rows: opts. pageSize
  83. }); If (opts. sortName ){
  84. $. Extend (param, {sort: opts. sortName,
  85. Order: opts. sortOrder });
  86. } If (opts. onBeforeLoad. call (target, param) = false) return;
  87. $ (Target). datagrid ('loading ');
  88. Var result = opts. loader. call (target, param, function (data) {$ (target). datagrid ('loaded ');
  89. Var data = opts. loadFilter. call (target, data); callback. call (opts. view, data. rows );
  90. // Opts. onLoadSuccess. call (target, data) ;}, function (){
  91. $ (Target). datagrid ('loaded'); opts. onLoadError. apply (target, arguments );
  92. }); If (result = false ){
  93. $ (Target). datagrid ('loaded ');}
  94. }},
  95. Populate: function (target ){
  96. Var state = $. data (target, 'datagrid '); var opts = state. options;
  97. Var dc = state. dc; var rowHeight = 25;
  98. If (this. rows. length ){
  99. Opts. view. render. call (opts. view, target, dc. body2, false); opts. view. render. call (opts. view, target, dc. body1, true );
  100. // Have you seen that the scroll bar has such a large space? Padding! Dc.body1.add(dc.body2).children('table.datagrid-btable'hangzhou.css ({
  101. PaddingTop: this. index * rowHeight, paddingBottom: state. data. total * rowHeight-this. rows. length * rowHeight-this. index * rowHeight
  102. }); Opts. onLoadSuccess. call (target ,{
  103. Total: state. data. total, rows: this. rows
  104. });}
  105. }});

    Analysis conclusion virtualScrollView is based on setting up and down padding of div to simulate a large amount of data. We only support two methods for virtualScrollView of trEasyUI, which is a little more visible: one is to request all data at a time; the other is to request ajax to pageSize each time. The virtualScrollView of EasyUI shows 2 * pageSize tr values (except for initial loading, at this time, only 1 * tr of pageSize is drawn. The virtualScrollView of EasyUI forcibly regards the Row Height as 25px. If you set the Row Height not 25px, this view cannot work normally because only 2 * pageSize tr values are drawn, so the dategrid height cannot be set to more than 2*25 * pageSize pixels, if the value is exceeded, the visible area will be left White. If the loadData method is used to load data, the loadData input parameter does not need the total attribute, as long as it is a rows array, and the total will be automatically calculated within loadData.

    For the previous points, let's take a look at the comments I have written in the source code. If you have a poor foundation, you just need to understand them. If you have a good foundation, you 'd better thoroughly study them.

    An endless loop in the background of a Bug request

    If the url method fails to load data for the first time, it will continuously request the background. If the callback function does not receive the rows, the scorll event should not be triggered because the scroll event will request background data. I have to add the condition:

    1. If (rows & rows. length> 0) {dc. body2.triggerHandler ('scroll. datagrid ');
    2. } Secondary request background

      In the url mode, if the data returned by the background is insufficient to fill the table height, the background will be repeatedly requested (note that only one request is repeated here, different from the first bug ). The reason for this problem is also very simple. In fact, in this case, the datagrid height is a little large, but there are only a few pieces of data in the background, resulting in only a batch of data, this batch of data is not enough to fill the height of the table's visible area. We can add a condition to the call to the getRows method in Row 3:

      1. If (this. rows. length = opts. pageSize) {this. getRows. call (this, target, page, function (rows ){
      2. If (! This. r2.length) {this. r2 = rows;
      3. } Else {this. r1 = this. r2;
      4. This. r2 = rows; this. index + = opts. pageSize;
      5. } This. rows = this. r1.concat (this. r2 );
      6. This. populate. call (this, target );});
      7. }

        This. rows is a batch of currently drawn rows. If the number of rows is not as large as pageSize, it means no data request is required.

        VirtualScrollView is a good Optimization Method and will be applied more and more widely in the future. Whether the VirtualScrollView of EasyUI supports editor or not is not supported by me, if you are interested, you can study it.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.