Pregunta

I have been reading forum posts and tweaking examples for a number of days now and have not found a solution to the following problem. I am evaluating the creation of large grids (here, big tables with many columns, rows and features) and I am trying Sencha ExtJS for this purpose (I am evaluating several options and Sencha seems very good for large grids).

My scenario is that I want to create a grid, with 20k rows, 100+ columns, variable row height, grouping, some special rendered cells (table in cells) and using buffered rendering. I want to have the entire (fake) data all at once loaded, then use the buffer rendering. My problem is that I have trouble with the performance (scrolling down). I am not sure what I should specify to increase speed (direct row height for each cell, or some *bufferZone option).

I am a beginner with Sencha still, so I am not sure I am using buffered rendering (and buffered storing) properly (have only added the pageSize, and *bufferZone props to the grid itself). Therefore, I would be grateful if someone could advise if possible on what I am doing wrong.

    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();
    })

I am aware that using many columns and variable row height does have an impact on performance. But, I have seen the infinite scrolling in grid with tuner example, which allows the large number of columns and still works fine.

Using a buffered store for my "fake data" throws some problem regarding the fact that I have grouping enabled; without grouping it complains about not having a url for my datastore. Not sure how to combine all settings to make scrolling work fine (acceptable performance meaning no freeze of view port) in my scenario.

¿Fue útil?

Solución

Though the answer is scenario-dependent (relative to the store/data), in a nuthshell, the idea for fast rendering seems to be use scrolling (buffered store and rendering):

It is one or the other .. you cannot load all data, then buffer it .. The entire goal of buffering is to load only what is to be viewable ( with buffers top and bottom )

See this for more details and a longer discussion.

Also see the ExtJS "infinite scroll with tuner" demo example (from their downloadable package package).

Please feel free to add more input/feedback regarding this subject.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top