在全功能网格中缓存滚动(具有许多列和可变行高)

时间:2014-04-09 14:04:34

标签: javascript performance extjs scroll extjs4.2

我一直在阅读论坛帖子和调整示例已经很多天了,并没有找到解决以下问题的方法。我正在评估大网格的创建(这里是包含许多列,行和特征的大表),我正在为此目的尝试Sencha ExtJS(我正在评估几个选项,Sencha似乎非常适合大网格)。

我的场景是我要创建一个网格,包括20k行,100多列,可变行高,分组,一些特殊渲染单元格(单元格中的表格)和使用缓冲渲染。我希望一次加载整个(假)数据,然后使用缓冲区渲染。我的问题是我的性能有问题(向下滚动)。我不确定应该指定什么来提高速度(每个单元格的直接行高,或者某些* bufferZone选项)。

我仍然是Sencha的初学者,所以我不确定我是否正确使用缓冲渲染(和缓冲存储)(只将pageSize和* bufferZone道具添加到网格本身)。因此,如果有人可以就我做错的事情提出建议,我将不胜感激。

    Ext.Loader.setConfig({enabled: true});
    Ext.Loader.setPath('Ext.ux', '../ux/');
    Ext.require([
        'Ext.grid.*',
        'Ext.data.*',
        'Ext.util.*',
        'Ext.grid.plugin.BufferedRenderer'
    ]);

    Ext.define('Employee', {
        extend: 'Ext.data.Model',
        fields: [{
            name: 'employeeNo'
        }, {
            name: 'rating',
            type: 'int'
        }, {
            name: 'salary',
            type: 'float'
        }, {
            name: 'forename'
        }, {
            name: 'surname'
        }, {
            name: 'email'
        }, {
            name: 'department'
        }, {
            name: 'dob',
            type: 'date',
            dateFormat: 'Ymd'
        }, {
            name: 'joinDate',
            type: 'date',
            dateFormat: 'Ymd'
        }, {
            name: 'noticePeriod'
        }, {
            name: 'sickDays',
            type: 'int'
        }, {
            name: 'holidayDays',
            type: 'int'
        }, {
            name: 'holidayAllowance',
            type: 'int'
        },
        'rowHeight'],
        idField: 'employeeNo'
        });


    Ext.onReady(function() {    

        var COMPLEXITY_OVERFLOW = 3,
        COMPLEXITY_MEDIUM = 2,
        COMPLEXITY_SIMPLE = 1,
        COMPLEXITY_DEFAULT = COMPLEXITY_SIMPLE; 

        var maxRows = 20000,
        maxCols = 100,
        scenario = COMPLEXITY_MEDIUM;

        var fakeDataStore = Ext.create('Ext.data.Store', {
                id: 'fakeDataStore',
                groupField: 'department',
                model: 'Employee'
                //pageSize: 1000,
                //trailingBufferZone: 80,
                //leadingBufferZone: 50,
                //purgePageCount: 0,
                //buffered: false
    //            proxy: {
    //                type: 'memory'
    //            }
        });

        var cellEditing = Ext.create('Ext.grid.plugin.CellEditing', {
            clicksToEdit: 1
        });



        function renderSimple(v, cellValues, rec) {
            return rec.get('forename') + ' ' + rec.get('surname');
        }

        function renderHtmlTable(value, metaData, record, row, col, store, gridView) {
            return "<table border='1' style='width:150px;'>"
            + "<tr><td>" + record.get('forename') + "</td><td>"+ record.get('surname') + "</td><td>50</td></tr>"
            + "<tr><td>" + record.get('forename') + "</td><td>"+ record.get('surname') + "</td><td>90</td></tr>"
            + "<tr><td>" + record.get('forename') + "</td><td>"+ record.get('surname') + "</td><td>30</td></tr></table>";
        }


        /*******************************/
        /** DEFINE AND CREATE COLUMNS **/
        var cols = [{
            xtype: 'rownumberer', //col 1
            width: 60,
            //locked   : true,
            sortable: false,
            xhooks: {
                renderer: function(v, meta, record) {
                    meta.tdAttr = 'style="vertical-align:center;height:' + record.data.rowHeight + 'px"';
                    return this.callParent(arguments);
                }
            }
        }, {
            text: 'Id',//col 2
            sortable: true,
    //            locked   : true,
            dataIndex: 'employeeNo',
            groupable: false,
            width: 70
        }, {
            text: 'Name',//col 3
            sortable: true,
            dataIndex: 'name',
            groupable: false,
            renderer: function(v, cellValues, rec) {
                return rec.get('forename') + ' ' + rec.get('surname');
            },
            field: {
                allowBlank: false
            },
            width: 120
        },{
            text: 'Complex',
            dataIndex: 'name',
            width: 200,
            renderer: renderHtmlTable
        },
        {
            text: 'Date of birth',//col 4
            dataIndex: 'dob',
            xtype: 'datecolumn',
            groupable: false
        }, {
            text: 'Join date',//col 5
            dataIndex: 'joinDate',
            xtype: 'datecolumn',
            groupable: false
        }, {
            text: 'Notice period',//col 6
            dataIndex: 'noticePeriod',
            groupable: false
        }, {
            text: 'Email address',//col 7
            dataIndex: 'email',
            width: 200,
            groupable: false,
            renderer: function(v) {
                return '<a href="mailto:' + v + '">' + v + '</a>';
            }
        }, {
            text: 'Department',//col 8 //NOT A COLUMN - it's hidden
            dataIndex: 'department',
            hidden: true,
            hideable: false,
            groupable: false
        }, {
            text: 'Work Related Info',
            columns: [{
                text: 'Absences',
                columns: [{
                    text: 'Illness',//col 9
                    dataIndex: 'sickDays',
                    width: 40,
                    groupable: false
                }, {
                    text: 'Holidays',//col 10
                    dataIndex: 'holidayDays',
                    width: 50,
                    groupable: false,
                    field: {
                        xtype: 'combobox',
                        typeAhead: true,
                        triggerAction: 'all',
                        selectOnTab: true,
                        store: [
                                [3,3],
                                [4,4],
                                [5,5]
                                ],
                                lazyRender: true,
                                listClass: 'x-combo-list-small'
                    }
                }, {
                    text: 'Holday Allowance',//col 11
                    dataIndex: 'holidayAllowance',
                    width: 50,
                    groupable: false
                }]
            }, {
                text: 'Rating',//col 12
                width: 50,
                sortable: true,
                dataIndex: 'rating',
                groupable: false
            }, {
                text: 'Salary',//col 13
                width: 110,
                sortable: true,
                dataIndex: 'salary',
                align: 'right',
                renderer: Ext.util.Format.usMoney,
                groupable: false
            },{
                text: 'Rating',//col 14
                width: 50,
                sortable: true,
                dataIndex: 'rating',
                groupable: false
            }, {
                text: 'Salary',//col 15
                width: 110,
                sortable: true,
                dataIndex: 'salary',
                align: 'right',
                renderer: Ext.util.Format.usMoney,
                groupable: false
            },{
                text: 'Rating',//col 16
                width: 50,
                sortable: true,
                dataIndex: 'rating',
                groupable: false
            }, {
                text: 'Salary',//col 17
                width: 110,
                sortable: true,
                dataIndex: 'salary',
                align: 'right',
                renderer: Ext.util.Format.usMoney,
                groupable: false
            }]
        }];

        /* add extra columns */
        var maxExtraCols = maxCols;
        /** what types of extra columns we should add */
        if (scenario == COMPLEXITY_SIMPLE) {
            for (var i = 0; i < maxExtraCols; i++) {
                cols.push({
                    text: 'Name ' + i ,
                    sortable: false,
                    dataIndex: 'name',
                    groupable: false,
                    renderer: renderSimple,
                    width: 70
                });
            }    
        } else if (scenario == COMPLEXITY_MEDIUM) {
            for (var i = 0; i < maxExtraCols; i++) {
                cols.push({
                    text: 'Name ' + i ,
                    sortable: false,
                    dataIndex: 'name',
                    groupable: false,
                    renderer: (i % 2 == 0 ? renderHtmlTable : renderSimple),
                    width: 200
                });
            }    
        } else if (scenario == COMPLEXITY_OVERFLOW) {
            //render more columns
            var tempMax = maxExtraCols / 3;
            for (var i = 0; i < tempMax; i++) {
                cols.push({
                    text: 'Name ' + i ,
                    sortable: false,
                    dataIndex: 'name',
                    groupable: false,
                    renderer: renderSimple,
                    width: 70
                });
            }
            var tempMax2 = tempMax * 2;
            for (var i = tempMax; i < tempMax2; i++) {
                cols.push({
                    text: 'Name ' + i ,
                    sortable: false,
                    dataIndex: 'name',
                    groupable: false,
                    renderer: renderHtmlTable,
                    width: 70
                });
            }
            for (var i = tempMax2; i < maxExtraCols; i++) {
                cols.push({
                    text: 'Name ' + i ,
                    sortable: false,
                    dataIndex: 'name',
                    groupable: false,
                    renderer: (i % 2 == 0 ? renderSimple : renderHtmlTable),
                    width: 60
                });
            }
        }

        /**********************/
        /** CREATE GRID PANEL **/
        console.log('Create grid begin');
        var grid = Ext.create('Ext.grid.Panel', {
            width: 1200,
            height: 600,
            title: 'Buffered Grid Testing',
            store: fakeDataStore,
            loadMask: true,
            verticalScroller: {
                variableRowHeight: true
            },
            invalidateScrollerOnRefresh: false,
    //            disableSelection: true,
            plugins: [{
                ptype: 'bufferedrenderer'
                //numFromEdge: 20,
                //trailingBufferZone: 40,
                //leadingBufferZone: 80
            }, cellEditing],
            selModel: {
                pruneRemoved: false
            },
            viewConfig: {
                trackOver: false
            },
            features: [{
                ftype: 'groupingsummary',
                groupHeaderTpl: 'Department: {name}',
                showSummaryRow: false
            }],
            // grid columns
            columns:  cols,
            bbar: [{
                labelWidth: 80,
                fieldLabel: 'Jump to row',
                xtype: 'numberfield',
                minValue: 1,
                maxValue: maxRows,
                allowDecimals: false,
                itemId: 'gotoLine',
                enableKeyEvents: true,
                listeners: {
                    specialkey: function(field, e){
                        if (e.getKey() === e.ENTER) {
                            jumpToRow();
                        }
                    }
                }
            }, {
                text: 'Go',
                handler: jumpToRow
            }],
            renderTo: Ext.getBody()
        });


        var jumpToRow = function(){
            var fld = grid.down('#gotoLine');
            if (fld.isValid()) {
                grid.view.bufferedRenderer.scrollTo(fld.getValue() - 1, true);
            }    
        };

        /**********************/
        /** CREATE FAKE DATA**/
        var data = [];
        function random(from, to) {
            return Math.floor(Math.random() * (to - from + 1) + from);
        }
        function getEmployeeNo() {
            var out = '',
            i = 0;
            for (; i < 6; ++i) {
                out += random(0, 7);
            }
            return out;
        }

        /**
         * Returns an array of fake data
         * @param {Number} count The number of fake rows to create data for
         * @return {Array} The fake record data, suitable for usage with an ArrayReader
         */
        function createFakeData(count, data) {
            var firstNames   = ['Ed', 'Tommy', 'Aaron', 'Abe', 'Jamie', 'Adam', 'Dave', 'David', 'Jay', 'Nicolas', 'Nige'],
            lastNames    = ['Spencer', 'Maintz', 'Conran', 'Elias', 'Avins', 'Mishcon', 'Kaneda', 'Davis', 'Robinson', 'Ferrero', 'White'],
            departments  = ['Engineering', 'Sales', 'Marketing', 'Managment', 'Support', 'Administration'],
            ratings      = [1, 2, 3, 4, 5],
            salaries     = [100, 400, 900, 1500, 1000000],
            noticePeriods= ['2 weeks', '1 month', '3 months'],
            i;


            for (i = 0; i < (count || 25); i++) {
                var firstName   = firstNames[random(0, firstNames.length - 1)],
                lastName    = lastNames[random(0, lastNames.length - 1)],
                name        = Ext.String.format("{0} {1}", firstName, lastName),
                email       = firstName.toLowerCase() + '.' + lastName.toLowerCase() + '@sentcha.com',
                rating      = ratings[(name === 'Nige White') ? 0 : random(0, ratings.length - 1)],
                salary      = salaries[(name === 'Nige White') ? 4 : random(0, salaries.length - 1)],
                department  = departments[random(0, departments.length - 1)],
                ageInYears  = random(23, 55),
                dob         = new Date(new Date().getFullYear() - ageInYears, random(0, 11), random(0, 31)),
                joinDate    = new Date(new Date() - random(60, 2000) * 1000 * 60 * 60 * 24),
                sickDays    = random(0, 10),
                holidayDays = random(0, 10),
                holidayAllowance = random(20, 40);


                data.push({
                    employeeNo: getEmployeeNo(),
                    rating: rating,
                    salary: salary,
                    forename: firstName,
                    surname: lastName,
                    email: email,
                    department: department,
                    dob: dob,
                    joinDate: joinDate,
                    sickDays: sickDays,
                    holidayDays: holidayDays,
                    holidayAllowance: holidayAllowance,
                    noticePeriod: noticePeriods[random(0, noticePeriods.length - 1)],
                    rowHeight: (i == count - 1) ? 150 : Ext.Number.randomInt(85, 110)
                });
            }
        }
        function makeData() {
            //create fake data
            var start = new Date().getTime();
            createFakeData(maxRows, data);
            var end = new Date().getTime();
            var time = end - start;
            console.log('Total time to create fake data: ' + time)
            //load data to store
            start = new Date().getTime();
            fakeDataStore.loadData(data);
            end = new Date().getTime();
            time = end - start;
            console.log('Total time to load fake data: ' + time)
        }
        makeData();
    })

我知道使用许多列和可变行高确实会对性能产生影响。但是,我已经看到网格中的无限滚动与调谐器示例,它允许大量的列,仍然可以正常工作。

对我的“假数据”使用缓冲存储会引发一些关于我启用了分组的​​问题;没有分组它抱怨没有我的数据存储区的URL。在我的场景中,不确定如何组合所有设置以使滚动工作正常(可接受的性能意味着没有冻结视口)。

1 个答案:

答案 0 :(得分:0)

虽然答案是依赖于场景的(相对于商店/数据),但是在快餐中,快速渲染的想法似乎是使用滚动(缓冲存储和渲染):

  

它是一个或者其他..你不能加载所有数据,然后缓冲它..   缓冲的整个目标是仅加载可查看的内容(   缓冲区顶部和底部)

有关详细信息和更长时间的讨论,请参阅this

另请参阅ExtJS&#34;无限滚动调谐器&#34;演示示例(来自可下载的软件包)。

请随时添加有关此主题的更多输入/反馈。