Question

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.

Était-ce utile?

La solution

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top